@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/writepipe.py
CHANGED
|
@@ -40,11 +40,12 @@ write 的六道闸(audit 校验 / consistency 冲突 / review 审核 / gated
|
|
|
40
40
|
|
|
41
41
|
import time
|
|
42
42
|
|
|
43
|
-
from . import twophase
|
|
43
|
+
from . import twophase, trust
|
|
44
44
|
|
|
45
45
|
__all__ = ["WritePipeline", "default_pipeline"]
|
|
46
46
|
|
|
47
47
|
|
|
48
|
+
# 生效条件:调用即对 cg 执行 flush()(无脏数据时为 no-op),且仅当该调用抛异常而 out 是 dict 时在 out 写入 "flush_error",异常本身不外抛;
|
|
48
49
|
def _commit_visibility(cg, out):
|
|
49
50
|
"""写提交边界(2026-09-16):把内存脏索引落分片日志,使本次写入对其他进程立即可见。
|
|
50
51
|
|
|
@@ -70,12 +71,14 @@ def _commit_visibility(cg, out):
|
|
|
70
71
|
class WritePipeline:
|
|
71
72
|
"""写入拦截器链(实例级;default_pipeline() 提供进程级默认单例)。"""
|
|
72
73
|
|
|
74
|
+
# 生效条件:无前置;初始化 _before / _after 两条空链(元素为 (name, fn) 二元组),不做任何注册、不触盘;
|
|
73
75
|
def __init__(self):
|
|
74
76
|
self._before = [] # [(name, fn)]
|
|
75
77
|
self._after = [] # [(name, fn)]
|
|
76
78
|
|
|
77
79
|
# ---------- 注册表 ----------
|
|
78
80
|
|
|
81
|
+
# 生效条件:fn 可调用时先按 name 摘除同名项,再在 position 为 None 时把 (str(name), fn) 追加到链尾、否则插入 max(0, int(position))(position=0 非 None,走插入分支);fn 不可调用则抛 TypeError;
|
|
79
82
|
def register_before(self, name, fn, position=None):
|
|
80
83
|
"""注册 before 拦截器(同名幂等替换;position=None 追加到链尾)。
|
|
81
84
|
|
|
@@ -90,11 +93,13 @@ class WritePipeline:
|
|
|
90
93
|
else:
|
|
91
94
|
self._before.insert(max(0, int(position)), item)
|
|
92
95
|
|
|
96
|
+
# 生效条件:按 str(name) 过滤 _before,仅保留 x[0] != str(name) 的项(即删除全部同名项),并返回删除前后长度是否不等以表示是否确有移除;
|
|
93
97
|
def unregister_before(self, name):
|
|
94
98
|
n0 = len(self._before)
|
|
95
99
|
self._before = [x for x in self._before if x[0] != str(name)]
|
|
96
100
|
return len(self._before) != n0
|
|
97
101
|
|
|
102
|
+
# 生效条件:fn 可调用时先按 name 摘除同名项,再把 (str(name), fn) 追加到 _after 链尾;fn 不可调用则抛 TypeError;
|
|
98
103
|
def register_after(self, name, fn):
|
|
99
104
|
"""注册 after 观察者:fn(ctx, out),落盘成功后按序调用。"""
|
|
100
105
|
if not callable(fn):
|
|
@@ -102,17 +107,20 @@ class WritePipeline:
|
|
|
102
107
|
self.unregister_after(name)
|
|
103
108
|
self._after.append((str(name), fn))
|
|
104
109
|
|
|
110
|
+
# 生效条件:按 str(name) 过滤 _after,仅保留 x[0] != str(name) 的项(即删除全部同名项),并返回删除前后长度是否不等以表示是否确有移除;
|
|
105
111
|
def unregister_after(self, name):
|
|
106
112
|
n0 = len(self._after)
|
|
107
113
|
self._after = [x for x in self._after if x[0] != str(name)]
|
|
108
114
|
return len(self._after) != n0
|
|
109
115
|
|
|
116
|
+
# 生效条件:无前置;返回 {"before": [...注册名], "after": [...注册名]},只暴露名字不暴露函数对象,顺序即执行顺序;
|
|
110
117
|
def names(self):
|
|
111
118
|
return {"before": [n for n, _f in self._before],
|
|
112
119
|
"after": [n for n, _f in self._after]}
|
|
113
120
|
|
|
114
121
|
# ---------- 执行 ----------
|
|
115
122
|
|
|
123
|
+
# 生效条件:传入 cg 与 a(a 为假值如 None 时按 {} 处理,nid 取 a.get("node_id") 或其假值回落 "mem_"+毫秒时间戳),任一 before 钩子返回非 None 即记 halted_by 并经 _commit_visibility 短路返回该响应,全部放行则记 twophase 意图后跑 _executor(其抛 BaseException 时记 STATUS_ERROR 并原样重抛)再顺序跑 after 链、_commit_visibility 并返回落盘 out;
|
|
116
124
|
def execute(self, cg, a):
|
|
117
125
|
"""写入请求入口:跑 before 链 → 链尾执行器 → after 链。
|
|
118
126
|
|
|
@@ -157,16 +165,23 @@ class WritePipeline:
|
|
|
157
165
|
# 默认链(原 mcp_server._cg_dispatch op=="write" 分支,行为逐字节搬运)
|
|
158
166
|
# --------------------------------------------------------------------------
|
|
159
167
|
|
|
168
|
+
# 生效条件:ctx["a"] 经 audit.audit 得出的 state 为 ACCEPT 时返 None 放行,为 REJECT 时经 cg.add_rejected 返回 ok=False/moved_to="rejected",其余 state 经 cg.propose 返回 moved_to="review_queue"(pr 带 dedup 时再附 dedup/dup_of/dup_status 并改写 hint);
|
|
160
169
|
def _gate_audit(ctx):
|
|
161
170
|
"""校验闸:audit.audit 四态。ACCEPT 放行;REJECT 负记忆;其余入审核队列。"""
|
|
162
171
|
a = ctx["a"]
|
|
163
172
|
cg = ctx["cg"]
|
|
164
173
|
from . import audit
|
|
174
|
+
payload = {"content": a.get("content", ""), "action": a.get("action"),
|
|
175
|
+
"sensitivity": a.get("sensitivity"),
|
|
176
|
+
"topic": a.get("query") or a.get("intent")}
|
|
177
|
+
if (a.get("content_kind") or "").strip() == "hyperedge":
|
|
178
|
+
# 超边验证器(回放比对)需要锚与结构键:fm 键平铺在 a 顶层,
|
|
179
|
+
# 经 hyperedge.audit_payload 装配三键载荷(缺锚由验证器 fail-closed)。
|
|
180
|
+
from . import hyperedge as _he
|
|
181
|
+
payload = _he.audit_payload(a)
|
|
165
182
|
verdict = audit.audit(
|
|
166
183
|
(a.get("content_kind") or "").strip(),
|
|
167
|
-
|
|
168
|
-
"sensitivity": a.get("sensitivity"),
|
|
169
|
-
"topic": a.get("query") or a.get("intent")},
|
|
184
|
+
payload,
|
|
170
185
|
{"cg": cg, "principal": getattr(cg, "principal", None)})
|
|
171
186
|
ctx["verdict"] = verdict
|
|
172
187
|
st = verdict["state"]
|
|
@@ -207,6 +222,7 @@ def _gate_audit(ctx):
|
|
|
207
222
|
return out
|
|
208
223
|
|
|
209
224
|
|
|
225
|
+
# 生效条件:ctx["a"]["consistency"] 为假值时返回 None;on_conflict 缺键或假值回落 "defer",仅当 verdict=REJECT 且 on_conflict=reject(返回 moved_to="conflict_rejected")或 verdict∈{REJECT,BLINDSPOT} 且 on_conflict=defer(转 review_queue,去重命中时改写 hint)才拦截,其余 on_conflict 取值返回 None;
|
|
210
226
|
def _gate_consistency(ctx):
|
|
211
227
|
"""冲突闸:节点间自动冲突检测(三级决策)。
|
|
212
228
|
|
|
@@ -264,6 +280,7 @@ def _gate_consistency(ctx):
|
|
|
264
280
|
return out
|
|
265
281
|
|
|
266
282
|
|
|
283
|
+
# 生效条件:ctx["a"] 的 gated 为假值时返 None 放行;为真值时按 cg.remember_gated 返回的 verdict 落两段式账,且仅 verdict 为 ACCEPT 时 ok/committed 为 True,verdict 为 MERGE 时记 committed 并置 moved_to="merged_into:"+merged_into,verdict 为 DROP/DEFER 时记 aborted 且 moved_to 为其小写值;
|
|
267
284
|
def _gate_gated(ctx):
|
|
268
285
|
"""主动遗忘闸(gated=true 时启用):writelimit 限流 + forgetting 三问四态。
|
|
269
286
|
|
|
@@ -315,9 +332,126 @@ def _gate_gated(ctx):
|
|
|
315
332
|
out["moved_to"] = "merged_into:" + str(res.get("merged_into"))
|
|
316
333
|
elif v in ("DROP", "DEFER"):
|
|
317
334
|
out["moved_to"] = v.lower()
|
|
335
|
+
# gated 是**替代落盘路径**(自行落盘/合并后直接返回终态、不跑 after 链),故
|
|
336
|
+
# 一跳同步传播须在此单独触发——否则 MERGE 类覆写会漏传下游(非对称边界,
|
|
337
|
+
# 与 `_after_trust` 注释互指)。
|
|
338
|
+
if committed or v == "MERGE":
|
|
339
|
+
prop = trust.mark_dependents(
|
|
340
|
+
cg, ctx["nid"],
|
|
341
|
+
reason="上游节点被 gated 写入/合并(内容或验证态可能已变)",
|
|
342
|
+
actor="writepipe:gated", trigger="write_gated")
|
|
343
|
+
if isinstance(prop, dict) and prop.get("changed"):
|
|
344
|
+
out["propagation"] = {"changed": prop.get("changed"),
|
|
345
|
+
"updated": (prop.get("updated") or [])[:10]}
|
|
318
346
|
return out
|
|
319
347
|
|
|
320
348
|
|
|
349
|
+
# 生效条件:ctx["a"]["content"] 的「# 子功能:」行含显式跨节点引用(`@<节点 id>`)且 depends_on 解析为空时返回 ok=False/error="E050" 的终态;depends_on 含库中不存在的 id 时返回 ok=False/error="E051" 的终态;其余(无该行 / 哨兵 / 自然语言自述 / 声明且目标齐备)返回 None 放行;
|
|
350
|
+
def _gate_deps(ctx):
|
|
351
|
+
"""依赖声明闸(before 链:linkref 之后、audit 之前):**硬拒条件缺失**。
|
|
352
|
+
|
|
353
|
+
「声明」的界定(收窄裁定 b,2026-09-19):以 `@<节点 id>` 显式引用为界——
|
|
354
|
+
自然语言**自述子功能**(描述本单元**内部**构成)不构成依赖声明。原因:CCG
|
|
355
|
+
编译产物六要素必含「子功能」行,若沿用「非哨兵即声明」,每个 CCG 节点落库后
|
|
356
|
+
都会被自己的闸门永久要求 depends_on(E050 死锁,test_ccgc V16f 实证)。
|
|
357
|
+
依赖不是必填元数据;**只有显式声称依赖却不落字段**才是违规(声称与落盘不一致)。
|
|
358
|
+
|
|
359
|
+
为何是硬拒而非告警:依赖是失效传播的**唯一入口**。声明缺失时,「上游变了
|
|
360
|
+
下游要存疑」这条链从源头就不存在——它既不报错、也不留任何信号,缺陷以
|
|
361
|
+
「静默不传播」的形态长期存活(比报错更难发现)。故按契约缺失处理,与
|
|
362
|
+
`ccgc` 的 E 码体系同构(E050 声明缺失 / E051 目标不存在)。
|
|
363
|
+
|
|
364
|
+
张力消解(与 provenance「写路径永不阻断」纪律的边界,二者不冲突):
|
|
365
|
+
- **声明缺失 / 目标不可解析 = 契约违规** → 硬拒(本闸只做这件事);
|
|
366
|
+
- **传播落盘失败 = 运维降级** → 告警不阻断(见 `_after_trust`)。
|
|
367
|
+
|
|
368
|
+
判据基于**入参**而非落盘后回读:首次写入时节点尚不存在,回读式校验会
|
|
369
|
+
永远放行(等于闸门失效)。
|
|
370
|
+
"""
|
|
371
|
+
a = ctx["a"]
|
|
372
|
+
cg = ctx["cg"]
|
|
373
|
+
from . import nodefile
|
|
374
|
+
if not nodefile.declares_dependency(a.get("content") or ""):
|
|
375
|
+
return None
|
|
376
|
+
deps = trust.as_deps(a.get("depends_on"))
|
|
377
|
+
if not deps:
|
|
378
|
+
return {"ok": False, "id": ctx["nid"], "committed": False,
|
|
379
|
+
"gate": "deps", "error": "E050",
|
|
380
|
+
"verdict": ctx.get("verdict"),
|
|
381
|
+
"hint": ("依赖声明缺失(E050):正文以 " + nodefile.DEP_REF_MARK
|
|
382
|
+
+ "<节点 id> 显式声明了跨节点依赖(「# 子功能:」行),"
|
|
383
|
+
"但 depends_on 未给出可解析目标。依赖必须是**可解析的字段**"
|
|
384
|
+
"(形如 depends_on=[\"<被依赖节点 id>\"]),不能只是散文——"
|
|
385
|
+
"否则被依赖单元变动时,下游无处可传。补齐后重试;"
|
|
386
|
+
"若该行只是描述本单元内部构成(自述),去掉 "
|
|
387
|
+
+ nodefile.DEP_REF_MARK + " 引用或改填「无」即可。"
|
|
388
|
+
"本闸是契约闸门的正常行为,不是工具故障。")}
|
|
389
|
+
known = set((getattr(cg, "index", None) or {}).get("nodes") or {})
|
|
390
|
+
missing = [d for d in deps if d not in known]
|
|
391
|
+
if missing:
|
|
392
|
+
return {"ok": False, "id": ctx["nid"], "committed": False,
|
|
393
|
+
"gate": "deps", "error": "E051", "missing": missing[:10],
|
|
394
|
+
"verdict": ctx.get("verdict"),
|
|
395
|
+
"hint": "依赖目标不存在(E051):depends_on 指向 "
|
|
396
|
+
+ ", ".join(missing[:5])
|
|
397
|
+
+ ",但库中查无此节点——声称依赖一个并不存在的地基,"
|
|
398
|
+
"失效传播会在此处断链。请先建立被依赖节点,或修正 id。"}
|
|
399
|
+
return None
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
# 生效条件:out 为 dict 且 out["committed"] 为真时,调 trust.mark_dependents 做一跳同步传播(异常吞掉并降级),并在节点时间轴非「时效内」时往 out 写 "validity" 提示;其余情况直接返回不做任何动作;
|
|
403
|
+
def _after_trust(ctx, out):
|
|
404
|
+
"""after 观察者:落盘后触发**一跳同步传播** + 时效提示。
|
|
405
|
+
|
|
406
|
+
为何落在 after 而非 before:只有真正落盘(内容确实变了)才构成「地基动了」;
|
|
407
|
+
before 链短路路径(REJECT / DEFER)本就不跑 after 链,语义天然正确。
|
|
408
|
+
例外:`gated` 是替代落盘路径(自行落盘并返回终态、不跑 after),故它的
|
|
409
|
+
传播在 `_gate_gated` 内部单独触发(见该处注释)。
|
|
410
|
+
|
|
411
|
+
**永不抛**:传播失败只降级(`trust.mark_dependents` 内部已兜底并写台账),
|
|
412
|
+
绝不把「写入已成功」改写为失败——与 `_commit_visibility` 同款边界。
|
|
413
|
+
"""
|
|
414
|
+
if not isinstance(out, dict) or not out.get("committed"):
|
|
415
|
+
return
|
|
416
|
+
cg = ctx["cg"]
|
|
417
|
+
nid = ctx.get("nid")
|
|
418
|
+
if not nid:
|
|
419
|
+
return
|
|
420
|
+
prop = trust.mark_dependents(
|
|
421
|
+
cg, nid, reason="上游节点被写入/覆写(内容或验证态可能已变)",
|
|
422
|
+
actor="writepipe:trust", trigger="write")
|
|
423
|
+
if isinstance(prop, dict) and prop.get("changed"):
|
|
424
|
+
out["propagation"] = {"changed": prop.get("changed"),
|
|
425
|
+
"updated": (prop.get("updated") or [])[:10]}
|
|
426
|
+
# 热路径失效(2026-09-19 热温冷分层):写入后清缓存
|
|
427
|
+
try:
|
|
428
|
+
from . import hotcache as _hc
|
|
429
|
+
_hc.invalidate(cg, nid)
|
|
430
|
+
except Exception: # noqa: BLE001
|
|
431
|
+
pass # 缓存失效失败不阻断写入(永不抛)
|
|
432
|
+
|
|
433
|
+
# 冷路径入队(2026-09-19 热温冷分层):异步深度验证
|
|
434
|
+
try:
|
|
435
|
+
from . import coldverify as _cv
|
|
436
|
+
_cv.enqueue(cg, nid, action="reverify",
|
|
437
|
+
reason="writepipe:after_trust")
|
|
438
|
+
except Exception: # noqa: BLE001
|
|
439
|
+
pass # 入队失败不阻断写入
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
# 生效条件:a 的 content_kind 非 hyperedge 即返回 {},为 hyperedge 时延迟导入
|
|
443
|
+
# hyperedge.EXTRA_FM_KEYS 收集 a 中非 None 的对应键返回(其余写入零键透传);
|
|
444
|
+
def _hyperedge_extra(a):
|
|
445
|
+
"""hyperedge 写入的 fm 扩展键透传。计划决策 2:超边写入走 write 动词既有
|
|
446
|
+
审核链、不造新通道——链尾执行器是唯一落盘点,cg.add 经 **extra 落 fm,
|
|
447
|
+
故扩展键在此透传;落盘面丢字段 = 上游声明静默失效。延迟导入防循环依赖。"""
|
|
448
|
+
if (a.get("content_kind") or "").strip() != "hyperedge":
|
|
449
|
+
return {}
|
|
450
|
+
from . import hyperedge as _he
|
|
451
|
+
return {k: a[k] for k in _he.EXTRA_FM_KEYS if a.get(k) is not None}
|
|
452
|
+
|
|
453
|
+
|
|
454
|
+
# 生效条件:由链尾以含 cg 与 a 的 ctx 调用即无条件执行 cg.add 落盘并返回 ok=True/committed=True,ctx["cvd"] 非 None 时附加 consistency 字段;
|
|
321
455
|
def _executor(ctx):
|
|
322
456
|
"""链尾执行器(常驻不可卸载):cg.add 直写落盘。
|
|
323
457
|
|
|
@@ -335,7 +469,13 @@ def _executor(ctx):
|
|
|
335
469
|
non_applicable_conditions=a.get("non_applicable_conditions"),
|
|
336
470
|
override=bool(a.get("override")), consistency=False,
|
|
337
471
|
derived_from=_split_ids(a.get("derived_from")),
|
|
338
|
-
relation=a.get("relation")
|
|
472
|
+
relation=a.get("relation"),
|
|
473
|
+
# 可验证记忆单元(裁定 D):依赖/双时间轴/验证态随写入落 fm。
|
|
474
|
+
# 透传而非丢弃——落盘面丢字段=上游声明静默失效(比报错难发现)。
|
|
475
|
+
depends_on=trust.as_deps(a.get("depends_on")),
|
|
476
|
+
valid_from=a.get("valid_from"), valid_until=a.get("valid_until"),
|
|
477
|
+
verification_state=a.get("verification_state"),
|
|
478
|
+
**_hyperedge_extra(a))
|
|
339
479
|
out = {"ok": True, "id": ctx["nid"], "committed": True,
|
|
340
480
|
"verdict": ctx["verdict"]}
|
|
341
481
|
if ctx.get("cvd") is not None:
|
|
@@ -343,6 +483,7 @@ def _executor(ctx):
|
|
|
343
483
|
return out
|
|
344
484
|
|
|
345
485
|
|
|
486
|
+
# 生效条件:value 传入即无条件延迟导入并转调 mcp_server._split_ids 后原样返回其结果(本符号无自身分支);
|
|
346
487
|
def _split_ids(value):
|
|
347
488
|
# 与 mcp_server._split_ids 同源(延迟导入,单一真源)
|
|
348
489
|
from .mcp_server import _split_ids as _f
|
|
@@ -356,17 +497,47 @@ def _split_ids(value):
|
|
|
356
497
|
_DEFAULT = None
|
|
357
498
|
|
|
358
499
|
|
|
500
|
+
# 生效条件:pipe 传入即对其依次注册 before 的 linkref(position=0)/deps/audit/consistency/gated 与 after 的 linkref/trust(同名幂等替换),并返回同一 pipe;
|
|
359
501
|
def install_default_gates(pipe):
|
|
360
|
-
"""
|
|
502
|
+
"""把默认闸以拦截器形态注册(幂等:同名替换,可重复调用)。
|
|
503
|
+
|
|
504
|
+
链序(2026-09-19 起):
|
|
505
|
+
before = linkref(解析) → deps(依赖声明) → audit → consistency → gated
|
|
506
|
+
→ 链尾执行器
|
|
507
|
+
after = linkref(建边) → trust(一跳传播)
|
|
508
|
+
|
|
509
|
+
linkref 置于链首的理由:正文引用解析是**纯读、无副作用**,且其结果必须
|
|
510
|
+
先于任何短路闸写入 ctx,供 after 链消费。短路闸(REJECT/DEFER/gated)
|
|
511
|
+
返回终态时 `execute` 不跑 after 链,故未落盘的写入不会建边——语义正确。
|
|
512
|
+
|
|
513
|
+
deps 夹在 linkref 与 audit 之间的理由:两件事都与内容政策无关,故在 audit
|
|
514
|
+
之前;linkref 之后是因为它只解析正文引用、不动依赖声明——依赖是**字段域**
|
|
515
|
+
而非正文域,顺序倒置不会互相污染,但保持「解析在前、裁决在后」的一致读序。
|
|
516
|
+
|
|
517
|
+
after 的 trust 置于 linkref 之后:建边先于传播——传播按 `depends_on`
|
|
518
|
+
反查(字段域)而非边域,故顺序不影响正确性;置于其后只为让 ctx 中的
|
|
519
|
+
边信息先落定,便于排障时读 ctx。
|
|
520
|
+
|
|
521
|
+
linkref 的**落点是 after 而非注入 `a["edges"]`**:`cg.add` 是全量重建
|
|
522
|
+
fm,注入 edges 会在覆写既有节点时清空其原有边(破坏性副作用);
|
|
523
|
+
`append_edge` 是边域窄原语且幂等(见 linkref 模块 docstring)。
|
|
524
|
+
依赖声明同忌经 `a["edges"]` 注入——走 `depends_on` 字段域。
|
|
525
|
+
"""
|
|
526
|
+
from . import linkref
|
|
527
|
+
pipe.register_before("linkref", linkref.before_hook(), position=0)
|
|
528
|
+
pipe.register_before("deps", _gate_deps)
|
|
361
529
|
pipe.register_before("audit", _gate_audit)
|
|
362
530
|
pipe.register_before("consistency", _gate_consistency)
|
|
363
531
|
pipe.register_before("gated", _gate_gated)
|
|
532
|
+
pipe.register_after("linkref", linkref.after_hook())
|
|
533
|
+
pipe.register_after("trust", _after_trust)
|
|
364
534
|
return pipe
|
|
365
535
|
|
|
366
536
|
|
|
537
|
+
# 生效条件:模块级 _DEFAULT 为 None 时新建 WritePipeline 并经 install_default_gates 注册后缓存返回,否则直接返回已缓存的 _DEFAULT 单例;
|
|
367
538
|
def default_pipeline():
|
|
368
539
|
"""进程级默认写入链(单例)。自定义闸门 register_before 即插即拔。"""
|
|
369
540
|
global _DEFAULT
|
|
370
541
|
if _DEFAULT is None:
|
|
371
542
|
_DEFAULT = install_default_gates(WritePipeline())
|
|
372
|
-
return _DEFAULT
|
|
543
|
+
return _DEFAULT
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@furongjun1999/dsh-memory",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.9",
|
|
4
4
|
"description": "灵枢(Lingshu·líng shū)DeepSeek Harness 插件:完整大脑——长期记忆/知识飞轮/自我认知/递归反思接入 DSH,对话自动沉淀进 md_cg 认知图(md 文档)",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"scripts": {
|
|
79
79
|
"build": "tsc -p tsconfig.json",
|
|
80
80
|
"test": "npm run build && node --import tsx --test \"test/*.test.ts\"",
|
|
81
|
-
"prepare": "npm
|
|
81
|
+
"prepare": "node -e \"const fs=require('fs');const tsc='node_modules/typescript/bin/tsc';fs.existsSync(tsc)?require('child_process').execFileSync(process.execPath,[tsc,'-p','tsconfig.json'],{stdio:'inherit'}):console.log('[prepare] 跳过构建:typescript 未安装(NODE_ENV=production 或 --omit=dev 会省略 devDependencies)——需要构建请用 npm install --include=dev')\"",
|
|
82
82
|
"prepublishOnly": "npm run build && npm test"
|
|
83
83
|
},
|
|
84
84
|
"repository": {
|
|
Binary file
|
|
@@ -50,6 +50,7 @@ DIM_KEYWORDS = (
|
|
|
50
50
|
)
|
|
51
51
|
|
|
52
52
|
|
|
53
|
+
# 生效条件:对 sys.stdout 与 sys.stderr 依次尝试 reconfigure(encoding="utf-8"),任何异常都被 try/except 吞掉后继续;
|
|
53
54
|
def _stdout_utf8():
|
|
54
55
|
for stream in (sys.stdout, sys.stderr):
|
|
55
56
|
try:
|
|
@@ -58,6 +59,7 @@ def _stdout_utf8():
|
|
|
58
59
|
pass
|
|
59
60
|
|
|
60
61
|
|
|
62
|
+
# 生效条件:text 为 None 或空串时返回 default,text 已是 dict/list 时原样返回,否则返回 json.loads(text) 的结果;
|
|
61
63
|
def _load_json_arg(text, default):
|
|
62
64
|
if text is None or text == "":
|
|
63
65
|
return default
|
|
@@ -66,12 +68,14 @@ def _load_json_arg(text, default):
|
|
|
66
68
|
return json.loads(text)
|
|
67
69
|
|
|
68
70
|
|
|
71
|
+
# 生效条件:value 为假值(None/""/[]/{}/0)时返回 [],value 是 str 时返回 [value],其余返回 list(value);
|
|
69
72
|
def _as_list(value):
|
|
70
73
|
if not value:
|
|
71
74
|
return []
|
|
72
75
|
return [value] if isinstance(value, str) else list(value)
|
|
73
76
|
|
|
74
77
|
|
|
78
|
+
# 生效条件:text 为假值时先置为 "",按 DIM_KEYWORDS 顺序返回首个含命中关键词的维度,全部未命中返回 "条件边界";
|
|
75
79
|
def classify_dimension(text):
|
|
76
80
|
"""把失配描述归入五失配维度之一;未命中归入「条件边界」。"""
|
|
77
81
|
text = text or ""
|
|
@@ -81,10 +85,12 @@ def classify_dimension(text):
|
|
|
81
85
|
return "条件边界"
|
|
82
86
|
|
|
83
87
|
|
|
88
|
+
# 生效条件:key 在 FOUR_DIMS 中时原样返回 key,否则返回 classify_dimension(key) 的归类结果;
|
|
84
89
|
def _dim_of_key(key):
|
|
85
90
|
return key if key in FOUR_DIMS else classify_dimension(key)
|
|
86
91
|
|
|
87
92
|
|
|
93
|
+
# 生效条件:conditions 为假值(None/""/{})时返回 {},conditions 是 str 时先 json.loads 再 dict(...),其余直接 dict(conditions);
|
|
88
94
|
def normalize_conditions(conditions):
|
|
89
95
|
if not conditions:
|
|
90
96
|
return {}
|
|
@@ -95,6 +101,7 @@ def normalize_conditions(conditions):
|
|
|
95
101
|
|
|
96
102
|
# ---------------------------------------------------------------- 步骤 1:声明
|
|
97
103
|
|
|
104
|
+
# 生效条件:position 为假值时回落 "designer",不在 POSITION_MAP 中时 raise SystemExit,space 假值时用空 OrderedDict 并 setdefault 观测位置,返回含 space_id、layer、missing_dims 的 OrderedDict;
|
|
98
105
|
def declare_core(position="designer", space=None):
|
|
99
106
|
"""条件空间声明:四维 + 有效范围 + 切换规则(3.1/3.2)。"""
|
|
100
107
|
position = position or "designer"
|
|
@@ -115,6 +122,7 @@ def declare_core(position="designer", space=None):
|
|
|
115
122
|
|
|
116
123
|
# ---------------------------------------------------------------- 步骤 2-3:白箱三问 + 四态
|
|
117
124
|
|
|
125
|
+
# 生效条件:evidence 取 candidate["evidence"] or conditions["anchors"] 再 _as_list,vpath 取 verification_path or candidate["verification_path"] 再 _as_list;evidence 为空返回 (False,"观测工具",detail),否则 conditions 缺任一 FOUR_DIMS 时返回 (False, 首个缺失维度, detail),否则 vpath 为空返回 (False,"条件边界",detail),全齐返回 (True,None,detail);
|
|
118
126
|
def whitebox_check(candidate, conditions, verification_path=None):
|
|
119
127
|
"""白箱三问(第〇章)。返回 (ok, missing_dim, detail)。"""
|
|
120
128
|
evidence = _as_list(candidate.get("evidence") or conditions.get("anchors"))
|
|
@@ -134,6 +142,7 @@ def whitebox_check(candidate, conditions, verification_path=None):
|
|
|
134
142
|
return True, None, detail
|
|
135
143
|
|
|
136
144
|
|
|
145
|
+
# 生效条件:candidates 为假值(None/[])判 BLINDSPOT,否则逐候选按 not_applicable 与 query 的冲突记 REJECT、when 与 conditions 不符记 DEFER,全为 REJECT 判 REJECT、无合格候选取首个 DEFER、多个合格候选仅唯一 distinguishing 时选之否则 DEFER,verdict 仍空时由 whitebox_check(cand, conditions, verification_path) 定 ACCEPT 或 DEFER;
|
|
137
146
|
def judge_core(query, conditions=None, candidates=None, verification_path=None):
|
|
138
147
|
"""四态资格裁决(6章.2):先拒绝,后接受。"""
|
|
139
148
|
conditions = normalize_conditions(conditions)
|
|
@@ -248,6 +257,7 @@ def emit_mcp_calls(result):
|
|
|
248
257
|
return calls
|
|
249
258
|
|
|
250
259
|
|
|
260
|
+
# 生效条件:从 result["conditions"] 取四维(.get 缺键显示 <未声明>)、从 result["traces"] 中首个含 "whitebox" 的项渲染白箱行,emit_mcp 为真值时追加 emit_mcp_calls(result) 的 JSON,返回 '\n'.join(lines);
|
|
251
261
|
def render_verdict(result, emit_mcp=False):
|
|
252
262
|
c = result["conditions"]
|
|
253
263
|
lines = ["[判定单]",
|
|
@@ -275,6 +285,7 @@ def render_verdict(result, emit_mcp=False):
|
|
|
275
285
|
|
|
276
286
|
# ---------------------------------------------------------------- 步骤 5:归因 / 蒸馏
|
|
277
287
|
|
|
288
|
+
# 生效条件:error 为假值时先 dict({}),missing 取 error.get("missing",""),dim 取 error.get("dimension") or classify_dimension(missing) 且不在 FIVE_MISMATCH 时回落 "条件边界",返回含 triple/mismatch_dim/discipline 的 OrderedDict;
|
|
278
289
|
def attribute_core(error):
|
|
279
290
|
"""条件层归因:三方条件空间失配 + 五失配维度定位(附录22.3)。"""
|
|
280
291
|
error = dict(error or {})
|
|
@@ -297,6 +308,7 @@ def attribute_core(error):
|
|
|
297
308
|
])
|
|
298
309
|
|
|
299
310
|
|
|
311
|
+
# 生效条件:对 rows 每行按 dimension 或 classify_dimension(row.get("missing","")) 分组(不在 FIVE_MISMATCH 归入 "条件边界"),每组以首行 symptom/missing 生成一条带 when/rule/change 的纪律项,rows 为空返回空 items 列表;
|
|
300
312
|
def distill_core(rows):
|
|
301
313
|
"""失败集合 → 带适用条件的可复用纪律(1.1.1)。"""
|
|
302
314
|
groups = defaultdict(list)
|
|
@@ -318,6 +330,7 @@ def distill_core(rows):
|
|
|
318
330
|
return items
|
|
319
331
|
|
|
320
332
|
|
|
333
|
+
# 生效条件:对 items 逐项(依赖 it["missing"]/["rule"]/["when"]/["change"]/["evidence"])编号渲染 markdown 段落,items 为空时返回仅含标题与说明的文本;
|
|
321
334
|
def render_distill(items):
|
|
322
335
|
lines = ["# 蒸馏纪律(失败资产化)", "",
|
|
323
336
|
"> 失败 → 根因(条件层)→ 可复用纪律(带适用条件)→ 注入 → 不再犯(1.1.1)", ""]
|
|
@@ -334,6 +347,7 @@ def render_distill(items):
|
|
|
334
347
|
|
|
335
348
|
# ---------------------------------------------------------------- probe / selftest
|
|
336
349
|
|
|
350
|
+
# 生效条件:按 os.environ.get("DMCG_HOME") 再 DEFAULT_DMCG_HOME 顺序,跳过假值或不含 md_cg 子目录的 base,命中的 base 插入 sys.path 后 import md_cg.mdcos,成功返回 available=True/mode=read-only,import 抛异常返回 available=False/degraded 附 error,全部未命中返回 available=False、base=None;
|
|
337
351
|
def probe_mdcg():
|
|
338
352
|
"""探测 md_cg 认知图可用性(只读 import,不实例化、不写库)。"""
|
|
339
353
|
bases = [os.environ.get("DMCG_HOME"), DEFAULT_DMCG_HOME]
|
|
@@ -358,6 +372,7 @@ def probe_mdcg():
|
|
|
358
372
|
("error", "未找到 md_cg(可设 DMCG_HOME 指向 dsh-memory 根目录)")])
|
|
359
373
|
|
|
360
374
|
|
|
375
|
+
# 生效条件:按 case.get("kind") 分派 —— declare 比对 declare_core 的 missing_dims 与 expect 的 dims_complete/observation_position_contains,judge 与 verify 比对 judge_core 的 verdict/missing_dim 与 expect 的 state/missing_dim/require_verification_path/require_missing_dim,missing 比对 attribute_core 的 mismatch_dim,其余 kind 记 ok=False;
|
|
361
376
|
def run_cases(cases):
|
|
362
377
|
results = []
|
|
363
378
|
for case in cases:
|
|
@@ -397,6 +412,7 @@ def run_cases(cases):
|
|
|
397
412
|
|
|
398
413
|
# ---------------------------------------------------------------- CLI
|
|
399
414
|
|
|
415
|
+
# 生效条件:items 为假值(None/空列表)时返回空 OrderedDict,否则每项不含 "=" 时 raise SystemExit,含 "=" 的按首个 "=" 拆分并 strip 后写入 space;
|
|
400
416
|
def _parse_space(items):
|
|
401
417
|
space = OrderedDict()
|
|
402
418
|
for it in items or []:
|
|
@@ -527,4 +543,4 @@ def main(argv=None):
|
|
|
527
543
|
|
|
528
544
|
|
|
529
545
|
if __name__ == "__main__":
|
|
530
|
-
sys.exit(main())
|
|
546
|
+
sys.exit(main())
|
|
@@ -21,6 +21,7 @@ sys.path.insert(0, os.path.join(ROOT, "scripts"))
|
|
|
21
21
|
import designer # noqa: E402 (路径注入后导入)
|
|
22
22
|
|
|
23
23
|
|
|
24
|
+
# 生效条件:读取 cases_path 的 JSONL(strip 后为空的行跳过)交给 designer.run_cases,返回 (results, passed, total) 三元组;
|
|
24
25
|
def run(cases_path):
|
|
25
26
|
with open(cases_path, encoding="utf-8") as fh:
|
|
26
27
|
cases = [json.loads(line) for line in fh if line.strip()]
|
|
@@ -30,6 +31,7 @@ def run(cases_path):
|
|
|
30
31
|
return results, passed, total
|
|
31
32
|
|
|
32
33
|
|
|
34
|
+
# 生效条件:argv(None 时取 sys.argv)解析后 --cases 缺省为 HERE/cases.jsonl,调用 run 后 passed==total 返回 0 否则返回 1,--json 为真值时打印 JSON 汇总、否则打印逐项明细;
|
|
33
35
|
def main(argv=None):
|
|
34
36
|
ap = argparse.ArgumentParser(prog="selftest.py",
|
|
35
37
|
description="设计者视角 · 认知能力验收")
|
|
@@ -57,4 +59,4 @@ def main(argv=None):
|
|
|
57
59
|
|
|
58
60
|
|
|
59
61
|
if __name__ == "__main__":
|
|
60
|
-
sys.exit(main())
|
|
62
|
+
sys.exit(main())
|
package/src/hooks.ts
CHANGED
|
@@ -21,6 +21,9 @@
|
|
|
21
21
|
* 模型请求组装 system prompt 时自动注入灵枢最近记忆
|
|
22
22
|
* (`stg(op=timeline)`,最近记忆节点时间线),让记忆"自动可用"而不只依赖
|
|
23
23
|
* Agent 主动调用 recall/think 工具。失败静默(不影响请求)。
|
|
24
|
+
* ⚠️ 该注入块的**稳定性**决定宿主是否新追加快照:内容没变时也必须照旧 push
|
|
25
|
+
* (宿主按渲染后的整段文本去重);跳过 push 反而会各追加一份「有块/无块」的快照
|
|
26
|
+
* —— 详见 installMemoryHooks 里的长注释。
|
|
24
27
|
*
|
|
25
28
|
* ⚠️ 注入文本**必经** escapePromptBraces(src/lib/prompt_safety.ts,issue #16):
|
|
26
29
|
* 宿主对 context 文本做严格 `{{variable}}` 插值,裸 `{{` 会让每轮 assemble 抛错
|
|
@@ -126,8 +129,6 @@ const RECALL_MIN_CHARS = 24
|
|
|
126
129
|
const RECALL_MAX_CHARS = 1400
|
|
127
130
|
/** 永久层不自动注入(按需用 mdcg_recall / lingshu_stg 取)。 */
|
|
128
131
|
const RECALL_SKIP_LAYERS = new Set(['anchor', 'self'])
|
|
129
|
-
/** 连续跳过多少步后强制补一次(内容未变也刷新,防止压缩归档后块消失)。 */
|
|
130
|
-
const RECALL_REPUSH_EVERY = 8
|
|
131
132
|
|
|
132
133
|
/** 分级递减渲染:按距当前的次序逐档收窄,早期条目信息量更大。
|
|
133
134
|
*
|
|
@@ -159,17 +160,6 @@ function formatTimelineDecayed(payload: unknown): string {
|
|
|
159
160
|
return out.join('\n').slice(0, RECALL_MAX_CHARS)
|
|
160
161
|
}
|
|
161
162
|
|
|
162
|
-
/** 去重:内容与上次相同就不再 push 新副本。
|
|
163
|
-
*
|
|
164
|
-
* 背景:本钩子挂在 `system-prompt/assemble` 上,每个 step 都会 push 一份,而每份
|
|
165
|
-
* 都会留成独立的 surface 节点 → 同一块累积 N 份(实测 11 份 ≈2700 tok/请求),
|
|
166
|
-
* 随步数线性增长。去重后同一块只占 1 份,外加每 RECALL_REPUSH_EVERY 步一次自愈刷新。 */
|
|
167
|
-
function shouldPushRecall(text: string, lastText: string, skippedSincePush: number): boolean {
|
|
168
|
-
if (!text) return false
|
|
169
|
-
if (text !== lastText) return true
|
|
170
|
-
return skippedSincePush >= RECALL_REPUSH_EVERY
|
|
171
|
-
}
|
|
172
|
-
|
|
173
163
|
/** 安装自动记忆钩子(effect 作用域内,随插件卸载自动移除)。
|
|
174
164
|
*
|
|
175
165
|
* mdcg 为 null(config.mdcg.enabled=false)时自动记忆整体停用:记忆真源是
|
|
@@ -200,19 +190,27 @@ export function installMemoryHooks(ctx: Context, mdcg: MdcgClient | null, opts:
|
|
|
200
190
|
|
|
201
191
|
// P1 完善(自动 recall 注入):每次模型请求组装 system prompt 时,注入灵枢最近记忆。
|
|
202
192
|
// 用 system-prompt/assemble 事件(waterfall)而非 llm/stream——后者请求 deep-frozen 不可改写。
|
|
193
|
+
//
|
|
194
|
+
// ⚠️ 必须**每步都 push**,哪怕内容与上一步逐字节相同。原因在宿主侧(dsh-agent-loop 的
|
|
195
|
+
// RuntimeContextProjection):assembly.contexts 会被渲染成一段「运行时上下文快照」,
|
|
196
|
+
// 每个 step 拿渲染后的**整段文本**与上一份已提交的快照比对,**只有不同才**在会话里
|
|
197
|
+
// append 一条新的 user/message(append 语义,旧的不会被替换或移除)。于是:
|
|
198
|
+
// · 内容不变 + 照旧 push → 渲染文本不变 → 宿主不追加任何东西(零开销、零增长);
|
|
199
|
+
// · 内容不变 + 跳过 push → 渲染文本**变了**(少了本块)→ 宿主追加一份「没有本块」的
|
|
200
|
+
// 快照;下一步再 push 又把本块加回来 → **再**追加一份。跳过一次反而多花两份快照
|
|
201
|
+
// (实测每份 ~250 tok),这正是 v0.4.8「每 8 步强制补一次」的自愈刷新会把长会话的
|
|
202
|
+
// inject 推到 30k+ tok 的原因。
|
|
203
|
+
// 因此本实现把「要不要补」交还给宿主:压缩归档后宿主会把 retained 置空并重新投影快照
|
|
204
|
+
// (RuntimeContextProjection 的 retained === null 分支),本块自然跟着回来——
|
|
205
|
+
// 不需要插件自己数步数做自愈。
|
|
203
206
|
if (opts.autoRecall) {
|
|
204
207
|
const recallLimit = Math.max(1, Math.min(10, opts.autoRecallLimit || 4))
|
|
205
|
-
// 去重状态:同一块内容只保留一份 surface 节点,避免随步数线性增长。
|
|
206
|
-
let lastRecallText = ''
|
|
207
|
-
let skippedSincePush = 0
|
|
208
208
|
ctx.on('system-prompt/assemble', async (assembly, _ctx, next) => {
|
|
209
209
|
try {
|
|
210
210
|
// 异步取最近记忆节点(失败静默——不阻塞模型请求)
|
|
211
211
|
if (graph.isReady()) {
|
|
212
212
|
const text = formatTimelineDecayed(await graph.timeline(recallLimit))
|
|
213
|
-
if (
|
|
214
|
-
lastRecallText = text
|
|
215
|
-
skippedSincePush = 0
|
|
213
|
+
if (text) {
|
|
216
214
|
// 注入边界转义(issue #16):宿主 system-prompt 对 context 文本做严格
|
|
217
215
|
// `{{variable}}` 插值,裸 `{{` 会 throw → 该轮请求整体失败。记忆原文
|
|
218
216
|
// (含用户命令里的 `{{.X}}`)必须保真落库,故只在注入副本上打断 `{{`。
|
|
@@ -220,8 +218,6 @@ export function installMemoryHooks(ctx: Context, mdcg: MdcgClient | null, opts:
|
|
|
220
218
|
name: 'lingshu:auto-recall',
|
|
221
219
|
text: escapePromptBraces(`【灵枢最近记忆】\n${text.slice(0, RECALL_MAX_CHARS)}`),
|
|
222
220
|
})
|
|
223
|
-
} else {
|
|
224
|
-
skippedSincePush += 1
|
|
225
221
|
}
|
|
226
222
|
}
|
|
227
223
|
}
|
package/src/index.ts
CHANGED
|
@@ -17,8 +17,10 @@
|
|
|
17
17
|
* name: '@furongjun1999/dsh-memory'
|
|
18
18
|
* config:
|
|
19
19
|
* mdcg: # 记忆真源:认知图(md 文档)
|
|
20
|
-
* # root = 记忆写入路径(用户可改)。缺省 =
|
|
21
|
-
* #
|
|
20
|
+
* # root = 记忆写入路径(用户可改)。缺省 = 用户级状态根 data/mdcg
|
|
21
|
+
* # (~/.dsh/.dsh-memory/data/mdcg;**不在插件包内**——包内数据会被
|
|
22
|
+
* # pnpm 更新连目录一起删掉,见 src/lib/datapath.ts 头注)
|
|
23
|
+
* # 覆盖优先级:env MDCG_ROOT > <用户级状态根>/paths.json > 本项 > 默认
|
|
22
24
|
* # root: 'D:/somewhere/mdcg' # 例:把记忆库放到别处
|
|
23
25
|
* actor: 'dsh-memory'
|
|
24
26
|
* env: # 写入凭据:默认【关闭】,由你决定是否打开
|
|
@@ -52,7 +54,7 @@ import { installMemoryHooks, type MemoryHooksOptions } from './hooks.js'
|
|
|
52
54
|
// LIB 本地库:角色扮演网页 / 互维维护 / 白箱 LLM 适配器统一收在 src/lib/。
|
|
53
55
|
import { installRoleplayWeb } from './lib/roleplay_web.js'
|
|
54
56
|
import { MdcgClient } from './lib/mdcg_client.js'
|
|
55
|
-
import { describeDataPaths, mdcgRoot } from './lib/datapath.js'
|
|
57
|
+
import { describeDataPaths, migrateLegacyData, mdcgRoot, repoRoot } from './lib/datapath.js'
|
|
56
58
|
// 写入凭据密钥环(首启引导):显式配置 → ~/.mdcg/token → 首启自动签发。
|
|
57
59
|
import { resolveToken, type TokenResolution } from './lib/token_store.js'
|
|
58
60
|
|
|
@@ -178,8 +180,9 @@ export const Config: z<Config> = z.object({
|
|
|
178
180
|
.default({ enabled: false, heartbeatMs: 10 * 60 * 1000 }),
|
|
179
181
|
/** 认知图(md_cg):记忆唯一真源。
|
|
180
182
|
* root 为**记忆写入路径**(用户可改)。缺省空串 = 未指定,按优先级解析:
|
|
181
|
-
* ① env MDCG_ROOT ②
|
|
182
|
-
*
|
|
183
|
+
* ① env MDCG_ROOT ② `<用户级状态根>/paths.json` 的 root
|
|
184
|
+
* (旧 `<插件仓>/data/paths.json` 兼容读)
|
|
185
|
+
* ③ 本项 ④ 默认 `<用户级状态根>/data/mdcg`
|
|
183
186
|
* 相对路径一律相对**插件仓根**解析——历史教训:相对 cwd 的相对路径随
|
|
184
187
|
* 宿主 cwd 漂移,cwd 落在别仓时记忆真源分裂成互不可见的两处。
|
|
185
188
|
* tenant/actor 决定私有内容加解密的身份:与 migrate_roleplay 的
|
|
@@ -255,15 +258,39 @@ export async function apply(ctx: Context, config: Config): Promise<void> {
|
|
|
255
258
|
timeoutMs: config.toolCallTimeoutMs,
|
|
256
259
|
maxRetryDelayMs: config.maxRetryDelayMs,
|
|
257
260
|
})
|
|
261
|
+
// 数据面迁出插件包(issue #18 相邻问题):旧版把运行时数据与路径配置写在包内
|
|
262
|
+
// `<pkg>/data`,pnpm 更新会连目录一起替换(实机实证:data/ 54 文件 → 0,
|
|
263
|
+
// 46 条记忆节点靠人工备份回填)。首启把「旧位置仍有货」的数据**复制**到用户级
|
|
264
|
+
// 数据根;必须在 mdcg.start() 之前——store 要在 Python 子进程接管前就位。
|
|
265
|
+
const migrated = migrateLegacyData()
|
|
266
|
+
if (migrated.ran) {
|
|
267
|
+
ctx.logger.info(
|
|
268
|
+
`dsh-memory: 数据面已迁出插件包(旧位置只复制未删除):`
|
|
269
|
+
+ `${migrated.from} → ${migrated.to}(${migrated.copied} 项`
|
|
270
|
+
+ `${migrated.failed.length ? `,失败 ${migrated.failed.join(',')}` : ''})`,
|
|
271
|
+
)
|
|
272
|
+
}
|
|
258
273
|
mdcg.start()
|
|
259
274
|
// 启动留痕:把「记忆真源在哪、由谁决定、路径是否存在」写进可审计日志,
|
|
260
275
|
// 避免再次出现「以为在记忆、其实写到了别仓」的静默分裂(历史事故)。
|
|
261
276
|
const dp = describeDataPaths(config.mdcg.root)
|
|
262
277
|
ctx.logger.info(
|
|
263
278
|
`dsh-memory: 记忆真源路径 = ${dp.mdcgRoot}(来源 ${dp.source},`
|
|
264
|
-
+
|
|
279
|
+
+ `状态根=${dp.stateRoot},dataRoot=${dp.dataRoot},`
|
|
280
|
+
+ `存在=${dp.mdcgRootExists ? '是' : '否(首次写入将创建)'},`
|
|
265
281
|
+ `用户可改:${dp.pathsFile})`,
|
|
266
282
|
)
|
|
283
|
+
// 包管理器装的插件 + 路径配置还在旧包内位置:pnpm 下次更新会把该文件连目录一起
|
|
284
|
+
// 删除,用户配置随之丢失(回落默认根 → 表现为「记忆不见了」)。只在真的会被删的
|
|
285
|
+
// 布局下提醒(开发用的 git clone 不含 node_modules 段 → 不打扰)。
|
|
286
|
+
const pnpmManaged = repoRoot().split(/[\\/]/).includes('node_modules')
|
|
287
|
+
if (pnpmManaged && String(dp.pathsFileSource) === 'legacy') {
|
|
288
|
+
ctx.logger.warn(
|
|
289
|
+
`dsh-memory: 路径配置仍在插件包内(${dp.pathsFile})——pnpm 更新该包会连目录`
|
|
290
|
+
+ `一起删除。请复制到 ${String(dp.stateRoot)}/paths.json 后重启`
|
|
291
|
+
+ `(数据面已自动复制到 ${String(dp.dataRoot)})。`,
|
|
292
|
+
)
|
|
293
|
+
}
|
|
267
294
|
brainReady = await mdcg.waitReady()
|
|
268
295
|
if (brainReady) {
|
|
269
296
|
ctx.logger.info(`dsh-memory: 认知图已就绪(MDCG_ROOT=${resolvedRoot})`)
|