@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
@@ -2,21 +2,33 @@
2
2
  * datapath.ts · 灵枢数据根解析(记忆写入路径可配置)
3
3
  *
4
4
  * 与 Python 侧 `md_cg/datapath.py` **同口径**——两侧读同一份
5
- * `<插件仓>/data/paths.json`,避免大脑与脚本对「记忆真源在哪」各持一说。
5
+ * `<用户级状态根>/paths.json`(新位置;旧版 `<插件仓>/data/paths.json`
6
+ * 仍兼容读),避免大脑与脚本对「记忆真源在哪」各持一说。
7
+ *
8
+ * 为什么默认不再取「插件仓自身 data/」(issue #18 相邻问题,数据丢失级):
9
+ * DSH 插件按 hoisted 布局装在 `<profile>/node_modules/<pkg>`,`pnpm` 更新该包
10
+ * 会**整个替换包目录**——运行时数据落在包内(`<pkg>/data`)时,每次更新成功
11
+ * 即连目录一起删掉(贡献者实机实证:`data/` 54 文件 → 0,46 条记忆节点只能
12
+ * 靠人工备份回填);`paths.json` 同址,用户配置一并丢失。故默认数据根与配置
13
+ * 文件一律落**用户级状态根**(见 `stateRoot()`),与包目录彻底解耦。
6
14
  *
7
15
  * 优先级(高 → 低):
8
16
  * 1. 环境变量 `MDCG_DATA_ROOT`(数据根)/ `MDCG_ROOT`(认知图根)
9
- * 2. 用户可编辑配置 `<插件仓>/data/paths.json` 的 `data_root` / `root`
17
+ * 2. 用户可编辑配置 `paths.json` 的 `data_root` / `root`
18
+ * (位置:`<用户级状态根>/paths.json`;旧 `<插件仓>/data/paths.json` 兼容读)
10
19
  * 3. 插件配置项 `mdcg.root`(可为空=未指定)
11
- * 4. 默认 `<插件仓>/data/mdcg`(**自身仓库**,与进程 cwd 解耦)
20
+ * 4. 默认 `<用户级状态根>/data/mdcg`
12
21
  *
13
- * 为什么默认不再取「相对 cwd 的 data/mdcg」:
22
+ * 为什么默认不取「相对 cwd 的 data/mdcg」:
14
23
  * node 侧插件与 python 侧脚本的 cwd 不同,相对路径会漂移成
15
24
  * `<cwd>/data/mdcg`。历史事故:宿主 cwd 落在 AEIS 时,记忆真源
16
25
  * 分裂到 `AEIS/data/mdcg`,与插件仓 `data/` 变成互不可见的两处。
17
- * 锚定 `package.json` 所在目录后,默认值稳定且可预期。
26
+ *
27
+ * 旧数据接手:`migrateLegacyData()` 在默认数据根生效且旧包内位置仍有内容时
28
+ * 一次性**复制**(不删除、不覆盖)到用户级位置——见该函数注释。
18
29
  */
19
- import { existsSync, readFileSync } from 'node:fs';
30
+ import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync } from 'node:fs';
31
+ import { homedir, tmpdir } from 'node:os';
20
32
  import { delimiter, dirname, isAbsolute, join, resolve } from 'node:path';
21
33
  import { fileURLToPath } from 'node:url';
22
34
  export const ENV_DATA_ROOT = 'MDCG_DATA_ROOT';
@@ -41,9 +53,52 @@ export function repoRoot() {
41
53
  cachedRepoRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
42
54
  return cachedRepoRoot;
43
55
  }
44
- /** 用户可编辑的路径配置文件(固定在插件仓 data/,不受 dataRoot 取值影响)。 */
56
+ export const ENV_STATE_ROOT = 'MDCG_STATE_ROOT';
57
+ let cachedStateRoot = null;
58
+ /**
59
+ * 用户级状态根(**插件包目录之外**):路径配置与默认数据根的落点。
60
+ * 优先级:`MDCG_STATE_ROOT` → `$DSH_HOME/.dsh-memory` → `~/.dsh/.dsh-memory`。
61
+ * `DSH_HOME` **未必**由宿主注入(本仓 `dsh/dsh-web-start.bat` 自行 set),
62
+ * 故家目录兜底;`~/.dsh` 是本插件既有约定(`bridge.ts` 调试日志同址)。
63
+ */
64
+ export function stateRoot() {
65
+ if (cachedStateRoot)
66
+ return cachedStateRoot;
67
+ const env = process.env[ENV_STATE_ROOT];
68
+ if (env) {
69
+ cachedStateRoot = resolve(env);
70
+ return cachedStateRoot;
71
+ }
72
+ const dshHome = process.env['DSH_HOME'];
73
+ cachedStateRoot = dshHome
74
+ ? join(resolve(dshHome), '.dsh-memory')
75
+ : join(homedir(), '.dsh', '.dsh-memory');
76
+ return cachedStateRoot;
77
+ }
78
+ /** 旧版落点(插件包内 `data/`)——更新时会被整个替换,只作兼容读与迁移源。 */
79
+ export function legacyDataRoot() {
80
+ return join(repoRoot(), 'data');
81
+ }
82
+ function samePath(a, b) {
83
+ return process.platform === 'win32' ? a.toLowerCase() === b.toLowerCase() : a === b;
84
+ }
85
+ /**
86
+ * 用户可编辑的路径配置文件:
87
+ * `<用户级状态根>/paths.json` → 不存在则回落**旧** `<插件仓>/data/paths.json`
88
+ * (兼容读)→ 都不存在返回新位置(首次写入时创建)。
89
+ * 不做自动复制:配置文件是「谁说了算」的唯一真源,悄悄复制会让旧件的后续编辑
90
+ * 静默失效——迁移只搬数据面,配置显式交给用户搬(或用 `set_user_root`)。
91
+ */
45
92
  export function pathsFile() {
46
- return join(repoRoot(), 'data', 'paths.json');
93
+ const current = join(stateRoot(), 'paths.json');
94
+ if (existsSync(current))
95
+ return current;
96
+ const legacy = join(legacyDataRoot(), 'paths.json');
97
+ return existsSync(legacy) ? legacy : current;
98
+ }
99
+ /** 生效中的 paths.json 位置来源:`user`=用户级 / `legacy`=旧包内(兼容读)。 */
100
+ export function pathsFileSource() {
101
+ return samePath(dirname(pathsFile()), stateRoot()) ? 'user' : 'legacy';
47
102
  }
48
103
  function readUserPaths() {
49
104
  try {
@@ -59,8 +114,133 @@ function readUserPaths() {
59
114
  function anchor(p) {
60
115
  return isAbsolute(p) ? p : resolve(repoRoot(), p);
61
116
  }
117
+ /** 默认数据根 = 用户级状态根下 `data/`(**插件包目录之外**,更新不触碰)。 */
62
118
  export function defaultDataRoot() {
63
- return join(repoRoot(), 'data');
119
+ return join(stateRoot(), 'data');
120
+ }
121
+ /** dst 为「空目录」时 true(不存在/非目录/有内容均为 false)。 */
122
+ function isEmptyDir(p) {
123
+ try {
124
+ return readdirSync(p).length === 0;
125
+ }
126
+ catch {
127
+ return false;
128
+ }
129
+ }
130
+ /**
131
+ * 一次性迁移:把旧版留在**插件包内** `data/` 的数据面复制到用户级默认数据根。
132
+ *
133
+ * 触发条件(三条同时满足,缺一不动):
134
+ * ① 数据根未被显式配置(env `MDCG_DATA_ROOT` / paths.json 的 `data_root` 都未设,
135
+ * 即 `dataRoot()` 恰为默认值)——显式配置是用户的决定,越权搬运等于改他的真源;
136
+ * ② 旧位置存在;
137
+ * ③ 目标顶层条目不存在或为空目录(**不覆盖、不合并**已有数据)。
138
+ *
139
+ * 只复制不删除(旧目录随后由 pnpm 更新自然移除);`paths.json` 不复制
140
+ * (配置走 `pathsFile()` 兼容读,复制会制造「哪个文件说了算」的两说)。
141
+ * 任何异常逐项吞掉并记入 `failed`——迁移是增益,不该成为启动失败源。
142
+ *
143
+ * 边界(如实):pnpm 更新是**先替换包目录再启动新代码**,故旧数据在升级瞬间
144
+ * 即已消失,本函数只能接手「旧位置那时仍在」的情形(手工安装、就地覆盖、
145
+ * 或包目录未被清理);已在升级中丢掉的数据无法由此恢复——发布说明须提示
146
+ * 0.4.8 及更早用户升级前备份 `<profile>/node_modules/<pkg>/data/`。
147
+ */
148
+ export function migrateLegacyData() {
149
+ const from = legacyDataRoot();
150
+ const to = defaultDataRoot();
151
+ const out = { ran: false, reason: '', from, to, copied: 0, failed: [] };
152
+ if (!samePath(dataRoot(), to)) {
153
+ out.reason = '数据根为显式配置(env/paths.json),不迁移';
154
+ return out;
155
+ }
156
+ if (!existsSync(from)) {
157
+ out.reason = '旧位置不存在(多为更新时已随包目录被替换)';
158
+ return out;
159
+ }
160
+ let entries;
161
+ try {
162
+ entries = readdirSync(from);
163
+ }
164
+ catch {
165
+ out.reason = '旧位置不可读';
166
+ return out;
167
+ }
168
+ const names = entries.filter((n) => n !== 'paths.json');
169
+ if (!names.length) {
170
+ out.reason = '旧位置无数据';
171
+ return out;
172
+ }
173
+ for (const name of names) {
174
+ const src = join(from, name);
175
+ const dst = join(to, name);
176
+ // 新位置已有实质内容 → 跳过(宁可少搬,不可覆盖)
177
+ if (existsSync(dst) && !isEmptyDir(dst))
178
+ continue;
179
+ try {
180
+ mkdirSync(to, { recursive: true });
181
+ cpSync(src, dst, { recursive: true, force: false, errorOnExist: false });
182
+ out.copied += 1;
183
+ }
184
+ catch {
185
+ out.failed.push(name);
186
+ }
187
+ }
188
+ out.ran = out.copied > 0;
189
+ if (!out.ran)
190
+ out.reason = out.failed.length ? '目标不可写' : '新位置已有数据,未覆盖';
191
+ return out;
192
+ }
193
+ export const ENV_CHILD_CWD = 'MDCG_CHILD_CWD';
194
+ let cachedRunRoot = null;
195
+ /**
196
+ * 子进程的工作目录(**必须落在插件包目录之外**)。
197
+ *
198
+ * 为什么不能沿用 `repoRoot()`:Windows 不允许删除/改名「正被某进程当作 CWD」
199
+ * 的目录。DSH 的插件按 hoisted 布局安装在 `<profile>/node_modules/<pkg>`,
200
+ * 而 `pnpm` 每次更新该包都要先 `rmdir` 包目录 → 子进程一旦把包目录当 CWD,
201
+ * 更新必然 `ERR_PNPM_EBUSY: resource busy or locked`(含升级回滚一起失败,
202
+ * 应用内永远升不动这个插件)。注意落点也**不能**是 `<包>/data`——它仍在包内,
203
+ * 实测同样 `err=32`。
204
+ *
205
+ * 落点要求「稳定存在」:进程 cwd 指向已消失的目录会引出新的怪问题,故按稳定度
206
+ * 排候选目录,取**第一个能建成**的:
207
+ * 1. `MDCG_CHILD_CWD`(显式覆盖)
208
+ * 2. `$DSH_HOME/.dsh-memory/run`
209
+ * 3. `~/.dsh/.dsh-memory/run` —— 家目录兜底:`DSH_HOME` **未必**由宿主注入
210
+ * (本仓 `dsh/dsh-web-start.bat` 是自行 `set DSH_HOME=%USERPROFILE%\.dsh`),
211
+ * 而 `~/.dsh` 是本插件既有约定(`bridge.ts` 调试日志、`token_store` 密钥环同址)
212
+ * 4. 系统临时目录(可能被清理,故排最后)
213
+ * 全部建不成时不抛错,交给 spawn 报错——cwd 落点只是防呆,不该成为启动失败源。
214
+ *
215
+ * 安全性:`python -m` 的模块解析由 `pythonPathValue()` 的 PYTHONPATH 独立保证,
216
+ * 不依赖 cwd;已实测 cwd=包目录 与 cwd=包外 时,MCP initialize 握手与
217
+ * tools/list 工具面(33 个工具)完全一致。
218
+ */
219
+ export function runRoot() {
220
+ if (cachedRunRoot)
221
+ return cachedRunRoot;
222
+ const candidates = [];
223
+ const override = process.env[ENV_CHILD_CWD];
224
+ if (override)
225
+ candidates.push(resolve(override));
226
+ const dshHome = process.env['DSH_HOME'];
227
+ if (dshHome)
228
+ candidates.push(join(resolve(dshHome), '.dsh-memory', 'run'));
229
+ candidates.push(join(homedir(), '.dsh', '.dsh-memory', 'run'));
230
+ candidates.push(join(tmpdir(), 'dsh-memory-run'));
231
+ for (const dir of candidates) {
232
+ try {
233
+ mkdirSync(dir, { recursive: true });
234
+ cachedRunRoot = dir;
235
+ return dir;
236
+ }
237
+ catch {
238
+ /* 该候选不可用(父目录只读等)→ 试下一个 */
239
+ }
240
+ }
241
+ const last = candidates[candidates.length - 1] ?? tmpdir();
242
+ cachedRunRoot = last;
243
+ return last;
64
244
  }
65
245
  /** 数据根(记忆/账本/运行态的父目录)。 */
66
246
  export function dataRoot() {
@@ -108,22 +288,28 @@ export function describeDataPaths(configured) {
108
288
  const dr = dataRoot();
109
289
  const mr = mdcgRoot(configured);
110
290
  const paths = readUserPaths();
111
- let source = 'default(插件仓自身 data/)';
291
+ const pfSource = pathsFileSource();
292
+ let source = 'default(用户级状态根 data/)';
112
293
  if (process.env[ENV_DATA_ROOT])
113
294
  source = `env:${ENV_DATA_ROOT}`;
114
295
  else if (process.env[ENV_MDCG_ROOT])
115
296
  source = `env:${ENV_MDCG_ROOT}`;
116
- else if (typeof paths['data_root'] === 'string' || typeof paths['root'] === 'string')
117
- source = 'paths.json';
297
+ else if (typeof paths['data_root'] === 'string' || typeof paths['root'] === 'string') {
298
+ source = pfSource === 'user' ? 'paths.json(用户级)' : 'paths.json(兼容读旧包内位置)';
299
+ }
118
300
  else if (configured && configured.trim())
119
301
  source = 'config.mdcg.root';
120
302
  return {
121
303
  repoRoot: repoRoot(),
304
+ stateRoot: stateRoot(),
122
305
  dataRoot: dr,
123
306
  mdcgRoot: mr,
124
307
  source,
125
308
  pathsFile: pathsFile(),
126
- isDefault: dr === defaultDataRoot(),
309
+ pathsFileSource: pfSource,
310
+ isDefault: samePath(dr, defaultDataRoot()),
311
+ legacyDataRoot: legacyDataRoot(),
312
+ legacyDataExists: existsSync(legacyDataRoot()),
127
313
  dataRootExists: existsSync(dr),
128
314
  mdcgRootExists: existsSync(mr),
129
315
  };
@@ -43,9 +43,12 @@ export interface MdcgOptions {
43
43
  args?: string[];
44
44
  /** 认知图根目录(MDCG_ROOT)。 */
45
45
  root: string;
46
- /** Python 子进程工作目录,默认**插件仓根**(issue #12:Python 只把 cwd
47
- * 注入 sys.path,宿主在插件仓外启动时 `python -m md_cg.mcp_server`
48
- * 找不到随包 md_cg → 必然 ModuleNotFoundError → 静默降级只读 guest)。 */
46
+ /** Python 子进程工作目录,默认 `runRoot()`——**插件包目录之外**的稳定目录
47
+ * (issue #18:Windows 不允许删除/改名「正被某进程当作 cwd」的目录,
48
+ * cwd 落在包内会让 pnpm 更新本包必然 `ERR_PNPM_EBUSY` 且永不自愈)。
49
+ * 模块解析**不依赖 cwd**:`python -m` 靠 `PYTHONPATH`
50
+ * (见 `pythonPathValue()`)解析随包 md_cg,issue #12 口径不变。
51
+ * 显式传入本项时完全尊重原值。 */
49
52
  cwd?: string;
50
53
  /** 调用主体标识(MDCG_ACTOR)。私有内容按 (tenant, actor) 派生 DEK,
51
54
  * 故与迁移脚本 --actor 必须一致,否则读不到已迁移节点。 */
@@ -35,7 +35,7 @@
35
35
  * 的**所有写入**(记忆沉淀 / 角色定义 / 对话转录)统一经 `mdcg_remember`。
36
36
  */
37
37
  import { LingshuBridge } from '../bridge.js';
38
- import { pythonPathValue, repoRoot } from './datapath.js';
38
+ import { pythonPathValue, runRoot } from './datapath.js';
39
39
  const DEFAULT_ARGS = ['-m', 'md_cg.mcp_server'];
40
40
  /** 认知图依据强度:这些 basis 视为「强依据」,可支撑 pass。
41
41
  * 取值须在 md_cg.mdcg.VERIFICATION_BASIS 允许集内:
@@ -63,10 +63,11 @@ function collectItems(payload) {
63
63
  export class MdcgClient {
64
64
  bridge;
65
65
  constructor(opts) {
66
- // issue #12:cwd 与 PYTHONPATH 双保险锚定插件仓根——cwd 是 `python -m`
67
- // 解析随包包的主通道,PYTHONPATH 覆盖显式自定义 args 的场景。opts.env
68
- // 显式提供 PYTHONPATH 时完全接管(其展开在最后:显式配置原样尊重)。
69
- const cwd = opts.cwd ?? repoRoot();
66
+ // 子进程 cwd 必须落在插件包目录之外,否则 pnpm 更新本插件时
67
+ // rmdir 包目录会撞上 Windows 的「目录被当作 CWD」共享冲突 → ERR_PNPM_EBUSY。
68
+ // 模块解析不依赖 cwd:PYTHONPATH(pythonPathValue())已锚定随包 md_cg;
69
+ // opts.env 显式提供 PYTHONPATH 时完全接管(其展开在最后)。详见 datapath.runRoot()。
70
+ const cwd = opts.cwd ?? runRoot();
70
71
  const env = {
71
72
  // ⚠️ 必须显式 utf-8:Windows 下 piped 子进程默认 gbk + surrogateescape,
72
73
  // Node 写出的 UTF-8 中文会被解成孤立代理字符(\udcXX),md_cg 在落盘 /
package/lib/lib/mutual.js CHANGED
@@ -15,7 +15,7 @@ import { exec, spawn } from 'node:child_process';
15
15
  import * as fs from 'node:fs';
16
16
  import * as path from 'node:path';
17
17
  import * as os from 'node:os';
18
- import { pythonPathValue, repoRoot } from './datapath.js';
18
+ import { pythonPathValue, runRoot } from './datapath.js';
19
19
  const DEFAULTS = {
20
20
  heartbeatMs: 10 * 60 * 1000,
21
21
  warnMs: 25 * 60 * 1000,
@@ -120,9 +120,9 @@ export async function ensureHarness(python = 'python', opts = DEFAULTS) {
120
120
  windowsHide: true,
121
121
  detached: true,
122
122
  stdio: 'ignore',
123
- // issue #12 同类:锚定插件仓根 + PYTHONPATH,与 mdcg_client / token_store
124
- // 同口径——不随宿主 cwd 漂移;harness 包不在仓内时注入不劣化(回落用户环境)。
125
- cwd: repoRoot(),
123
+ // 同 mdcg_client:cwd 必须在包外(否则 pnpm 更新时 rmdir 包目录被 Windows
124
+ // 拒绝 → ERR_PNPM_EBUSY);模块解析由 PYTHONPATH 保证,见 datapath.runRoot()。
125
+ cwd: runRoot(),
126
126
  env: { ...process.env, PYTHONPATH: pythonPathValue() },
127
127
  });
128
128
  }
@@ -30,7 +30,7 @@ import { spawnSync } from 'node:child_process';
30
30
  import { chmodSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
31
31
  import { homedir } from 'node:os';
32
32
  import { dirname, join } from 'node:path';
33
- import { pythonPathValue, repoRoot } from './datapath.js';
33
+ import { pythonPathValue, runRoot } from './datapath.js';
34
34
  /** 默认密钥环路径:与 md_cg 的 `~/.mdcg/` 约定同目录(`_tokens.json` 亦在此)。 */
35
35
  export function defaultKeyringPath() {
36
36
  return join(homedir(), '.mdcg', 'token');
@@ -66,10 +66,9 @@ function issueToken(python, role, actor, clearance) {
66
66
  windowsHide: true,
67
67
  timeout: 15_000,
68
68
  // 与工作纪律第 15 条同源:显式 UTF-8 + PYTHONUTF8,规避 Windows GBK 解码异常。
69
- // issue #12:cwd+PYTHONPATH 锚定插件仓根——否则 npm 包形态下宿主在仓外
70
- // 启动时 `python -m md_cg.tokens` 找不到随包 md_cg,首启签发静默失败,
71
- // md_cg 侧降级只读 guest(写入全不落盘且无报错)。
72
- cwd: repoRoot(),
69
+ // 模块解析靠 PYTHONPATH 锚定随包 md_cg(不依赖 cwd);cwd 取包外目录,
70
+ // 否则 pnpm 更新本插件时 rmdir 包目录必报 ERR_PNPM_EBUSY。见 datapath.runRoot()。
71
+ cwd: runRoot(),
73
72
  env: {
74
73
  ...process.env,
75
74
  PYTHONPATH: pythonPathValue(),
package/md_cg/audit.py CHANGED
@@ -30,17 +30,19 @@ CONTENT_KINDS = {
30
30
  "work_done": "工作完成 → 工作项是否通过验收",
31
31
  "work_wip": "工作进行 → 是否已有完整成果 + 是否符合纪律",
32
32
  "ccg_marks": "CCG 六要素候选 → 编外验证方实测确认(生成方不得自证)",
33
+ "hyperedge": "跨端验证超边 → 回放重建比对(回执逐字段一致)",
33
34
  }
34
35
 
35
36
  # content_kind → 建议的 verification_basis
36
37
  # (见 nodefile.VERIFICATION_BASIS:compiler|test|measurement|formal_proof|data|textbook|public_kb|other)
37
38
  KIND_BASIS = {"code": "test", "image_desc": "measurement", "text": "other",
38
39
  "permission": "data", "work_done": "test", "work_wip": "other",
39
- "ccg_marks": "test"}
40
+ "ccg_marks": "test", "hyperedge": "test"}
40
41
 
41
42
  VERIFIERS = {}
42
43
 
43
44
 
45
+ # 生效条件:kind 属于 CONTENT_KINDS 且(kind 不在 VERIFIERS 或 override 为真值)时把 fn 写入 VERIFIERS[kind];kind 未知、或 kind 已在 VERIFIERS 而 override 为假值时抛 ValueError。
44
46
  def register_verifier(kind, fn, override=False):
45
47
  """注入/替换某类内容的验证器(外部能力接入点)。"""
46
48
  if kind not in CONTENT_KINDS:
@@ -50,6 +52,7 @@ def register_verifier(kind, fn, override=False):
50
52
  VERIFIERS[kind] = fn
51
53
 
52
54
 
55
+ # 生效条件:传入 state、kind、evidence(detail 可缺省为 None)时,返回以 state/kind/evidence 为键、basis 取 KIND_BASIS.get(kind)(查不到即 None)的判定字典。
53
56
  def _verdict(state, kind, evidence, detail=None):
54
57
  return {"state": state, "kind": kind, "basis": KIND_BASIS.get(kind),
55
58
  "evidence": evidence, "detail": detail}
@@ -57,6 +60,7 @@ def _verdict(state, kind, evidence, detail=None):
57
60
 
58
61
  # ---------- 规则库(合规 / 纪律) ----------
59
62
 
63
+ # 生效条件:path 为假值(None 或空串)时回落到 os.environ 的 MDCG_POLICY_FILE,所得路径仍为假值或 os.path.exists 判定为假时返回 {};否则按 json.load 读取,抛 OSError/ValueError 或结果非 dict 时返回 {},是 dict 则返回该 dict。
60
64
  def load_rulebook(path=None):
61
65
  """{"forbidden": [正则], "required": [正则]};缺失返回空规则。"""
62
66
  path = path or os.environ.get("MDCG_POLICY_FILE")
@@ -70,6 +74,7 @@ def load_rulebook(path=None):
70
74
  return rules if isinstance(rules, dict) else {}
71
75
 
72
76
 
77
+ # 生效条件:当 rules 的 forbidden 与 required 去空后非全空时,逐条对 text 做 re.search(非法正则跳过),无禁止命中且必需项全部命中才返回 ACCEPT,否则 REJECT;两类都为空时返回 DEFER。
73
78
  def _rule_check(text, rules):
74
79
  """规则为空 → DEFER(无规则不能假装合规)。"""
75
80
  forbidden = [r for r in (rules.get("forbidden") or []) if r]
@@ -96,12 +101,14 @@ def _rule_check(text, rules):
96
101
 
97
102
  # ---------- 内建验证器 ----------
98
103
 
104
+ # 生效条件:以 payload['content'](为假值则回落 payload['text'],再为假值取空串)作为文本,用 ctx['rules'](为假值则回落 load_rulebook())做规则检查,返回 _verdict(检查状态, 'text', 证据)。
99
105
  def _verify_text(payload, ctx):
100
106
  text = str(payload.get("content") or payload.get("text") or "")
101
107
  state, ev = _rule_check(text, ctx.get("rules") or load_rulebook())
102
108
  return _verdict(state, "text", ev)
103
109
 
104
110
 
111
+ # 生效条件:ctx['principal'] 缺失或为 None 时恒 DEFER;否则按 payload['action'](为假值取空串)是否属于 admin/forget/restore/review_decide 分别取 p.can_admin 或 p.can_write,且 p 具 allows 方法而 payload['sensitivity'] 为真值时再叠加 p.allows(sensitivity),按最终 ok 返回 ACCEPT/REJECT。
105
112
  def _verify_permission(payload, ctx):
106
113
  p = ctx.get("principal")
107
114
  if p is None:
@@ -119,6 +126,7 @@ def _verify_permission(payload, ctx):
119
126
  f"can_admin={getattr(p, 'can_admin', None)}")
120
127
 
121
128
 
129
+ # 生效条件:payload['content'](为假值取空串)经 ctx['rules'](为假值回落 load_rulebook())检查后,state==REJECT 即 REJECT;否则 ctx['cg'] 非 None 且 payload['topic'](为假值回落 payload['query'],再为假值取空串)非空且 cg.search 结果含 ACCEPT 状态节点时 DEFER(查询抛异常则跳过该路);再 state==DEFER 时 DEFER,否则 ACCEPT。
122
130
  def _verify_work_wip(payload, ctx):
123
131
  text = str(payload.get("content") or "")
124
132
  state, ev = _rule_check(text, ctx.get("rules") or load_rulebook())
@@ -140,6 +148,7 @@ def _verify_work_wip(payload, ctx):
140
148
  return _verdict(ACCEPT, "work_wip", f"纪律通过且未见重复成果({ev})")
141
149
 
142
150
 
151
+ # 生效条件:payload['content'](为假值取空串)为空白即 REJECT '空内容';ast.parse 抛 SyntaxError 即 REJECT 语法错误;payload['test_cmd'] 与 os.environ 的 MDCG_CODE_TEST_CMD 均为假值时 ACCEPT(仅静态验证);否则 shlex.split 抛 ValueError 或 subprocess 抛 OSError/SubprocessError 即 DEFER,returncode 为 0 即 ACCEPT,非 0 即 REJECT 并附 returncode 与 stdout/stderr 尾部。
143
152
  def _verify_code(payload, ctx):
144
153
  """代码内容 → 实测:AST 可解析为最低门槛;给了 test_cmd 则真跑测试。
145
154
 
@@ -184,12 +193,15 @@ def _verify_code(payload, ctx):
184
193
  return _verdict(REJECT, "code", f"实测失败 rc={p.returncode}:{tail}")
185
194
 
186
195
 
196
+ # 生效条件:调用 _pending 并传入 kind 与 why 后,其返回的内层闭包 _fn 对任意 payload/ctx 均求值为 _verdict(DEFER, kind, why),其中 kind 与 why 来自外层 _pending 的闭包,而非 _fn 的形参;。
187
197
  def _pending(kind, why):
198
+ # 生效条件:调用 _pending 并传入 kind 与 why 后,其返回的内层闭包 _fn 对任意 payload/ctx 均求值为 _verdict(DEFER, kind, why),其中 kind 与 why 来自外层 _pending 的闭包,而非 _fn 的形参;。
188
199
  def _fn(payload, ctx):
189
200
  return _verdict(DEFER, kind, why)
190
201
  return _fn
191
202
 
192
203
 
204
+ # 生效条件:payload['node_id'] 去空后为空即 DEFER;否则取 payload['unit_verdict'](为假值回落 payload['verdict'])去空转大写,其属于 STATES 时——verifier 为空即 DEFER、ctx['compiled_by'](为假值回落 payload['compiled_by'])非空且等于 verifier 即 REJECT E041、verdict==ACCEPT 而 evidence 为空即 DEFER、其余按该 verdict 裁定;其不属于 STATES 时经 units.probe 探测(探测抛异常亦恒 DEFER)后恒 DEFER 并给出通道与下一步提示。
193
205
  def _verify_ccg_marks(payload, ctx):
194
206
  """CCG 六要素候选的验证闸门:**只认认知图外的复核裁决**(裁定 A)。
195
207
 
@@ -259,6 +271,7 @@ def _verify_ccg_marks(payload, ctx):
259
271
 
260
272
  # ---------- 分派入口 ----------
261
273
 
274
+ # 生效条件:content_kind 去空后不在 CONTENT_KINDS 即 BLINDSPOT;否则取 VERIFIERS 中该 kind 的验证器,缺失即 DEFER,调用抛异常即 DEFER,成功则以返回值 state(不在 STATES 时降级为 DEFER)连同 evidence/detail 构造裁定。
262
275
  def audit(content_kind, payload=None, ctx=None):
263
276
  """按内容类型分派验证器。未知类型 → BLINDSPOT;缺验证器 → DEFER。"""
264
277
  kind = (content_kind or "").strip()
@@ -276,6 +289,7 @@ def audit(content_kind, payload=None, ctx=None):
276
289
  return _verdict(state, kind, str(v.get("evidence") or ""), v.get("detail"))
277
290
 
278
291
 
292
+ # 生效条件:对模块级常量 CONTENT_KINDS 中的每个 k 返回 action=CONTENT_KINDS[k]、basis=KIND_BASIS[k]、verifier 为 'builtin'(k 在 VERIFIERS 中)否则 'missing'。
279
293
  def kinds():
280
294
  """内容类型清单 + 验证器可用性(供 service_info / health 自描述)。"""
281
295
  return {k: {"action": CONTENT_KINDS[k], "basis": KIND_BASIS[k],
@@ -296,6 +310,7 @@ def kinds():
296
310
  VERIFIER_MODULES_ENV = "MDCG_VERIFIER_MODULES"
297
311
 
298
312
 
313
+ # 生效条件:modules 为 None 时按 os.environ 的 VERIFIER_MODULES_ENV(为假值取空串)取逗号分隔模块名,去空后列表为空即返回空报告;否则逐个 import_module 并调用其可调用的 register(本模块),单个导入或调用失败记入 failed 且 strict 为真值时立即抛出、为假值时继续,成功者记入 loaded,最后以 CONTENT_KINDS 生成 verifiers 可用性映射。
299
314
  def load_external_verifiers(modules=None, strict=False):
300
315
  """按 `MDCG_VERIFIER_MODULES`(逗号分隔 import 路径)加载外部验证器模块。
301
316
 
@@ -351,4 +366,4 @@ register_verifier("work_done", _pending("work_done", "未注入验收器(需
351
366
  # ccg_marks:闸门本身不是「能力」而是**准入判据**——只认编外单元的裁决
352
367
  # (unit_verdict + verifier);无裁决时探测复核通道(蜂巢→配置→子代理)并给出
353
368
  # 下一步,恒不假装通过。真实复核由外部单元完成,认知图只负责记账。
354
- register_verifier("ccg_marks", _verify_ccg_marks)
369
+ register_verifier("ccg_marks", _verify_ccg_marks)