@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.
- package/README.md +54 -26
- package/codebuddy/CODEBUDDY.md +196 -195
- package/codebuddy/README.md +13 -1
- package/codebuddy/mcp.json +9 -0
- 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
- package/docs/README.md +1 -1
- package/docs/discipline/harnesses.yaml +18 -7
- package/docs/discipline/templates/full.md.tmpl +4 -3
- package/docs/experiments/linkref_backfill/candidates_20260917.json +726 -0
- package/docs/experiments/linkref_backfill/candidates_internal_20260917.json +602 -0
- package/docs/experiments/linkref_backfill/candidates_internal_v2.json +603 -0
- package/docs/experiments/linkref_backfill/candidates_secret_20260917.json +884 -0
- package/docs/experiments/linkref_backfill/candidates_secret_v2.json +789 -0
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- package/docs/mdcg/README/350/257/246/347/273/206/347/211/210_v0.4.5.md +631 -625
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- 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
- package/dsh/README.md +33 -0
- package/dsh/cordis.yml.example +13 -7
- package/dsh/hive-mcp-probe.mjs +94 -0
- package/dsh/hive-mcp.example.yml +62 -0
- package/dsh/update-lingshu.bat +11 -0
- package/dsh/update-lingshu.ps1 +337 -0
- package/lib/hooks.d.ts +3 -0
- package/lib/hooks.js +17 -23
- package/lib/index.d.ts +4 -2
- package/lib/index.js +29 -6
- package/lib/lib/datapath.d.ts +76 -1
- package/lib/lib/datapath.js +199 -13
- package/lib/lib/mdcg_client.d.ts +6 -3
- package/lib/lib/mdcg_client.js +6 -5
- package/lib/lib/mutual.js +4 -4
- package/lib/lib/token_store.js +4 -5
- package/md_cg/audit.py +17 -2
- package/md_cg/autonomy.py +86 -15
- package/md_cg/backfill.py +36 -1
- package/md_cg/backfill_bigdomain.py +34 -0
- package/md_cg/bench6_arms.py +28 -1
- package/md_cg/bench6_common.py +10 -1
- package/md_cg/bench6_competitors.py +6 -1
- package/md_cg/bench_axis_domain.py +9 -1
- package/md_cg/bench_blind_comp.py +7 -1
- package/md_cg/bench_en_atoms_public.py +9 -0
- package/md_cg/bench_governance.py +348 -0
- package/md_cg/bench_lme_zh.py +16 -1
- package/md_cg/bench_locomo.py +2 -1
- package/md_cg/bench_locomo_zh.py +16 -1
- package/md_cg/bench_locomo_zh_public.py +4 -1
- package/md_cg/bench_longmem.py +2 -1
- package/md_cg/bench_membench.py +27 -1
- package/md_cg/bench_p0.py +4 -1
- package/md_cg/bench_progressive.py +13 -1
- package/md_cg/bench_role_views.py +238 -0
- package/md_cg/bench_task_ab.py +8 -1
- package/md_cg/bench_task_ab_llm.py +13 -1
- package/md_cg/bench_unified_en.py +6 -1
- package/md_cg/bench_zh_mad.py +20 -1
- package/md_cg/blindspot_tickets.py +123 -0
- package/md_cg/branches.py +12 -1
- package/md_cg/build_postings.py +73 -0
- package/md_cg/ccgc.py +67 -2
- package/md_cg/census.py +5 -1
- package/md_cg/chain.py +24 -3
- package/md_cg/codeindex.py +134 -17
- package/md_cg/coldverify.py +265 -0
- package/md_cg/comment_gate.py +338 -0
- package/md_cg/cond_compose.py +190 -0
- package/md_cg/cond_facts.py +155 -0
- package/md_cg/cond_template.json +107 -0
- package/md_cg/condition_anchor.py +143 -0
- package/md_cg/conformance.py +69 -4
- package/md_cg/consistency.py +24 -1
- package/md_cg/consolidate.py +53 -2
- package/md_cg/corpus.py +4 -0
- package/md_cg/crosscheck.py +42 -2
- package/md_cg/crypto.py +35 -1
- package/md_cg/d_meta.py +310 -0
- package/md_cg/datapath.py +201 -26
- package/md_cg/docindex.py +122 -1
- package/md_cg/eval_common.py +29 -1
- package/md_cg/evidence.py +27 -1
- package/md_cg/evolution.py +21 -1
- package/md_cg/export.py +11 -1
- package/md_cg/forgetting.py +23 -1
- package/md_cg/fsutil.py +18 -1
- package/md_cg/hotcache.py +214 -0
- package/md_cg/hyperedge.py +251 -0
- package/md_cg/identity.py +18 -1
- package/md_cg/insight.py +17 -1
- package/md_cg/lexicon/build_cedict_en_zh.py +9 -0
- package/md_cg/lexicon/build_standard_en.py +171 -168
- package/md_cg/lexicon/expand_en_zh.py +6 -0
- package/md_cg/lifecycle.py +12 -1
- package/md_cg/linkref.py +281 -0
- package/md_cg/links.py +29 -1
- package/md_cg/mcp_server.py +362 -43
- package/md_cg/md_whitebox.py +53 -1
- package/md_cg/mdcg.py +1003 -27
- package/md_cg/mdcos.py +558 -36
- package/md_cg/metacognition.py +37 -2
- package/md_cg/migrate.py +4 -0
- package/md_cg/migrate_aeis.py +221 -213
- package/md_cg/migrate_roleplay.py +8 -0
- package/md_cg/migrate_wisdom_graph.py +14 -1
- package/md_cg/mreview/__main__.py +3 -0
- package/md_cg/mreview/bundle.py +8 -0
- package/md_cg/mreview/candidates.py +9 -0
- package/md_cg/mreview/govern.py +21 -1
- package/md_cg/mreview/locate.py +34 -0
- package/md_cg/mreview/pipeline.py +29 -1
- package/md_cg/mreview/ruleset.py +16 -1
- package/md_cg/nodefile.py +233 -3
- package/md_cg/pooling.py +23 -1
- package/md_cg/postings.py +298 -0
- package/md_cg/predict.py +89 -9
- package/md_cg/progressive.py +3 -0
- package/md_cg/protect.py +14 -1
- package/md_cg/protocol.py +372 -0
- package/md_cg/provenance.py +262 -0
- package/md_cg/reach.py +453 -0
- package/md_cg/refindex.py +47 -2
- package/md_cg/refine.py +20 -1
- package/md_cg/roleviews.py +89 -0
- package/md_cg/routing.py +76 -0
- package/md_cg/scrub.py +63 -2
- package/md_cg/security.py +26 -1
- package/md_cg/self_state.py +64 -1
- package/md_cg/selfreport.py +151 -0
- package/md_cg/semantic/canonical.py +5 -0
- package/md_cg/semantic/en_normalizer.py +364 -355
- package/md_cg/semantic/zh_en_atoms.py +139 -136
- package/md_cg/signer.py +41 -1
- package/md_cg/sources.py +583 -547
- package/md_cg/statushdr.py +179 -0
- package/md_cg/stg.py +59 -18
- package/md_cg/subgraph.py +23 -0
- package/md_cg/sustain.py +56 -1
- package/md_cg/tasks.py +26 -2
- package/md_cg/test_autonomy.py +26 -0
- package/md_cg/test_bench_governance.py +102 -0
- package/md_cg/test_blindspot_tickets.py +166 -0
- package/md_cg/test_ccgc.py +10 -0
- package/md_cg/test_codeindex.py +338 -0
- package/md_cg/test_comment_gate.py +187 -0
- package/md_cg/test_cond_compose_anchors.py +76 -0
- package/md_cg/test_condition_anchor.py +82 -0
- package/md_cg/test_d_meta.py +412 -0
- package/md_cg/test_datapath_root.py +188 -0
- package/md_cg/test_gain_gate.py +47 -1
- package/md_cg/test_hot_cold.py +187 -0
- package/md_cg/test_hyperedge.py +245 -0
- package/md_cg/test_linkref.py +306 -0
- package/md_cg/test_md_access_parity.py +15 -3
- package/md_cg/test_mr_m1.py +108 -18
- package/md_cg/test_mr_m3.py +8 -1
- package/md_cg/test_p26_refindex.py +49 -20
- package/md_cg/test_p27_docindex.py +236 -2
- package/md_cg/test_p2_mcp.py +1 -1
- package/md_cg/test_p31_insight.py +24 -0
- package/md_cg/test_p44_md_whitebox.py +14 -1
- package/md_cg/test_protocol.py +243 -0
- package/md_cg/test_reach.py +378 -0
- package/md_cg/test_reach_keys.py +201 -0
- package/md_cg/test_reach_meta_exits.py +145 -0
- package/md_cg/test_read_clip.py +8 -4
- package/md_cg/test_retr_s1.py +340 -0
- package/md_cg/test_retr_s1b.py +209 -0
- package/md_cg/test_retr_s3.py +194 -0
- package/md_cg/test_retr_s4.py +163 -0
- package/md_cg/test_retr_s5.py +200 -0
- package/md_cg/test_retr_s6.py +157 -0
- package/md_cg/test_retr_s7.py +385 -0
- package/md_cg/test_retr_s8_time.py +316 -0
- package/md_cg/test_retr_s9_edges.py +286 -0
- package/md_cg/test_retr_s9_entity_ctx.py +175 -0
- package/md_cg/test_review_conformance.py +59 -2
- package/md_cg/test_role_views.py +354 -0
- package/md_cg/test_trust.py +361 -0
- package/md_cg/test_units_poll.py +71 -0
- package/md_cg/test_v14_fixes.py +397 -0
- package/md_cg/test_validity_filter.py +280 -0
- package/md_cg/test_wisdom_md_store.py +7 -3
- package/md_cg/test_writepipe.py +5 -1
- package/md_cg/theory.py +16 -1
- package/md_cg/tokens.py +40 -8
- package/md_cg/tool_face.py +13 -2
- package/md_cg/trust.py +943 -0
- package/md_cg/twophase.py +12 -1
- package/md_cg/units.py +129 -10
- package/md_cg/vision_evidence.py +24 -1
- package/md_cg/weights.py +24 -1
- package/md_cg/whitebox.py +32 -1
- package/md_cg/whitebox_kb/data/verify_cache.json +21210 -365
- package/md_cg/whitebox_kb/data/verify_savings.jsonl +5078 -0
- package/md_cg/whitebox_kb/wisdom/audit_log/chain_heat.json +10 -10
- package/md_cg/whitebox_kb/wisdom/code_compose.py +113 -6
- package/md_cg/whitebox_kb/wisdom/code_solidified.json +1 -1
- package/md_cg/whitebox_kb/wisdom/verifier.py +340 -55
- package/md_cg/whitebox_kb/wisdom/wisdom-book-cloud.db +0 -0
- package/md_cg/writelimit.py +18 -4
- package/md_cg/writepipe.py +178 -7
- package/package.json +2 -2
- package/skills/skills/designer-perspective/scripts/__pycache__/designer.cpython-310.pyc +0 -0
- package/skills/skills/designer-perspective/scripts/designer.py +17 -1
- package/skills/skills/designer-perspective/tests/selftest.py +3 -1
- package/src/hooks.ts +17 -21
- package/src/index.ts +33 -6
- package/src/lib/datapath.ts +211 -13
- package/src/lib/mdcg_client.ts +12 -8
- package/src/lib/mutual.ts +411 -411
- package/src/lib/token_store.ts +4 -5
- package/zcode/AGENTS.md +196 -195
- package/zcode/README.md +4 -0
- package/md_cg/whitebox_kb/wisdom/wisdom-book-cloud.db-shm +0 -0
- package/md_cg/whitebox_kb/wisdom/wisdom-book-cloud.db-wal +0 -0
package/md_cg/ccgc.py
CHANGED
|
@@ -60,6 +60,9 @@ E_CODES = {
|
|
|
60
60
|
"E041": "自证拒绝:验证方标识 == 编译执行者(LLM 不得自己验证自己)",
|
|
61
61
|
"E042": "验证未通过:签章判定 REJECT / BLINDSPOT",
|
|
62
62
|
"E043": "修正非法:只允许修正 condition_space 四槽",
|
|
63
|
+
"E050": ("依赖声明缺失:正文以 `@<节点 id>` 声明了跨节点依赖(「# 子功能:」行),"
|
|
64
|
+
"但 depends_on 未给出可解析目标——依赖必须是可解析的字段,不能只是散文"),
|
|
65
|
+
"E051": "依赖目标不存在:depends_on 指向的节点不在库中(悬空依赖)",
|
|
63
66
|
}
|
|
64
67
|
|
|
65
68
|
# ---- 候选来源标识(写进留痕,可溯源到「谁说的」) ----
|
|
@@ -91,13 +94,16 @@ class CompileResult:
|
|
|
91
94
|
verdict: Optional[Dict] = None # 验证单元终裁
|
|
92
95
|
|
|
93
96
|
@property
|
|
97
|
+
# 生效条件:BLINDSPOT:缺证据(无 required 形参与模块级常量,仅有未给出的 self.errors 来源)。
|
|
94
98
|
def has_errors(self) -> bool:
|
|
95
99
|
return len(self.errors) > 0
|
|
96
100
|
|
|
101
|
+
# 生效条件:对任意 code,msg 取 E_CODES.get(code, code)(缺键回落 code 本身),向 self.errors 追加 "code 空格 msg",extra 为真值时再追加 "(extra)"。
|
|
97
102
|
def err(self, code: str, extra: str = "") -> None:
|
|
98
103
|
msg = E_CODES.get(code, code)
|
|
99
104
|
self.errors.append(code + " " + msg + (("(" + extra + ")") if extra else ""))
|
|
100
105
|
|
|
106
|
+
# 生效条件:无入参,首行按 self.success 输出「编译成功」或「编译失败(len(self.errors) 个错误)」,要素行取 nodefile.CCG_MARKS 与 self.lines 的交集,errors 列前 10 条、warnings 列前 5 条,返回 "\n".join(out)。
|
|
101
107
|
def summary(self) -> str:
|
|
102
108
|
out = ["编译成功" if self.success else
|
|
103
109
|
"编译失败(%d 个错误)" % len(self.errors)]
|
|
@@ -140,14 +146,17 @@ class LinkResult:
|
|
|
140
146
|
# 工具(就地实现,避免跨模块 import 环——与 mdcg.py 复制 _ccg_line 的既有做法一致)
|
|
141
147
|
# =============================================================================
|
|
142
148
|
|
|
149
|
+
# 生效条件:对任意 s(含 None/空串,源码以 `(s or "")` 兜底为空串),返回 hashlib.sha1(该串 utf-8 编码).hexdigest()[:12]。
|
|
143
150
|
def _sha(s: str) -> str:
|
|
144
151
|
return hashlib.sha1((s or "").encode("utf-8")).hexdigest()[:12]
|
|
145
152
|
|
|
146
153
|
|
|
154
|
+
# 生效条件:对任意 cg,返回 os.path.join(cg.root, LOG_NAME)(即 cg.root 与模块级常量 LOG_NAME 的拼接)。
|
|
147
155
|
def _log_path(cg) -> str:
|
|
148
156
|
return os.path.join(cg.root, LOG_NAME)
|
|
149
157
|
|
|
150
158
|
|
|
159
|
+
# 生效条件:x 为 None 或非 str 时原样返回 x;x 为 str 时返回 MdCGOS(x) 构造出的 cg 实例。
|
|
151
160
|
def _as_cg(x):
|
|
152
161
|
"""接受 root 路径或已构造的 cg 实例——保持密级隔离与密钥上下文。"""
|
|
153
162
|
if x is None or not isinstance(x, str):
|
|
@@ -156,11 +165,13 @@ def _as_cg(x):
|
|
|
156
165
|
return MdCGOS(x)
|
|
157
166
|
|
|
158
167
|
|
|
168
|
+
# 生效条件:content 为字符串(None 视作空串)时,若其中含 "# " + field_name + ":" 或 "# " + field_name + ":" 则返回 True,否则 False。
|
|
159
169
|
def _has_ccg_line(content: str, field_name: str) -> bool:
|
|
160
170
|
text = content or ""
|
|
161
171
|
return ("# " + field_name + ":") in text or ("# " + field_name + ":") in text
|
|
162
172
|
|
|
163
173
|
|
|
174
|
+
# 生效条件:在 `(content or "").split("\n")` 中命中首个 strip 后以 "#" 开头、含 field_name、且去 "#" 后按全角或半角冒号切出的名字等于 field_name 的行→替换为 "# field_name:value" 并返回;否则若有行 strip 后以 "# 功能名" 开头→在该行后插入新行并返回;否则返回 `"# field_name:value\n" + (content or "")`。
|
|
164
175
|
def _upsert_ccg_line(content: str, field_name: str, value: str) -> str:
|
|
165
176
|
"""写入/替换 `# <字段>:<值>`,优先插在「# 功能名」之后。
|
|
166
177
|
|
|
@@ -183,6 +194,7 @@ def _upsert_ccg_line(content: str, field_name: str, value: str) -> str:
|
|
|
183
194
|
return newline + "\n" + (content or "")
|
|
184
195
|
|
|
185
196
|
|
|
197
|
+
# 生效条件:对任意 path 与 rec,以追加模式写入 json.dumps(rec, ensure_ascii=False) + "\n",仅 OSError 被吞掉且无返回值。
|
|
186
198
|
def _append_jsonl(path: str, rec: dict) -> None:
|
|
187
199
|
try:
|
|
188
200
|
with open(path, "a", encoding="utf-8") as f:
|
|
@@ -191,6 +203,7 @@ def _append_jsonl(path: str, rec: dict) -> None:
|
|
|
191
203
|
pass
|
|
192
204
|
|
|
193
205
|
|
|
206
|
+
# 生效条件:path 为假值或 os.path.exists(path) 为假→返回 [];否则逐行 strip、跳过空行、json.loads 成功者追加、单行 json.loads 抛 ValueError 者跳过,中途 open/读取抛 OSError 时返回 [];全部读完返回 out。
|
|
194
207
|
def _read_jsonl(path: str) -> List[dict]:
|
|
195
208
|
out: List[dict] = []
|
|
196
209
|
if not path or not os.path.exists(path):
|
|
@@ -218,6 +231,7 @@ def _read_jsonl(path: str) -> List[dict]:
|
|
|
218
231
|
_FRAG_SPLIT = re.compile(r"[;;、,,]+")
|
|
219
232
|
|
|
220
233
|
|
|
234
|
+
# 生效条件:value 经 `str(value).strip()`(None 记 "")后为空→False;否则按 _FRAG_SPLIT 切分并去掉空分片,若分片列表为空→False;否则返回全部分片都在 dialog 中的 all 判定结果。
|
|
221
235
|
def _value_grounded(dialog: str, value) -> bool:
|
|
222
236
|
"""值是否 grounded:按分片切分后,每个分片都是对话记录的字面子串。
|
|
223
237
|
|
|
@@ -233,6 +247,7 @@ def _value_grounded(dialog: str, value) -> bool:
|
|
|
233
247
|
return all(f in dialog for f in frags)
|
|
234
248
|
|
|
235
249
|
|
|
250
|
+
# 生效条件:span 经 `str(span).strip()`(None 记 "")后须非空且作为连续子串出现在 dialog 中,否则返回 False。
|
|
236
251
|
def _span_grounded(dialog: str, span) -> bool:
|
|
237
252
|
"""引用 span 是否 grounded:必须是对话记录的**连续**字面子串。"""
|
|
238
253
|
s = "" if span is None else str(span).strip()
|
|
@@ -264,10 +279,12 @@ _SENT_SPLIT = re.compile(r"[。!?!?;;\n]+")
|
|
|
264
279
|
_TS_PAT = re.compile(r"(20\d{2})[-/年](\d{1,2})[-/月](\d{1,2})")
|
|
265
280
|
|
|
266
281
|
|
|
282
|
+
# 生效条件:dialog 为假值时按 "" 处理,按模块级 _SENT_SPLIT 切分后 strip 并过滤空串,返回句子列表。
|
|
267
283
|
def _sentences(dialog: str) -> List[str]:
|
|
268
284
|
return [s.strip() for s in _SENT_SPLIT.split(dialog or "") if s.strip()]
|
|
269
285
|
|
|
270
286
|
|
|
287
|
+
# 生效条件:对 sents 逐句判 `any(c in s for c in cues)`,命中即 append,命中后 `len(out) >= limit` 即 break——limit 为 0 或负数时首个命中项仍被 append 后立刻 break(多返 1 项);cues 为空容器时 any 恒假、返回空列表。
|
|
271
288
|
def _pick_by_cues(sents: List[str], cues, limit: int = 1) -> List[str]:
|
|
272
289
|
"""按线索词命中挑句(命中即整句作为候选——整句天然是原文子串,不会捏造)。"""
|
|
273
290
|
out: List[str] = []
|
|
@@ -279,6 +296,7 @@ def _pick_by_cues(sents: List[str], cues, limit: int = 1) -> List[str]:
|
|
|
279
296
|
return out
|
|
280
297
|
|
|
281
298
|
|
|
299
|
+
# 生效条件:类无 __init__ 形参,实例化即成立,类属性 name 恒为模块级常量 SRC_RULE,候选能力经 candidates(dialog, ctx=None) 以 dialog 为必需入参调用;
|
|
282
300
|
class RuleParser:
|
|
283
301
|
"""内生规则解析器:**诚实下界能力**,不是主路径。
|
|
284
302
|
|
|
@@ -290,6 +308,7 @@ class RuleParser:
|
|
|
290
308
|
|
|
291
309
|
name = SRC_RULE
|
|
292
310
|
|
|
311
|
+
# 生效条件:sents(来自 _sentences(dialog))非空时「功能名」取首句(超 30 字符截前 30);pos/tool/cons 仅在 _pick_by_cues 用 _CUES 对应线索命中时写入对应槽;time_window 先按 _TS_PAT 在 dialog 中匹配,命中且 mktime 未抛 ValueError/OverflowError/OSError 才填 [当日0点, +86399],否则回落 [nodefile.FULL_TIME_WINDOW_MIN, nodefile.FULL_TIME_WINDOW_MAX] 并标 synthetic="full_time_window";「不适用条件/执行/子功能」按 _NEG_CUES/_EXEC_CUES/_SUB_CUES 命中首句写入;「验证方式」取 _BASIS_CUES 中首个出现在 dialog 中的线索。
|
|
293
312
|
def candidates(self, dialog: str, ctx: Optional[dict] = None) -> Dict[str, Any]:
|
|
294
313
|
sents = _sentences(dialog)
|
|
295
314
|
out: Dict[str, Any] = {"marks": {}, "slots": {}}
|
|
@@ -337,6 +356,7 @@ class RuleParser:
|
|
|
337
356
|
return out
|
|
338
357
|
|
|
339
358
|
|
|
359
|
+
# 生效条件:对给定 parser、dialog、ctx,getattr(parser,"candidates",parser) 得到的 fn 被调用为 fn(dialog, ctx or {}),若该调用抛 Exception 或返回非 dict 则返回 {"marks": {}, "slots": {}},否则返回该 dict 并 setdefault("marks",{}) 与 setdefault("slots",{}) 后的结果。
|
|
340
360
|
def _invoke_parser(parser, dialog: str, ctx: Optional[dict]) -> Dict[str, Any]:
|
|
341
361
|
"""调用外部/内生解析器,统一形态;异常不视作通过(返回空候选)。"""
|
|
342
362
|
fn = getattr(parser, "candidates", parser)
|
|
@@ -355,6 +375,7 @@ def _invoke_parser(parser, dialog: str, ctx: Optional[dict]) -> Dict[str, Any]:
|
|
|
355
375
|
# 核心:compile_dialog(对话记录 → 六要素候选;只编译,不入库)
|
|
356
376
|
# =============================================================================
|
|
357
377
|
|
|
378
|
+
# 生效条件:dialog 空记 E001、node_id 空记 E002、actor 空记 E003,且 cg 转换后非 None 而 node_id 非空时该节点不存在再记 E002,存在任一 errors 即以 verdict{passed:False, reason:errors[0], authority:VERIFICATION_UNIT} 提前返回 res;否则按「marks/slots 任一为非空 dict → SRC_EXPLICIT;二者皆空且 parser 为 None → SRC_RULE(附下界告警);二者皆空且 parser 非 None → SRC_PARSER」收集候选,四槽逐个校验(key 不在 cand_slots 即跳过;time_window 非长度 2 的 list/tuple 记 E021;其余槽空值/占位记 E021;仅当非「src_kind==SRC_EXPLICIT 且 strict_spans=False」豁免时,值非 dialog 子串记 E011、span 非空而定位失败记 E010),clean_slots 缺必需槽或 condition_space_text 为空记 E020;五要素按同样规则过滤后入 res.lines(生效条件行只由四槽 env_text 合成),验证基底按「候选 basis → 验证方式命中 _BASIS_CUES → 默认 other 并附告警」取值、不在 nodefile.VERIFICATION_BASIS 中记 E030;最终 res.success 与 verdict.passed 同取「无 errors」,verdict 恒带 requires_attestation=True,通过时再按 nodefile.CCG_REQUIRED 缺失项补 warning;
|
|
358
379
|
def compile_dialog(dialog: str, node_id: str, actor: str, *,
|
|
359
380
|
marks: Optional[Dict[str, Any]] = None,
|
|
360
381
|
slots: Optional[Dict[str, Any]] = None,
|
|
@@ -429,6 +450,7 @@ def compile_dialog(dialog: str, node_id: str, actor: str, *,
|
|
|
429
450
|
res.warnings.append(
|
|
430
451
|
"strict_spans=False:显式入参豁免名实门,含未经对话原文支撑的声明(已留痕)。")
|
|
431
452
|
|
|
453
|
+
# 生效条件:item 为 dict 时返回 (item.get("value"), item.get("span"), item.get("basis"), item.get("synthetic"))(各键缺失即回落 None);item 非 dict 时返回 (item, None, None, None)。
|
|
432
454
|
def _field(item):
|
|
433
455
|
"""候选项 → (value, span, basis, synthetic);容忍裸值与 dict 两种形态。"""
|
|
434
456
|
if isinstance(item, dict):
|
|
@@ -550,6 +572,7 @@ def compile_dialog(dialog: str, node_id: str, actor: str, *,
|
|
|
550
572
|
# attest:认知图**外**的验证方签章(裁定 A 的落点)
|
|
551
573
|
# =============================================================================
|
|
552
574
|
|
|
575
|
+
# 生效条件:依次判 strip 后的 node_id 为空→E002;verifier 为空→E003;verifier==compiled_by→E041;state(verdict.strip())不在模块级 STATES→非法裁决;state 非 ACCEPT→E042;全部通过才 res.ok=True;随后恒算 token=_sha(...),仅当 ledger 为真且 _as_cg(cg) 非 None 时追加一条 ccgc_attest 审计 jsonl。
|
|
553
576
|
def attest(node_id: str, verdict: str, verifier: str, compiled_by: str, *,
|
|
554
577
|
slot_corrections: Optional[Dict[str, Any]] = None,
|
|
555
578
|
evidence: str = "", cg: Any = None, ledger: bool = True) -> AttestResult:
|
|
@@ -596,6 +619,37 @@ def attest(node_id: str, verdict: str, verifier: str, compiled_by: str, *,
|
|
|
596
619
|
# link:签章通过才写入(缺签章恒不写入)
|
|
597
620
|
# =============================================================================
|
|
598
621
|
|
|
622
|
+
# 生效条件:依次判 compiled.success 为假→返回带错误;attestation 为 None→E040;attestation.node_id 不等于 compiled.node_id 的取值→目标不一致拒绝;attestation.verifier 为真值且 == `(actor or compiled.actor)`→E041;attestation.ok 为假→E042;_as_cg(cg) 为 None→E002;节点不在 cg.index 的 nodes 中→E002;apply 为假→ok=True 的 dry-run 返回;否则 apply 为真时写入(fm 为 None 或 content 加密→E004),basis 为假值则回落 compiled.sources.get("verification_basis") 或 "other",成功后 out.written=len(compiled.lines)。
|
|
623
|
+
def _check_deps(_cg, node_id: str) -> List[str]:
|
|
624
|
+
"""依赖声明硬闸门 → 错误列表(E050 / E051)。**读面失败不误杀**(返回空即放行)。
|
|
625
|
+
|
|
626
|
+
契约口径:CCG「子功能」行的契约角色是**依赖 dependency**(nodefile 术语真源),
|
|
627
|
+
但该槽同时承载**自述子功能**(描述本单元内部构成)——判据以 `@<节点 id>` 显式
|
|
628
|
+
引用为界(`nodefile.declares_dependency`,收窄裁定 b):**显式声称依赖**才要求在
|
|
629
|
+
`depends_on` 给出可解析目标——否则「依赖」只剩散文,被依赖单元一旦变动,下游
|
|
630
|
+
无处可传(失效传播从源头断链)。自然语言自述不算声明(依赖不是必填元数据)。
|
|
631
|
+
二者齐全时目标必须真实存在:悬空依赖 = 声称依赖一个并不存在的地基。
|
|
632
|
+
"""
|
|
633
|
+
errs: List[str] = []
|
|
634
|
+
try:
|
|
635
|
+
node = _cg.get(node_id)
|
|
636
|
+
except Exception: # noqa: BLE001 —— 读面异常按「无声明」放行
|
|
637
|
+
return errs
|
|
638
|
+
if not node:
|
|
639
|
+
return errs
|
|
640
|
+
from . import trust as _trust
|
|
641
|
+
fm = node.get("frontmatter") or {}
|
|
642
|
+
deps = _trust.as_deps(fm.get(nodefile.DEPENDS_ON_FIELD))
|
|
643
|
+
if nodefile.declares_dependency(node.get("content") or "") and not deps:
|
|
644
|
+
errs.append("E050 " + E_CODES["E050"])
|
|
645
|
+
known = set((getattr(_cg, "index", None) or {}).get("nodes") or {})
|
|
646
|
+
missing = [d for d in deps if d not in known]
|
|
647
|
+
if missing:
|
|
648
|
+
errs.append("E051 " + E_CODES["E051"] + ":" + ",".join(missing[:5]))
|
|
649
|
+
return errs
|
|
650
|
+
|
|
651
|
+
|
|
652
|
+
# 生效条件:依次判 compiled.success 为假→返回带错误;attestation 为 None→E040;attestation.node_id 不等于 compiled.node_id 的取值→目标不一致拒绝;attestation.verifier 为真值且 == `(actor or compiled.actor)`→E041;attestation.ok 为假→E042;_as_cg(cg) 为 None→E002;节点不在 cg.index 的 nodes 中→E002;依赖声明闸门(E050/E051)不通过→拒绝写入;apply 为假→ok=True 的 dry-run 返回;否则 apply 为真时写入(fm 为 None 或 content 加密→E004),basis 为假值则回落 compiled.sources.get("verification_basis") 或 "other",成功后 out.written=len(compiled.lines)。
|
|
599
653
|
def link(compiled: CompileResult, attestation: Optional[AttestResult], *,
|
|
600
654
|
cg: Any = None, apply: bool = False, actor: str = "",
|
|
601
655
|
basis: str = "") -> LinkResult:
|
|
@@ -633,6 +687,10 @@ def link(compiled: CompileResult, attestation: Optional[AttestResult], *,
|
|
|
633
687
|
if not entry:
|
|
634
688
|
out.errors.append("E002 节点不存在:" + node_id)
|
|
635
689
|
return out
|
|
690
|
+
_dep_errs = _check_deps(_cg, node_id)
|
|
691
|
+
if _dep_errs:
|
|
692
|
+
out.errors.extend(_dep_errs)
|
|
693
|
+
return out
|
|
636
694
|
if not apply:
|
|
637
695
|
out.ok = True
|
|
638
696
|
out.warnings.append("dry-run(apply=False):未写入;签章 token=" + attestation.token)
|
|
@@ -686,6 +744,7 @@ def link(compiled: CompileResult, attestation: Optional[AttestResult], *,
|
|
|
686
744
|
# recalibrate:用后续验证修正生效条件(裁定 A 的闭环)
|
|
687
745
|
# =============================================================================
|
|
688
746
|
|
|
747
|
+
# 生效条件:node_id 空→E002;verifier 为真值且==compiled_by→E041;corrections 为假值(空 dict/None)→E043;corrections 含不在模块级 nodefile.CONDITION_SLOTS 的键→E043;_as_cg(cg) 为 None→E002;节点不在 cg.index 的 nodes 中→E002;fm 为 None 或内容加密→E004;旧槽合并 corrections 后 condition_space_text 为空→E020;apply 为假→ok=True 的 dry-run 返回;否则写入并按 1 计 written。
|
|
689
748
|
def recalibrate(node_id: str, corrections: Dict[str, Any], verifier: str,
|
|
690
749
|
compiled_by: str, *, evidence: str = "", cg: Any = None,
|
|
691
750
|
apply: bool = False) -> LinkResult:
|
|
@@ -772,11 +831,13 @@ def recalibrate(node_id: str, corrections: Dict[str, Any], verifier: str,
|
|
|
772
831
|
PENDING_DIR = "_ccgc_pending"
|
|
773
832
|
|
|
774
833
|
|
|
834
|
+
# 生效条件:node_id 为 None 或空串时按 "" 处理,非 [0-9A-Za-z_.\-] 字符一律替换为 "-",取前 80 个字符;结果为空串时返回 "unnamed"。
|
|
775
835
|
def _safe_name(node_id: str) -> str:
|
|
776
836
|
"""node_id → 文件名安全形态(分支节点形如 mem_x@br1,须剥掉非 [A-Za-z0-9_.-])。"""
|
|
777
837
|
return re.sub(r"[^0-9A-Za-z_.\-]", "-", str(node_id or ""))[:80] or "unnamed"
|
|
778
838
|
|
|
779
839
|
|
|
840
|
+
# 生效条件:_as_cg(cg) 的 root 属性为假值→返回 "";否则返回 os.path.join(str(root), PENDING_DIR, _safe_name(node_id) + ".json")。
|
|
780
841
|
def pending_path(cg, node_id: str) -> str:
|
|
781
842
|
_cg = _as_cg(cg)
|
|
782
843
|
root = getattr(_cg, "root", None)
|
|
@@ -785,12 +846,14 @@ def pending_path(cg, node_id: str) -> str:
|
|
|
785
846
|
return os.path.join(str(root), PENDING_DIR, _safe_name(node_id) + ".json")
|
|
786
847
|
|
|
787
848
|
|
|
849
|
+
# 生效条件:lines、slots 为假值时按 {} 处理,返回 `_sha(json.dumps({"lines":..., "slots":...}, ensure_ascii=False, sort_keys=True))`。
|
|
788
850
|
def _payload_hash(lines, slots) -> str:
|
|
789
851
|
"""候选内容摘要(六行 + 四槽,键序无关)——签章与产物的绑定依据。"""
|
|
790
852
|
return _sha(json.dumps({"lines": lines or {}, "slots": slots or {}},
|
|
791
853
|
ensure_ascii=False, sort_keys=True))
|
|
792
854
|
|
|
793
855
|
|
|
856
|
+
# 生效条件:_as_cg(cg) 为 None→返回 {'ok': False, 'error': '未提供 cg'};pending_path 为空→返回 {'ok': False, 'error': '无法定位 pending 目录'};否则写 json(OSError 时返回 ok=False 且带 path 与异常名),成功返回 {'ok': True, 'path': p, 'hash': rec['hash']},其中 attest 按 `attestation is not None` 决定是否落盘。
|
|
794
857
|
def save_pending(cg, compiled: CompileResult,
|
|
795
858
|
attestation: Optional[AttestResult] = None) -> dict:
|
|
796
859
|
"""候选(+ 可选签章)落 pending;返回 {ok, path, hash}。"""
|
|
@@ -813,6 +876,7 @@ def save_pending(cg, compiled: CompileResult,
|
|
|
813
876
|
return {"ok": True, "path": p, "hash": rec["hash"]}
|
|
814
877
|
|
|
815
878
|
|
|
879
|
+
# 生效条件:_as_cg(cg) 为 None→error='未提供 cg';pending_path 为空或 os.path.isfile(p) 为假→error=未找到 pending;open/json.load 抛 OSError 或 ValueError→error=pending 不可读;否则按 dataclasses.fields 过滤重建 CompileResult 与(rec['attest'] 为真值时的)AttestResult,hash_ok = `rec.get("hash") == _payload_hash(c.lines, c.slots)`,ok=True。
|
|
816
880
|
def load_pending(cg, node_id: str) -> dict:
|
|
817
881
|
"""读回候选与签章,并校验 hash(内容被改即 hash_ok=False,调用方须拒绝写入)。"""
|
|
818
882
|
_cg = _as_cg(cg)
|
|
@@ -845,6 +909,7 @@ def load_pending(cg, node_id: str) -> dict:
|
|
|
845
909
|
return out
|
|
846
910
|
|
|
847
911
|
|
|
912
|
+
# 生效条件:pending_path(cg, node_id) 为空→返回 False;否则 os.remove 成功→True,抛 OSError→False。
|
|
848
913
|
def drop_pending(cg, node_id: str) -> bool:
|
|
849
914
|
"""删除 pending(link 成功后调用;失败静默——待办件不是事实,无需强保证)。"""
|
|
850
915
|
p = pending_path(cg, node_id)
|
|
@@ -857,6 +922,7 @@ def drop_pending(cg, node_id: str) -> bool:
|
|
|
857
922
|
return False
|
|
858
923
|
|
|
859
924
|
|
|
925
|
+
# 生效条件:load_pending(cg, node_id) 的 ok 为假→带其 error 返回 LinkResult;hash_ok 为假→以「hash 不符」错误返回;否则转调 link(got["compiled"], got.get("attest"), cg=cg, apply=apply, actor=actor, basis=basis) 并返回其结果,且仅当 res.ok 与 apply 同时为真时调用 drop_pending。
|
|
860
926
|
def link_pending(cg, node_id: str, *, apply: bool = False, actor: str = "",
|
|
861
927
|
basis: str = "") -> LinkResult:
|
|
862
928
|
"""从 pending 读回候选与签章后 link:hash 校验 → 签章准入 → 写入 → 清理 pending。"""
|
|
@@ -880,5 +946,4 @@ __all__ = ["ACCEPT", "REJECT", "DEFER", "BLINDSPOT", "STATES", "CONTRACT_ROLES",
|
|
|
880
946
|
"E_CODES", "CompileResult", "AttestResult", "LinkResult", "RuleParser",
|
|
881
947
|
"compile_dialog", "attest", "link", "recalibrate",
|
|
882
948
|
"PENDING_DIR", "pending_path", "save_pending", "load_pending",
|
|
883
|
-
"drop_pending", "link_pending"]
|
|
884
|
-
|
|
949
|
+
"drop_pending", "link_pending"]
|
package/md_cg/census.py
CHANGED
|
@@ -31,6 +31,7 @@ DEFAULT_ROOT = os.path.join(
|
|
|
31
31
|
ARCHIVE_DIRS = ("trash", "_protected_history", "_protected", "_index_log", "hippocampus")
|
|
32
32
|
|
|
33
33
|
|
|
34
|
+
# 生效条件:当 root 为记忆库根目录、其下 .md 能被 nodefile.loads 解析出 frontmatter 时,返回 [(node_id, layer, condition_space, tags)...];ARCHIVE_DIRS 与 _protected_history_ 目录整棵剪枝,无 frontmatter 的 md 跳过。
|
|
34
35
|
def load(root):
|
|
35
36
|
"""遍历 md 记忆库根,返回 [(node_id, layer, condition_space, tags), ...]。
|
|
36
37
|
|
|
@@ -62,6 +63,7 @@ def load(root):
|
|
|
62
63
|
return out
|
|
63
64
|
|
|
64
65
|
|
|
66
|
+
# 生效条件:rows 非空、每行可解包为 (_, _, d, t) 且 keyfn(d, t) 可调用时,打印桶数、最大桶占比、单例桶比与期望扫描占比,无返回值。
|
|
65
67
|
def report(name, rows, keyfn):
|
|
66
68
|
cnt = collections.Counter(keyfn(d, t) for _, _, d, t in rows)
|
|
67
69
|
n = len(rows)
|
|
@@ -78,12 +80,14 @@ def report(name, rows, keyfn):
|
|
|
78
80
|
print(f" ★条件路由后期望扫描占比 = {exp * 100:.1f}% (100%=退化成全量, 越低越有效)")
|
|
79
81
|
|
|
80
82
|
|
|
83
|
+
# 生效条件:传入 keys(任意可迭代键序列,含空序列时对 json.dumps({}) 求哈希)即返回一个接收 (d, t) 的 lambda,该 lambda 按 keys 逐个取 d.get(k)(缺键得 None)并 sort_keys=True、ensure_ascii=False 序列化后取 sha256 十六进制前 10 位,形参 t 不参与计算。
|
|
81
84
|
def by_keys(keys):
|
|
82
85
|
return lambda d, t: hashlib.sha256(
|
|
83
86
|
json.dumps({k: d.get(k) for k in keys}, sort_keys=True,
|
|
84
87
|
ensure_ascii=False).encode()).hexdigest()[:10]
|
|
85
88
|
|
|
86
89
|
|
|
90
|
+
# 生效条件:以 root 调用 load(root) 得 rows,先打印「总节点: len(rows)」(空序列也会先打印该行),仅当 rows 为假值时才打印空库提示并 return 2,否则继续打印分层与各维度取值分布、并执行方案A/B/C/D 的 report 后 return 0。
|
|
87
91
|
def main(root):
|
|
88
92
|
rows = load(root)
|
|
89
93
|
print(f"md 记忆库: {root}")
|
|
@@ -126,4 +130,4 @@ if __name__ == "__main__":
|
|
|
126
130
|
# 会在解释器退出阶段丢缓冲(实测只落盘 444 字节),CI 里会看不到失败原因。
|
|
127
131
|
if hasattr(sys.stdout, "reconfigure"):
|
|
128
132
|
sys.stdout.reconfigure(encoding="utf-8")
|
|
129
|
-
sys.exit(main(sys.argv[1] if len(sys.argv) > 1 else DEFAULT_ROOT))
|
|
133
|
+
sys.exit(main(sys.argv[1] if len(sys.argv) > 1 else DEFAULT_ROOT))
|
package/md_cg/chain.py
CHANGED
|
@@ -39,11 +39,17 @@ EDGE_WEIGHTS = {
|
|
|
39
39
|
"correlational": 0.45,
|
|
40
40
|
"cyclic": 0.35,
|
|
41
41
|
"opposite": 0.30,
|
|
42
|
+
# 正文引用边(linkref,写入侧自动解析,2026-09-17):取弱权重——正文提及
|
|
43
|
+
# ≠ 语义相似 ≠ 因果依赖,故与 DEFAULT_EDGE_WEIGHT 同值;显式登记的意义在
|
|
44
|
+
# 「类型已知」(可审计、可按类型调参、可区分于未登记类型的兜底默认)。
|
|
45
|
+
"reference": 0.50,
|
|
42
46
|
}
|
|
43
47
|
DEFAULT_EDGE_WEIGHT = 0.50
|
|
44
48
|
|
|
45
49
|
CAUSAL_TYPES = ("causal",)
|
|
46
50
|
# 检索默认沿「有语义方向」的关系走:因果 / 时序 / 条件适用
|
|
51
|
+
# 注:reference **刻意不入**——正文提及不应进入「前提→结论」条件序列遍历,
|
|
52
|
+
# 否则 causal 链会被「提到过」这类无向弱关联稀释(模式分离)。
|
|
47
53
|
CHAIN_TYPES_DEFAULT = ("causal", "sequential", "applies_to")
|
|
48
54
|
|
|
49
55
|
MAX_DEPTH_DEFAULT = 5
|
|
@@ -51,6 +57,7 @@ MAX_DEPTH_HARD = 64
|
|
|
51
57
|
MAX_NODES_DEFAULT = 500
|
|
52
58
|
|
|
53
59
|
|
|
60
|
+
# 生效条件:edge 为 dict 时按 relation_type→relation→type 顺序取首个真值、非 dict 时 rel 记为 None,两者统一返回 str(rel or "").strip().lower()——键缺失或全为假值时得空串。
|
|
54
61
|
def edge_rel(edge):
|
|
55
62
|
"""取边的 relation_type(兼容 dict / 字符串两种写法),统一小写。"""
|
|
56
63
|
if isinstance(edge, dict):
|
|
@@ -60,6 +67,7 @@ def edge_rel(edge):
|
|
|
60
67
|
return str(rel or "").strip().lower()
|
|
61
68
|
|
|
62
69
|
|
|
70
|
+
# 生效条件:edge 为 dict 时取 target or target_id(前者假值回落后者),该值非 None 则返回其 str().strip()、为 None 返回 None;edge is None 返回 None;其余(裸字符串等)返回 str(edge).strip() 或空白串时 None。
|
|
63
71
|
def edge_target(edge):
|
|
64
72
|
"""取边的目标节点 id(兼容 target / target_id / 裸字符串)。"""
|
|
65
73
|
if isinstance(edge, dict):
|
|
@@ -70,6 +78,7 @@ def edge_target(edge):
|
|
|
70
78
|
return str(edge).strip() or None
|
|
71
79
|
|
|
72
80
|
|
|
81
|
+
# 生效条件:base 取 EDGE_WEIGHTS.get(edge_rel(edge), DEFAULT_EDGE_WEIGHT);edge 为 dict 时 conf 为 float(edge.get("confidence", 1.0))(缺该键得 1.0,值不可转 float 如 None 触发 TypeError/ValueError 时也回落 1.0),非 dict 时 conf 恒为 1.0,返回 round(base * max(0.0, min(1.0, conf)), 6);
|
|
73
82
|
def edge_weight(edge):
|
|
74
83
|
"""边权重 = 类型 base × 边置信度(缺失置信度视为 1.0 的已声明边)。"""
|
|
75
84
|
base = EDGE_WEIGHTS.get(edge_rel(edge), DEFAULT_EDGE_WEIGHT)
|
|
@@ -82,6 +91,7 @@ def edge_weight(edge):
|
|
|
82
91
|
return round(base * max(0.0, min(1.0, conf)), 6)
|
|
83
92
|
|
|
84
93
|
|
|
94
|
+
# 生效条件:edge 非 dict 时返回 "";edge 为 dict 时按 ("condition","conditions","条件") 顺序取第一个真值(list/tuple 先以 ";" 连接其中 str(x).strip() 非空的项,连接结果为空则视为假值跳过该键)并返回 str(v).strip();三键均无真值且 edge.get("condition_space") 为 dict 时返回 condition_space_text(cs, require_full=False);否则返回 "";
|
|
85
95
|
def edge_condition(edge):
|
|
86
96
|
"""边的条件标注:条件链上「这一跳在什么条件下成立」。"""
|
|
87
97
|
if not isinstance(edge, dict):
|
|
@@ -101,6 +111,7 @@ def edge_condition(edge):
|
|
|
101
111
|
return ""
|
|
102
112
|
|
|
103
113
|
|
|
114
|
+
# 生效条件:cg.get(nid) 为假值(该节点缺失或为空)时返回 [];否则以 node.get("frontmatter") or {} 与 node.get("content") or "" 调 _declared_conditions,返回其正向条件列表 pos;
|
|
104
115
|
def node_conditions(cg, nid):
|
|
105
116
|
"""节点自己声明的生效条件(CCG `# 生效条件:` 等三处来源合并)。"""
|
|
106
117
|
node = cg.get(nid)
|
|
@@ -112,6 +123,7 @@ def node_conditions(cg, nid):
|
|
|
112
123
|
return pos
|
|
113
124
|
|
|
114
125
|
|
|
126
|
+
# 生效条件:cg 已有 _chain_adj 且其 [0] 等于 bool(include_hierarchy) 时直接返回缓存的 [1];否则以 cg.index["nodes"](无 index 或无该键时视为无节点)逐节点收集 frontmatter.edges 中 edge_target 非空的出边,include_hierarchy 为真时再为 subgraph.nodes 各合成一条 relation_type="part_of"、confidence=1.0 的层级边,仅对有出边的 nid 建表,写回 cg._chain_adj=(bool(include_hierarchy), adj) 后返回 adj;
|
|
115
127
|
def adjacency(cg, include_hierarchy=True):
|
|
116
128
|
"""出邻接表:nid → [(target_id, edge_dict)]。
|
|
117
129
|
|
|
@@ -146,6 +158,7 @@ def adjacency(cg, include_hierarchy=True):
|
|
|
146
158
|
return adj
|
|
147
159
|
|
|
148
160
|
|
|
161
|
+
# 生效条件:调用即把 cg._chain_adj 置为 None(赋值抛异常时静默忽略),无返回值;
|
|
149
162
|
def invalidate_cache(cg):
|
|
150
163
|
"""写入/删除节点后丢弃邻接缓存(与 subgraph.invalidate_cache 成对调用)。"""
|
|
151
164
|
try:
|
|
@@ -154,6 +167,7 @@ def invalidate_cache(cg):
|
|
|
154
167
|
pass
|
|
155
168
|
|
|
156
169
|
|
|
170
|
+
# 生效条件:对 adj 的每个 src→[(tgt, e)] 逐条把 (src, e) 追加到 rev[tgt](同 tgt 多次追加保持出现顺序),adj 为空字典时 rev 为空字典并返回;
|
|
157
171
|
def _reverse(adj):
|
|
158
172
|
rev = {}
|
|
159
173
|
for src, outs in adj.items():
|
|
@@ -162,6 +176,7 @@ def _reverse(adj):
|
|
|
162
176
|
return rev
|
|
163
177
|
|
|
164
178
|
|
|
179
|
+
# 生效条件:start_id 起步迭代 DFS——max_depth 非 None 时先钳为 max(0, min(int(max_depth), MAX_DEPTH_HARD)),rels 为 (relation_types or ()) 的小写元组(None 或空则不作类型过滤),direction=="in" 时邻接表改用 _reverse(adjacency(cg, include_hierarchy)),每跳要求 rel∈rels(rels 非空时)、tgt 不在 seen、weight*edge_weight(e) ≥ min_weight,链在 max_depth 为 None 或 len(hops+[hop]) ≤ max_depth 时收入、在 max_depth 为 None 或 len(hops+[hop]) < max_depth 时继续下探,首跳且边无显式条件时条件回退 start_id 的 node_conditions,扩展节点数受 max_nodes、收链数受 max_chains 限制,sort=="length" 时按 (depth, -avg_weight) 排序否则按 (-weight, -depth) 排序,返回 chains[:max_chains];
|
|
165
180
|
def walk(cg, start_id, relation_types=CAUSAL_TYPES, max_depth=MAX_DEPTH_DEFAULT,
|
|
166
181
|
direction="out", max_nodes=MAX_NODES_DEFAULT, min_weight=0.0,
|
|
167
182
|
max_chains=200, include_hierarchy=True, sort="strength"):
|
|
@@ -218,13 +233,18 @@ def walk(cg, start_id, relation_types=CAUSAL_TYPES, max_depth=MAX_DEPTH_DEFAULT,
|
|
|
218
233
|
stack.append((tgt, seen | {tgt}, nhop, nw))
|
|
219
234
|
if not extended and not hops:
|
|
220
235
|
continue
|
|
236
|
+
# 终键 tuple(nodes):图遍历序取决于邻接结构的枚举序,并列(同 depth/
|
|
237
|
+
# weight/avg_weight)时若无终键,截断结果随索引构建路径漂移。
|
|
221
238
|
if sort == "length": # 对齐 AEIS infer_causal_paths 的 Occam 偏好
|
|
222
|
-
chains.sort(key=lambda c: (c["depth"], -c["avg_weight"]
|
|
239
|
+
chains.sort(key=lambda c: (c["depth"], -c["avg_weight"],
|
|
240
|
+
tuple(c["nodes"])))
|
|
223
241
|
else:
|
|
224
|
-
chains.sort(key=lambda c: (-c["weight"], -c["depth"]
|
|
242
|
+
chains.sort(key=lambda c: (-c["weight"], -c["depth"],
|
|
243
|
+
tuple(c["nodes"])))
|
|
225
244
|
return chains[:max_chains]
|
|
226
245
|
|
|
227
246
|
|
|
247
|
+
# 生效条件:kw 未含 relation_types 时先注入 CAUSAL_TYPES,再以 walk(cg, start_id, **kw) 的每条链逐跳渲染(跳条件为空串则显示「(未声明条件)」),返回 {"start": start_id, "count": 渲染链数, "chains": rendered};
|
|
228
248
|
def explain(cg, start_id, **kw):
|
|
229
249
|
"""人类可读的链式解释:「什么条件下 → 发生什么」。"""
|
|
230
250
|
kw.setdefault("relation_types", CAUSAL_TYPES)
|
|
@@ -242,6 +262,7 @@ def explain(cg, start_id, **kw):
|
|
|
242
262
|
return {"start": start_id, "count": len(rendered), "chains": rendered}
|
|
243
263
|
|
|
244
264
|
|
|
265
|
+
# 生效条件:seeds 为 dict 时取其 items、否则取 list(seeds or [])(假值 seeds 得空列表,返回空 best),逐 (sid, s0) 跳过 sid 为假值或 float(s0) 抛 TypeError/ValueError 的项,对 walk(cg, sid, relation_types, max_depth, max_nodes, max_chains, include_hierarchy) 每条链取 nodes[-1] 为 nid(nid==sid 则跳过)并按 s0*链 weight*decay^depth 对每个 nid 只保留最高分,返回 best;
|
|
245
266
|
def expand_from_seeds(cg, seeds, relation_types=CHAIN_TYPES_DEFAULT,
|
|
246
267
|
max_depth=MAX_DEPTH_DEFAULT, decay=0.9,
|
|
247
268
|
max_nodes=MAX_NODES_DEFAULT, max_chains=500,
|
|
@@ -277,4 +298,4 @@ def expand_from_seeds(cg, seeds, relation_types=CHAIN_TYPES_DEFAULT,
|
|
|
277
298
|
if cur is None or sc > cur["score"]:
|
|
278
299
|
best[nid] = {"score": round(sc, 6), "chain": c,
|
|
279
300
|
"depth": c["depth"], "conditions": c["conditions"]}
|
|
280
|
-
return best
|
|
301
|
+
return best
|