@furongjun1999/dsh-memory 0.4.8 → 0.4.10
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 +40 -3
- package/lib/lib/mdcg_client.js +46 -22
- 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_subproc_encoding.py +188 -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 +132 -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 +64 -25
- 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/evolution.py
CHANGED
|
@@ -84,18 +84,22 @@ _STATE_RE = re.compile(r"```json state\s*\n(.*?)\n```", re.S)
|
|
|
84
84
|
# 路径 / 追加
|
|
85
85
|
# --------------------------------------------------------------------------
|
|
86
86
|
|
|
87
|
+
# 生效条件:cg 可解析出 root 时,返回 os.path.join(cg.root, 模块常量 EVOLUTION_DIR),无守卫分支。
|
|
87
88
|
def evolution_dir(cg) -> str:
|
|
88
89
|
return os.path.join(cg.root, EVOLUTION_DIR)
|
|
89
90
|
|
|
90
91
|
|
|
92
|
+
# 生效条件:cg 可解析出 root 时,返回 evolution_dir(cg) 与模块常量 LEDGER_NAME 的 os.path.join 结果。
|
|
91
93
|
def ledger_path(cg) -> str:
|
|
92
94
|
return os.path.join(evolution_dir(cg), LEDGER_NAME)
|
|
93
95
|
|
|
94
96
|
|
|
97
|
+
# 生效条件:无入参,任何一次调用都返回 "evo-" + time.strftime("%Y%m%d-%H%M%S") + "-" + uuid.uuid4().hex 的前 4 位。
|
|
95
98
|
def new_entry_id() -> str:
|
|
96
99
|
return "evo-" + time.strftime("%Y%m%d-%H%M%S") + "-" + uuid.uuid4().hex[:4]
|
|
97
100
|
|
|
98
101
|
|
|
102
|
+
# 生效条件:cg 与 text 给定时,在 FileLock(ledger_path(cg)) 内取旧内容(读不到或空则回退 _HEADER,不以换行结尾则补一个换行)并 atomic_write 写入 old + text,无需 text 非空。
|
|
99
103
|
def _append(cg, text: str):
|
|
100
104
|
"""向账本追加一条(读-改-写 + 跨进程锁,崩溃不留半截文件)。"""
|
|
101
105
|
p = ledger_path(cg)
|
|
@@ -112,6 +116,7 @@ def _append(cg, text: str):
|
|
|
112
116
|
atomic_write(p, old + text)
|
|
113
117
|
|
|
114
118
|
|
|
119
|
+
# 生效条件:ledger_path(cg) 对应路径不存在时返回 "",否则以 encoding="utf-8"、errors="replace" 打开并返回 f.read() 的全部内容。
|
|
115
120
|
def read_ledger(cg) -> str:
|
|
116
121
|
p = ledger_path(cg)
|
|
117
122
|
if not os.path.exists(p):
|
|
@@ -124,7 +129,9 @@ def read_ledger(cg) -> str:
|
|
|
124
129
|
# 渲染 / 解析
|
|
125
130
|
# --------------------------------------------------------------------------
|
|
126
131
|
|
|
132
|
+
# 生效条件:field、before、after 任意取值下都返回 f"{field} {_short(before)}→{_short(after)}"。
|
|
127
133
|
def _fmt_change(field, before, after) -> str:
|
|
134
|
+
# 生效条件:v 为 list/tuple 返回 f"{len(v)}条",为 dict 返回 f"{len(v)}键",为 None 返回 "—",为 float 返回 f"{v:g}",其余类型返回 str(v)。
|
|
128
135
|
def _short(v):
|
|
129
136
|
if isinstance(v, (list, tuple)):
|
|
130
137
|
return f"{len(v)}条"
|
|
@@ -138,6 +145,7 @@ def _fmt_change(field, before, after) -> str:
|
|
|
138
145
|
return f"{field} {_short(before)}→{_short(after)}"
|
|
139
146
|
|
|
140
147
|
|
|
148
|
+
# 生效条件:before 与 after 中任一为假值(None、{} 等)时该侧按 {} 参与比较,返回 STATE_FIELDS 中 b.get(k) != a.get(k) 的字段对应的 _fmt_change(k, b.get(k), a.get(k)) 列表。
|
|
141
149
|
def diff(before, after):
|
|
142
150
|
"""状态差异 → 人类可读列表(只比认知状态字段)。"""
|
|
143
151
|
b, a = before or {}, after or {}
|
|
@@ -145,6 +153,7 @@ def diff(before, after):
|
|
|
145
153
|
for k in STATE_FIELDS if b.get(k) != a.get(k)]
|
|
146
154
|
|
|
147
155
|
|
|
156
|
+
# 生效条件:entry 含键 'entry_id'(缺失即 KeyError)时,按 _BULLET_ORDER 输出值不属 None/""/[]/{} 的字段行(node_id 假值显示 '—'),并在 entry.get("state") 为真值时追加 json 状态块,返回以 "\n" 连接的文本。
|
|
148
157
|
def _fmt(entry: dict) -> str:
|
|
149
158
|
lines = [f"## {entry['entry_id']} · `{entry.get('node_id') or '—'}`", ""]
|
|
150
159
|
for k in _BULLET_ORDER:
|
|
@@ -162,6 +171,7 @@ def _fmt(entry: dict) -> str:
|
|
|
162
171
|
return "\n".join(lines)
|
|
163
172
|
|
|
164
173
|
|
|
174
|
+
# 生效条件:text 中 _ENTRY_RE 匹配到 0 个条目时返回 [],匹配到则按各匹配区间解析为字典列表(node 为空或 "—" 时 node_id 置 None,state 段 json.loads 抛 ValueError 时 state 置 None,kind 缺省回落模块常量 KIND_CONDITION_GAP)。
|
|
165
175
|
def _parse(text: str):
|
|
166
176
|
out = []
|
|
167
177
|
marks = list(_ENTRY_RE.finditer(text))
|
|
@@ -190,6 +200,7 @@ def _parse(text: str):
|
|
|
190
200
|
# 认知状态
|
|
191
201
|
# --------------------------------------------------------------------------
|
|
192
202
|
|
|
203
|
+
# 生效条件:cg.get(node_id) 为假值(含 None)时返回 None,否则返回 frontmatter 中属于 STATE_FIELDS 且值不为 None 的字段("layer" 缺失时用 node.get("path") 首段补上),保留 []/{}/"" 等显式空值。
|
|
193
204
|
def state_of(cg, node_id):
|
|
194
205
|
"""抽取节点的认知状态(可回滚字段),节点不存在返回 None。
|
|
195
206
|
|
|
@@ -209,6 +220,7 @@ def state_of(cg, node_id):
|
|
|
209
220
|
# 记录
|
|
210
221
|
# --------------------------------------------------------------------------
|
|
211
222
|
|
|
223
|
+
# 生效条件:pattern.strip() 非空且 kind 属于模块常量 KINDS 时追加并返回条目 e(pattern 为空白或 kind 未知抛 ValueError;action 去空白后为空且 extra 为真时取 extra.pop("change", "") 作 action;before/after 任一 non-None 时 state 记入二者 or {};extra 中值不属 None/""/[]/{} 的键并入 e)。
|
|
212
224
|
def record(cg, node_id=None, pattern="", missing="", action="", evidence="",
|
|
213
225
|
source="", kind=KIND_CONDITION_GAP, before=None, after=None,
|
|
214
226
|
extra=None):
|
|
@@ -249,6 +261,7 @@ def record(cg, node_id=None, pattern="", missing="", action="", evidence="",
|
|
|
249
261
|
# 查询
|
|
250
262
|
# --------------------------------------------------------------------------
|
|
251
263
|
|
|
264
|
+
# 生效条件:cg 给定时返回 _parse(read_ledger(cg)) 的列表——node_id 为真值则只留该节点记录、kind 为真值则只留该类型记录、newest_first 为真值则 reverse、limit 为真值时截断为前 int(limit) 条(limit 为 0/None 等假值时不截断)。
|
|
252
265
|
def entries(cg, limit=None, node_id=None, kind=None, newest_first=True):
|
|
253
266
|
recs = _parse(read_ledger(cg))
|
|
254
267
|
if node_id:
|
|
@@ -262,6 +275,7 @@ def entries(cg, limit=None, node_id=None, kind=None, newest_first=True):
|
|
|
262
275
|
return recs
|
|
263
276
|
|
|
264
277
|
|
|
278
|
+
# 生效条件:遍历 entries(cg, limit=0)(limit=0 为假值故不截断,覆盖全部记录),命中 r.get("entry_id") == entry_id 时返回该记录,否则返回 None。
|
|
265
279
|
def show(cg, entry_id):
|
|
266
280
|
for r in entries(cg, limit=0):
|
|
267
281
|
if r.get("entry_id") == entry_id:
|
|
@@ -269,10 +283,12 @@ def show(cg, entry_id):
|
|
|
269
283
|
return None
|
|
270
284
|
|
|
271
285
|
|
|
286
|
+
# 生效条件:cg 与 node_id 给定时返回 {"node_id": node_id, "entries": entries(cg, limit=limit, node_id=node_id)}——node_id 为假值时 entries 不按节点过滤,limit 默认 50 为真值故截断,传 0 等假值则不截断。
|
|
272
287
|
def history(cg, node_id, limit=50):
|
|
273
288
|
return {"node_id": node_id, "entries": entries(cg, limit=limit, node_id=node_id)}
|
|
274
289
|
|
|
275
290
|
|
|
291
|
+
# 生效条件:cg 给定时返回统计字典,其中 recs 取 entries(cg, limit=0) 全量、real 剔除 kind == 模块常量 KIND_ROLLBACK(missing 为空的条目不计入 by_missing、kind 缺省回落 KIND_CONDITION_GAP、source 缺省回落 "unknown"),top_patterns 取按 count 降序 pattern 升序排序后的前 int(limit) 项(limit 为 0 时切片为空列表)。
|
|
276
292
|
def patterns(cg, limit=10):
|
|
277
293
|
"""规律统计:哪一维条件反复缺失、由谁触发、哪些规律重复出现。
|
|
278
294
|
|
|
@@ -301,6 +317,7 @@ def patterns(cg, limit=10):
|
|
|
301
317
|
"by_source": by_source, "top_patterns": top[:int(limit)]}
|
|
302
318
|
|
|
303
319
|
|
|
320
|
+
# 生效条件:cg 给定时返回固定结构字典——missing_top 取 patterns(cg) 的 by_missing 中计数最大项的键(by_missing 为空则 "(暂无)"),top_pattern 取 top_patterns[0]["pattern"](为空则 "(暂无)"),recent 取 entries(cg, limit=5)。
|
|
304
321
|
def summary(cg):
|
|
305
322
|
p = patterns(cg)
|
|
306
323
|
missing_top = (max(p["by_missing"].items(), key=lambda kv: kv[1])[0]
|
|
@@ -322,6 +339,7 @@ def summary(cg):
|
|
|
322
339
|
# 回滚
|
|
323
340
|
# --------------------------------------------------------------------------
|
|
324
341
|
|
|
342
|
+
# 生效条件:cg.get(node_id) 为假时返回 ([], [{"field": "*", "reason": "节点不存在"}]),否则先处理 target 中的 "layer"(为真且不同于当前 path 首段时经 cg._move_layer 迁移并 appended "layer",抛异常则记入 skipped,相同/为假时直接 appended "layer"),再按 STATE_FIELDS 处理:k 在 remove 中且存在于 frontmatter 则 pop 并 appended "-k",k 在 target 中且值相同则 appended k、不同则改写并在 changed 时经 cg._write_node 回写,最终返回 (applied, skipped)(若层迁移后 node 取不回则提前返回)。
|
|
325
343
|
def _apply_state(cg, node_id, target, remove=()):
|
|
326
344
|
"""把目标状态写回节点。返回 (applied, skipped)。
|
|
327
345
|
|
|
@@ -375,6 +393,7 @@ def _apply_state(cg, node_id, target, remove=()):
|
|
|
375
393
|
return applied, skipped
|
|
376
394
|
|
|
377
395
|
|
|
396
|
+
# 生效条件:cg 与 entry_id 定位 show(cg, entry_id) 后逐项判定——条目不存在返回未找到错误、kind == 模块常量 KIND_ROLLBACK 返回拒回滚错误、state 的 before 为假值返回无回滚状态错误、node_id 为假值返回未绑定节点错误、state_of 为 None 返回节点不可读错误;dry_run 为真值时返回含 current/target/would_remove/would_change 的预览;否则经 _apply_state(target, remove=after 中不属 before 且非 "layer" 的字段) 回写、尝试 cg.rebuild_index()(异常静默)并 record 一条 KIND_ROLLBACK 条目后返回 ok=True 结果。
|
|
378
397
|
def rollback(cg, entry_id, dry_run=False, note=""):
|
|
379
398
|
"""把某条演化撤回其 before 状态,并记一条 rollback 条目(撤销不可静默)。"""
|
|
380
399
|
src = show(cg, entry_id)
|
|
@@ -423,6 +442,7 @@ def rollback(cg, entry_id, dry_run=False, note=""):
|
|
|
423
442
|
# 自描述
|
|
424
443
|
# --------------------------------------------------------------------------
|
|
425
444
|
|
|
445
|
+
# 生效条件:无入参,任何调用都返回含 module/schema/ledger/carrier/principles/fields/kinds/state_fields/condition_dims/rollback/actions 的固定字典(kinds、state_fields、condition_dims 分别来自模块常量 KINDS、STATE_FIELDS、CONDITION_DIMS)。
|
|
426
446
|
def catalog():
|
|
427
447
|
return {
|
|
428
448
|
"module": "evolution",
|
|
@@ -455,4 +475,4 @@ def catalog():
|
|
|
455
475
|
},
|
|
456
476
|
"actions": ["record", "entries", "show", "history", "patterns",
|
|
457
477
|
"summary", "rollback", "catalog"],
|
|
458
|
-
}
|
|
478
|
+
}
|
package/md_cg/export.py
CHANGED
|
@@ -27,12 +27,14 @@ _ROW_KEYS = ("id", "layer", "path", "tags", "importance", "confidence",
|
|
|
27
27
|
"created_at", "edges", "protected", "sensitivity", "content")
|
|
28
28
|
|
|
29
29
|
|
|
30
|
+
# 生效条件:给定 cg 与 kind 即返回 os.path.join(cg.root, f"export_{kind}_{当前 %Y%m%d_%H%M%S 时间戳}.jsonl"),无任何前置校验或分支。
|
|
30
31
|
def _default_out(cg, kind: str) -> str:
|
|
31
32
|
"""默认导出路径:`<root>/export_<kind>_<ts>.jsonl`(可搬运、可灾备)。"""
|
|
32
33
|
ts = time.strftime("%Y%m%d_%H%M%S")
|
|
33
34
|
return os.path.join(cg.root, f"export_{kind}_{ts}.jsonl")
|
|
34
35
|
|
|
35
36
|
|
|
37
|
+
# 生效条件:cg.get(nid) 为 None 时返回 None;否则以 fm = node.get("frontmatter") or {}(缺键或假值回落空 dict)与 entry 组装行,layer 取 fm 的 layer、为假值时回落 entry.get("layer"),include_content 为真值时追加 content = node.get("content") or "",最终只保留 _ROW_KEYS 中实际存在的键。
|
|
36
38
|
def _row(cg, nid: str, entry: dict, include_content: bool = True):
|
|
37
39
|
"""索引条目 → 导出行(回读节点拿到 frontmatter + 正文)。
|
|
38
40
|
|
|
@@ -62,6 +64,7 @@ def _row(cg, nid: str, entry: dict, include_content: bool = True):
|
|
|
62
64
|
return {k: row[k] for k in _ROW_KEYS if k in row}
|
|
63
65
|
|
|
64
66
|
|
|
67
|
+
# 生效条件:源为 cg.index 的 nodes(缺 "nodes" 键或假值回落空 dict)——ids 为真值时只取其中确实在 nodes 里的 id,否则取全部——按 (float(created_at or 0), id) 排序后逐个产出同时满足 layer(为真时须 (e.layer or "") == layer)、tag(为真时须在 e.tags or [] 中)、since/until(非 None 时按 float 比较 created_at)的条目,limit 为真值且已产出 n 条并 n >= int(limit) 时停止(limit 为 0 或 None 不设上限)。
|
|
65
68
|
def _iter_entries(cg, layer=None, since=None, until=None, tag=None,
|
|
66
69
|
ids=None, limit=None):
|
|
67
70
|
"""按条件遍历索引条目(不读文件,保证筛选阶段零 IO)。
|
|
@@ -91,6 +94,7 @@ def _iter_entries(cg, layer=None, since=None, until=None, tag=None,
|
|
|
91
94
|
return
|
|
92
95
|
|
|
93
96
|
|
|
97
|
+
# 生效条件:将 entries 逐条经 _row 转换后写入 out_path(先 makedirs 其父目录、写 out_path + ".tmp"、结束后 os.replace 为 out_path),_row 返回 None 的条目只累加 skipped_unreadable 而不写入,其余写入并累加 written 及 by_layer(row 的 layer 缺键或假值记为 "?");include_content 透传给 _row 决定行是否含正文;返回含 ok/out/written/skipped_unreadable/by_layer/bytes/elapsed_ms 的统计 dict。
|
|
94
98
|
def _write_jsonl(cg, out_path: str, entries, include_content: bool = True):
|
|
95
99
|
"""流式写 JSONL(tmp + 原子改名)。返回统计 dict。"""
|
|
96
100
|
out_path = os.path.abspath(out_path)
|
|
@@ -120,6 +124,7 @@ def _write_jsonl(cg, out_path: str, entries, include_content: bool = True):
|
|
|
120
124
|
"elapsed_ms": round((time.time() - t0) * 1000, 1)}
|
|
121
125
|
|
|
122
126
|
|
|
127
|
+
# 生效条件:out 为假值(None/空串)时回落为 _default_out(cg, "graph"),以 _iter_entries(cg, layer=layer, limit=limit) 为条目流调用 _write_jsonl(include_content=include_content),再补 action="graph" 与 note 后返回该结果 dict。
|
|
123
128
|
def export_graph(cg, out: str = None, layer=None, limit=None,
|
|
124
129
|
include_content: bool = True):
|
|
125
130
|
"""全库导出(默认含正文)。"""
|
|
@@ -133,6 +138,7 @@ def export_graph(cg, out: str = None, layer=None, limit=None,
|
|
|
133
138
|
return res
|
|
134
139
|
|
|
135
140
|
|
|
141
|
+
# 生效条件:ids 先按 [str(i) for i in (ids or []) if str(i).strip()] 规整,规整结果为空(ids 为 None/空容器/全空白项)时返回 {'ok': False, 'error': 'ids 不能为空'};否则 out 为假值时回落为 _default_out(cg, "nodes"),以 _iter_entries(cg, ids=ids) 为条目流调用 _write_jsonl(include_content=include_content),再补 action="nodes"、requested=len(ids)、missing=索引 keys 与 ids 的差集排序后返回。
|
|
136
142
|
def export_nodes(cg, ids, out: str = None, include_content: bool = True):
|
|
137
143
|
"""按 id 列表导出(顺序 = 传入顺序)。"""
|
|
138
144
|
ids = [str(i) for i in (ids or []) if str(i).strip()]
|
|
@@ -147,6 +153,7 @@ def export_nodes(cg, ids, out: str = None, include_content: bool = True):
|
|
|
147
153
|
return res
|
|
148
154
|
|
|
149
155
|
|
|
156
|
+
# 生效条件:out 为假值(None/空串)时回落为 _default_out(cg, "slice"),以 _iter_entries(cg, layer=layer, since=since, until=until, tag=tag, limit=limit) 为条目流调用 _write_jsonl(include_content=include_content),再补 action="slice" 与记录 layer/since/until/tag 的 filter 后返回。
|
|
150
157
|
def export_slice(cg, out: str = None, layer=None, since=None, until=None,
|
|
151
158
|
tag=None, limit=None, include_content: bool = True):
|
|
152
159
|
"""按层 / 时间窗 / 标签切片导出(有界,便于增量搬运)。"""
|
|
@@ -159,6 +166,7 @@ def export_slice(cg, out: str = None, layer=None, since=None, until=None,
|
|
|
159
166
|
return res
|
|
160
167
|
|
|
161
168
|
|
|
169
|
+
# 生效条件:只读 cg.index 的 nodes(缺 "nodes" 键或假值回落空 dict),逐条统计 layer(缺键或假值记 "?")、verification_basis(缺键或假值记 "(未声明)")、tags 计数(按计数降序取前 15)、protected 与 has_neg_conditions 为真值的条目数,以及 created_at 为真值时的 min/max(全为 0 或缺键时二者均为 None),返回含 total/by_layer/by_verification_basis/protected/with_non_applicable/top_tags/time_range/note 的 dict。
|
|
162
170
|
def export_stat(cg):
|
|
163
171
|
"""导出前体检:层分布 / 验证基底 / 标签 Top / 时间范围。**只读索引,零 IO**。"""
|
|
164
172
|
nodes = (cg.index.get("nodes") or {})
|
|
@@ -180,7 +188,8 @@ def export_stat(cg):
|
|
|
180
188
|
if ca:
|
|
181
189
|
t_min = ca if t_min is None else min(t_min, ca)
|
|
182
190
|
t_max = ca if t_max is None else max(t_max, ca)
|
|
183
|
-
top_tags = sorted(by_tag.items(),
|
|
191
|
+
top_tags = sorted(by_tag.items(),
|
|
192
|
+
key=lambda kv: (-kv[1], str(kv[0])))[:15]
|
|
184
193
|
return {"ok": True, "action": "stat", "total": len(nodes),
|
|
185
194
|
"by_layer": by_layer, "by_verification_basis": by_basis,
|
|
186
195
|
"protected": protected, "with_non_applicable": with_neg,
|
|
@@ -190,6 +199,7 @@ def export_stat(cg):
|
|
|
190
199
|
"如需密级分布请用 graph 导出后统计。")}
|
|
191
200
|
|
|
192
201
|
|
|
202
|
+
# 生效条件:act = (action or "graph").strip().lower()(action 为 None/空串等假值时取 "graph")——act 为 "graph"/"nodes"/"slice"/"stat" 时分别转调 export_graph/export_nodes/export_slice/export_stat(out、layer、limit、since、until、tag、ids 取自 kw 对应键,include_content 取 kw.get("include_content", True)),其它 act 值抛 ValueError。
|
|
193
203
|
def run(cg, action: str = "graph", **kw):
|
|
194
204
|
"""export op 唯一入口。"""
|
|
195
205
|
act = (action or "graph").strip().lower()
|
package/md_cg/forgetting.py
CHANGED
|
@@ -80,6 +80,7 @@ LOG_FILE = "_forgetting.jsonl"
|
|
|
80
80
|
|
|
81
81
|
# ---------------------------------------------------------------- 三问
|
|
82
82
|
|
|
83
|
+
# 生效条件:role 与 verification_basis 各自经 str(x or "").strip().lower() 后按序判——role 命中模块常量 EXTERNAL_ROLES 返回 "external_surprising";否则 role 命中 INTERNAL_ROLES、或两者都不命中前者时 verification_basis 命中 DETERMINISTIC_BASIS,返回 "internal_deterministic";否则 role 为 "assistant"/"agent" 返回 "self_generated";全不命中返回 "unknown"。
|
|
83
84
|
def source_kind(role=None, verification_basis=None):
|
|
84
85
|
"""Q3 的来源面:内部确定性产生 vs 外部惊奇来源。"""
|
|
85
86
|
r = str(role or "").strip().lower()
|
|
@@ -95,6 +96,7 @@ def source_kind(role=None, verification_basis=None):
|
|
|
95
96
|
return "unknown"
|
|
96
97
|
|
|
97
98
|
|
|
99
|
+
# 生效条件:new_grams 为空集(假值)时返回 0.0;非空时返回 len(new_grams & body_grams)/len(new_grams)。
|
|
98
100
|
def _coverage(new_grams, body_grams):
|
|
99
101
|
if not new_grams:
|
|
100
102
|
return 0.0
|
|
@@ -107,6 +109,7 @@ def _coverage(new_grams, body_grams):
|
|
|
107
109
|
_TEMPLATE_LABELS = ("功能名", "生效条件", "子功能", "执行", "验证方式", "不适用条件")
|
|
108
110
|
|
|
109
111
|
|
|
112
|
+
# 生效条件:content 为 None 或假值时按 "" 处理,结果为空串;否则逐行剥离 "#" 与 _TEMPLATE_LABELS 标签后以 "" 直接拼接。
|
|
110
113
|
def payload(content):
|
|
111
114
|
"""剥离 CCG 固定标签后的**内容骨架**(保留字段值,丢弃字段名与标记)。"""
|
|
112
115
|
out = []
|
|
@@ -123,6 +126,7 @@ def payload(content):
|
|
|
123
126
|
return "".join(out)
|
|
124
127
|
|
|
125
128
|
|
|
129
|
+
# 生效条件:content 经 payload/bigrams 得空集合时直接返回零值 best(max=0.0、with=None、compared=0);否则遍历 cg.index 的 nodes,跳过 nid==exclude,layer 为真值时只比较 str(layer 字段 or "")==layer 的节点,cg.get(nid) 抛异常/返回假值、或该节点 content 的 bigrams 为空则跳过,每计入一个节点后若 n>=limit 立即 break(故 limit 为 0 或负数时只比较首项即停),返回覆盖度最大者 best(无覆盖度提升时不更新 with/jaccard,compared 为实际计入数)。
|
|
126
130
|
def redundancy(cg, content, layer="contextual", exclude=None, limit=MAX_COMPARE):
|
|
127
131
|
"""Q1 重复?——新内容被既有同层节点覆盖的最大比例。"""
|
|
128
132
|
new = bigrams(payload(content))
|
|
@@ -157,6 +161,7 @@ def redundancy(cg, content, layer="contextual", exclude=None, limit=MAX_COMPARE)
|
|
|
157
161
|
return best
|
|
158
162
|
|
|
159
163
|
|
|
164
|
+
# 生效条件:dup 必填并转 float;dup=0 时 p 取 EPS,返回 -log2(EPS) 这一有限大值;dup>=1 时返回 0.0。
|
|
160
165
|
def self_information(dup):
|
|
161
166
|
"""Q3 的自信息代理:I = -log2(min(1, dup + ε)),单位 bit。
|
|
162
167
|
|
|
@@ -167,6 +172,7 @@ def self_information(dup):
|
|
|
167
172
|
return -math.log(p, 2.0)
|
|
168
173
|
|
|
169
174
|
|
|
175
|
+
# 生效条件:hint 非 None 且可转 float(含 hint=0)时返回 from="hint" 的裁剪分数;否则用 novelty、SOURCE_WEIGHT.get(kind, SOURCE_WEIGHT["unknown"])、len(content)/200 三因子启发式。
|
|
170
176
|
def importance_score(hint, novelty, kind, content):
|
|
171
177
|
"""Q2 重要?——显式 hint 优先,否则启发式(对齐 longterm_snapshot 四因子简化版)。"""
|
|
172
178
|
if hint is not None:
|
|
@@ -182,6 +188,7 @@ def importance_score(hint, novelty, kind, content):
|
|
|
182
188
|
return {"score": round(max(0.0, min(1.0, s)), 4), "from": "heuristic"}
|
|
183
189
|
|
|
184
190
|
|
|
191
|
+
# 生效条件:以 source_kind(role,verification_basis) 的 kind 与 redundancy(cg,content,layer=layer,exclude=node_id) 的 red["max"] 为输入,按 if/elif 顺序取首个命中分支——imp["score"]≥PROTECT_IMPORTANCE→"ACCEPT";否则 kind=="internal_deterministic" 且 red["max"]≥DUP_DROP→"DROP";否则 red["max"]≥DUP_MERGE→"MERGE";否则 red["max"]≥DUP_DROP 且 imp["score"]<IMPORTANCE_MIN→"DEFER";否则 imp["score"]≥IMPORTANCE_MIN→"ACCEPT";否则 novelty≥NOVELTY_MIN→"ACCEPT";否则→"DEFER"。
|
|
185
192
|
def assess(cg, content, layer="contextual", role=None, verification_basis=None,
|
|
186
193
|
importance_hint=None, node_id=None):
|
|
187
194
|
"""三问 → 四态裁决。返回完整判据(可审计,不只给结论)。"""
|
|
@@ -226,6 +233,7 @@ def assess(cg, content, layer="contextual", role=None, verification_basis=None,
|
|
|
226
233
|
|
|
227
234
|
# ---------------------------------------------------------------- 落库动作
|
|
228
235
|
|
|
236
|
+
# 生效条件:cg 与 rec 必填;append_jsonl 写 cg.root/LOG_FILE 抛任意异常时被吞掉,仍返回 rec。
|
|
229
237
|
def log(cg, rec):
|
|
230
238
|
"""裁决留痕(append-only)。DROP/DEFER 也留痕——否则遗忘变黑箱。"""
|
|
231
239
|
try:
|
|
@@ -235,6 +243,7 @@ def log(cg, rec):
|
|
|
235
243
|
return rec
|
|
236
244
|
|
|
237
245
|
|
|
246
|
+
# 生效条件:cg 与 node_id 必填,delta 默认 0.05;cg.get(node_id) 抛异常或返回假值时返回 None;imp 跨过 PROTECT_IMPORTANCE 即写 protected。
|
|
238
247
|
def reinforce(cg, node_id, delta=0.05):
|
|
239
248
|
"""MERGE 的落库动作:不新增节点,把「又一次见到」折算成既有节点的强化。
|
|
240
249
|
|
|
@@ -276,6 +285,7 @@ def reinforce(cg, node_id, delta=0.05):
|
|
|
276
285
|
"merge_count": fm["merge_count"], "protected": bool(fm.get("protected"))}
|
|
277
286
|
|
|
278
287
|
|
|
288
|
+
# 生效条件:cg 必填,limit 默认 100;日志路径不存在时返回 [];否则返回 out[-limit:],limit=0 时 -0 退化为 out[0:] 即全量。
|
|
279
289
|
def history(cg, limit=100):
|
|
280
290
|
"""读取遗忘留痕(最近 limit 条)。"""
|
|
281
291
|
p = os.path.join(cg.root, LOG_FILE)
|
|
@@ -296,6 +306,7 @@ def history(cg, limit=100):
|
|
|
296
306
|
return out[-limit:]
|
|
297
307
|
|
|
298
308
|
|
|
309
|
+
# 生效条件:cg 必填;日志路径不存在返回 {"total": 0, "by_verdict": {}};否则流式累计行数与 verdict 分布。
|
|
299
310
|
def summary(cg):
|
|
300
311
|
"""遗忘留痕聚合(流式,不把全量日志读进内存):总数 + 四态分布。"""
|
|
301
312
|
p = os.path.join(cg.root, LOG_FILE)
|
|
@@ -339,6 +350,7 @@ VERIFIED_BASES = ("formal_proof", "compiler", "test", "textbook", "public_kb")
|
|
|
339
350
|
TIER_WORKING = 0.40
|
|
340
351
|
|
|
341
352
|
|
|
353
|
+
# 生效条件:e 必填;protected 为真、importance>=PROTECT_IMPORTANCE、或 evidence_count>=3 且 verification_basis 在 VERIFIED_BASES → "longterm";importance>=TIER_WORKING 或 vb 在 VERIFIED_BASES → "working";否则 "candidate"。
|
|
342
354
|
def _tier_of(e: dict) -> str:
|
|
343
355
|
"""索引快照 → 分层:longterm(长期)/ working(工作)/ candidate(候选待评估)。"""
|
|
344
356
|
imp = float(e.get("importance", 0.5) or 0.5)
|
|
@@ -351,19 +363,23 @@ def _tier_of(e: dict) -> str:
|
|
|
351
363
|
return "candidate"
|
|
352
364
|
|
|
353
365
|
|
|
366
|
+
# 生效条件:e 必填;e["edges"] 为假值(缺失/空列表)且 e["subgraph"] 为假值时返回 True,否则 False。
|
|
354
367
|
def _is_island(e: dict) -> bool:
|
|
355
368
|
"""无边孤岛:既无出边也无子图声明(夜间整理的首要候选)。"""
|
|
356
369
|
return (not (e.get("edges") or [])) and (not e.get("subgraph"))
|
|
357
370
|
|
|
358
371
|
|
|
372
|
+
# 生效条件:cg 必填且提供 cg.root,恒返回 os.path.join(cg.root, LONGTERM_DIR)。
|
|
359
373
|
def longterm_dir(cg) -> str:
|
|
360
374
|
return os.path.join(cg.root, LONGTERM_DIR)
|
|
361
375
|
|
|
362
376
|
|
|
377
|
+
# 生效条件:cg 必填,恒返回 longterm_dir(cg) 下的 "current.json" 路径。
|
|
363
378
|
def current_path(cg) -> str:
|
|
364
379
|
return os.path.join(longterm_dir(cg), "current.json")
|
|
365
380
|
|
|
366
381
|
|
|
382
|
+
# 生效条件:apply 为真且由 cg.index 的 nodes(layer 为假值时不过滤、为真时仅取 layer 字段相等者,max_rows 为真值时先取 ids[:int(max_rows)])算出的 snapshot_id 与 current.json 所记 snapshot_id 不同或其记录的 path 文件不存在(same 为假)时,才写断面文件、原子更新 current 指针、执行 _prune 并追加维护日志;apply 为假时只返回 dry_run=True 的统计(out 与 force 在源码中未被引用)。
|
|
367
383
|
def longterm_assess(cg, apply=False, out=None, layer=None, keep=LONGTERM_KEEP,
|
|
368
384
|
max_rows=None, force=False, actor="maintain"):
|
|
369
385
|
"""评估后分层落盘:生成一个可回溯的长期记忆断面。
|
|
@@ -440,6 +456,7 @@ def longterm_assess(cg, apply=False, out=None, layer=None, keep=LONGTERM_KEEP,
|
|
|
440
456
|
}
|
|
441
457
|
|
|
442
458
|
|
|
459
|
+
# 生效条件:cg 与 keep 必填;keep<=0 时不删除任何断面返回 [];否则删除除最近 keep 个 .jsonl 外的旧断面。
|
|
443
460
|
def _prune(cg, keep):
|
|
444
461
|
"""只保留最近 keep 个断面文件(按文件名时间前缀排序)。"""
|
|
445
462
|
d = longterm_dir(cg)
|
|
@@ -457,6 +474,7 @@ def _prune(cg, keep):
|
|
|
457
474
|
return removed
|
|
458
475
|
|
|
459
476
|
|
|
477
|
+
# 生效条件:cg 必填,limit 默认 20;目录不可读返回 [];否则新的在前逐个 append,因先 append 后判 len(out)>=limit,limit=0 时仍返回 1 条快照。
|
|
460
478
|
def longterm_list(cg, limit=20):
|
|
461
479
|
"""列出历史断面(新的在前):{snapshot_id, path, ts, total, tiers}。"""
|
|
462
480
|
d = longterm_dir(cg)
|
|
@@ -481,6 +499,7 @@ def longterm_list(cg, limit=20):
|
|
|
481
499
|
return {"current": cur, "snapshots": out}
|
|
482
500
|
|
|
483
501
|
|
|
502
|
+
# 生效条件:longterm_dir(cg) 不可列出(OSError)时返回 {"ok":False,"error":"no_snapshot"};否则在倒序文件名中取首个满足 snapshot_id 为 None 或为其子串的 .jsonl(snapshot_id="" 与任意文件名匹配),无匹配返回 {"ok":False,"error":"snapshot_not_found"};命中则逐行聚合该文件(空行与 json.loads 抛 ValueError 的行跳过),返回 file/total/tiers/by_layer/islands。
|
|
484
503
|
def longterm_show(cg, snapshot_id=None):
|
|
485
504
|
"""读取某个断面的分层统计(不载全量行,只聚合)。"""
|
|
486
505
|
d = longterm_dir(cg)
|
|
@@ -524,6 +543,7 @@ def longterm_show(cg, snapshot_id=None):
|
|
|
524
543
|
# 写入**之前**的新奇检测:重复项并入既有(MERGE),而非新增;无关噪音丢弃;
|
|
525
544
|
# 有歧义的半重复留痕待复核。这是「写入侧前置」的落库动作,比夜间整理更早一步。
|
|
526
545
|
|
|
546
|
+
# 生效条件:cg 与 content 必填;恒经 assess 得四态并映射 decision(ACCEPT→write 等),落留痕后返回 ok=True,不写任何节点。
|
|
527
547
|
def prefeed(cg, content, layer="contextual", role=None, verification_basis=None,
|
|
528
548
|
importance_hint=None, node_id=None):
|
|
529
549
|
"""前馈裁决(不写盘):返回四态 + 判据,并留痕 `_forgetting.jsonl`。
|
|
@@ -548,13 +568,15 @@ def prefeed(cg, content, layer="contextual", role=None, verification_basis=None,
|
|
|
548
568
|
return {"ok": True, "action": "prefeed", **rec}
|
|
549
569
|
|
|
550
570
|
|
|
571
|
+
# 生效条件:content 为 None 或假值时按 "" 计算,恒返回 "pre_"+sha1(content).hexdigest()[:12]。
|
|
551
572
|
def _prefeed_id(content):
|
|
552
573
|
return "pre_" + hashlib.sha1((content or "").encode("utf-8")).hexdigest()[:12]
|
|
553
574
|
|
|
554
575
|
|
|
576
|
+
# 生效条件:cg 必填,limit 默认 100;action 为假值(None/空串)时不过滤,真值只留该 action;返回 recs[-int(limit):],limit=0 时退化为全量。
|
|
555
577
|
def maintain_history(cg, limit=100, action=None):
|
|
556
578
|
"""维护留痕(`_maintain.jsonl` 最近 limit 条),可按 action 过滤。"""
|
|
557
579
|
recs = list(read_jsonl(os.path.join(cg.root, MAINTAIN_LOG)))
|
|
558
580
|
if action:
|
|
559
581
|
recs = [r for r in recs if r.get("action") == action]
|
|
560
|
-
return recs[-int(limit):]
|
|
582
|
+
return recs[-int(limit):]
|
package/md_cg/fsutil.py
CHANGED
|
@@ -28,6 +28,7 @@ _RENAME_TRIES = 20
|
|
|
28
28
|
_RENAME_WAIT = 0.005
|
|
29
29
|
|
|
30
30
|
|
|
31
|
+
# 生效条件:tmp 与 path 给定后循环至多 _RENAME_TRIES 次调用 os.replace(tmp, path),成功即返回;仅捕获 PermissionError,非最后一次则 time.sleep(_RENAME_WAIT) 重试,最后一次仍 PermissionError 则抛出。
|
|
31
32
|
def _publish(tmp: str, path: str):
|
|
32
33
|
"""把临时文件 rename 到位,Windows 上短重试。"""
|
|
33
34
|
for i in range(_RENAME_TRIES):
|
|
@@ -40,6 +41,7 @@ def _publish(tmp: str, path: str):
|
|
|
40
41
|
time.sleep(_RENAME_WAIT)
|
|
41
42
|
|
|
42
43
|
|
|
44
|
+
# 生效条件:path 与 data 给定时取 path 所在目录 d 建目录,用 tempfile.mkstemp 在 d 内建临时文件按 encoding 写入 data,durable 为真才 flush+os.fsync(假值不 fsync),再经 _publish(tmp, path) 替换;任一步失败时 finally 里若 tmp 仍非 None 且 os.path.exists(tmp) 为真则 os.remove(OSError 忽略)。
|
|
43
45
|
def atomic_write(path: str, data: str, encoding: str = "utf-8", durable: bool = False):
|
|
44
46
|
"""整文件替换。临时文件与目标同目录(保证同一文件系统,rename 才原子),
|
|
45
47
|
临时名唯一(并发写者不共享),失败即清理而不是留在可能刚写满的磁盘上。
|
|
@@ -71,6 +73,7 @@ def atomic_write(path: str, data: str, encoding: str = "utf-8", durable: bool =
|
|
|
71
73
|
pass
|
|
72
74
|
|
|
73
75
|
|
|
76
|
+
# 生效条件:os.path.isdir(d) 为假时直接返回;否则对 d 下名字以 "." 开头且含 ".tmp-" 的条目,当 now - os.path.getmtime(p) > older_than 时 os.remove(p)(OSError 忽略),其余条目不动。
|
|
74
77
|
def sweep_stale_temps(d: str, older_than: float = 3600):
|
|
75
78
|
"""清理被杀死的进程留下的唯一命名临时文件(它们不会被下一个写者复用清掉)。"""
|
|
76
79
|
if not os.path.isdir(d):
|
|
@@ -87,6 +90,7 @@ def sweep_stale_temps(d: str, older_than: float = 3600):
|
|
|
87
90
|
pass
|
|
88
91
|
|
|
89
92
|
|
|
93
|
+
# 生效条件:path 加 ".lock" 后缀作为锁文件,进入时按 timeout 秒内以 poll 间隔轮询获取 OS 级排它锁(IS_WIN 用 msvcrt.locking 锁首字节,否则 fcntl.flock),超时仍未获锁时 strict 为真抛 TimeoutError、否则返回自身放行。
|
|
90
94
|
class FileLock:
|
|
91
95
|
"""跨进程排它锁(OS 级)。
|
|
92
96
|
|
|
@@ -102,6 +106,7 @@ class FileLock:
|
|
|
102
106
|
好过静默放行后退化为无锁并发(丢一条提案/裁决比让写入者等一下代价大)。
|
|
103
107
|
"""
|
|
104
108
|
|
|
109
|
+
# 生效条件:path 加 ".lock" 后缀存入 self.path,timeout、poll、strict 原样保存,并置 self._f = None、self.acquired = False。
|
|
105
110
|
def __init__(self, path: str, timeout: float = 10.0, poll: float = 0.01,
|
|
106
111
|
strict: bool = False):
|
|
107
112
|
self.path = path + ".lock"
|
|
@@ -111,6 +116,7 @@ class FileLock:
|
|
|
111
116
|
self._f = None
|
|
112
117
|
self.acquired = False
|
|
113
118
|
|
|
119
|
+
# 生效条件:先按 self.path 建父目录并 open(self.path, "a+b"),再在 self.timeout 到期前每 self.poll 秒尝试加锁(IS_WIN 用 msvcrt.locking(LK_NBLCK),否则 fcntl.flock(LOCK_EX|LOCK_NB));成功即置 self.acquired=True 并返回 self;OSError 的 errno 不在 (EACCES, EAGAIN, EDEADLK) 时直接 raise,超时后 self.strict 为真抛 TimeoutError、否则返回 self 放行。
|
|
114
120
|
def __enter__(self):
|
|
115
121
|
os.makedirs(os.path.dirname(os.path.abspath(self.path)) or ".", exist_ok=True)
|
|
116
122
|
self._f = open(self.path, "a+b")
|
|
@@ -134,6 +140,7 @@ class FileLock:
|
|
|
134
140
|
return self # 放行,不阻断写路径
|
|
135
141
|
time.sleep(self.poll)
|
|
136
142
|
|
|
143
|
+
# 生效条件:self.acquired 为真时按 IS_WIN 用 msvcrt.locking(LK_UNLCK) 或 fcntl.flock(LOCK_UN) 解锁(OSError 被吞掉);finally 中只要 self._f 为真就 close,随后 self._f=None、self.acquired=False。
|
|
137
144
|
def __exit__(self, *exc):
|
|
138
145
|
try:
|
|
139
146
|
if self.acquired:
|
|
@@ -151,6 +158,7 @@ class FileLock:
|
|
|
151
158
|
self.acquired = False
|
|
152
159
|
|
|
153
160
|
|
|
161
|
+
# 生效条件:os.path.getsize(path) 为 0 时返回 False;否则二进制打开 path 并从 size-1 处读 1 字节,返回 f.read(1) != b"\n";getsize/open/seek/read 抛 OSError 时返回 False。
|
|
154
162
|
def ends_mid_line(path: str) -> bool:
|
|
155
163
|
"""行式日志的最后一字节是否不是换行——即上一个写者被杀死留下的半截记录。
|
|
156
164
|
追加者若不先补一个换行,新记录会粘在这行上,两条都解析不出来。"""
|
|
@@ -165,6 +173,7 @@ def ends_mid_line(path: str) -> bool:
|
|
|
165
173
|
return False
|
|
166
174
|
|
|
167
175
|
|
|
176
|
+
# 生效条件:record 序列化为 json.dumps(record, ensure_ascii=False, separators=(",", ":")) + "\n";若 ends_mid_line(path) 为真则在行首再补一个 "\n";随后建父目录并以 O_CREAT|O_WRONLY|O_APPEND、权限 0o600 打开 path 写入该行 UTF-8 字节后关闭。
|
|
168
177
|
def append_jsonl(path: str, record: dict):
|
|
169
178
|
"""向 append-only 日志追加一条记录(best-effort 语义)。
|
|
170
179
|
|
|
@@ -187,6 +196,7 @@ def append_jsonl(path: str, record: dict):
|
|
|
187
196
|
os.close(fd)
|
|
188
197
|
|
|
189
198
|
|
|
199
|
+
# 生效条件:os.path.exists(path) 为假时生成器直接结束不产出;否则逐行 strip,空行跳过,json.loads 成功则 yield 该对象,抛 ValueError 的行跳过,其余异常不捕获。
|
|
190
200
|
def read_jsonl(path: str):
|
|
191
201
|
"""读 append-only 日志,跳过被截断/粘连的坏行(不因自身簿记而失败)。"""
|
|
192
202
|
if not os.path.exists(path):
|
|
@@ -205,6 +215,7 @@ def read_jsonl(path: str):
|
|
|
205
215
|
_COUNT_CACHE = {} # abspath -> (bytes_scanned, mtime_ns, lines)
|
|
206
216
|
|
|
207
217
|
|
|
218
|
+
# 生效条件:os.stat(os.path.abspath(path or "")) 抛 OSError 时返回 0;缓存命中且已扫字节数与 mtime_ns 均与 stat 一致时直接返回缓存计数;若缓存已扫字节 < 当前 size 且 mtime_ns 不同则从该偏移起按 chunk 分块累计 b"\n" 个数并加上缓存值;读文件抛 OSError 时返回 total or 0(已累计值为假则返回 0)。
|
|
208
219
|
def count_jsonl(path: str, chunk: int = 1 << 20) -> int:
|
|
209
220
|
"""数 append-only 日志的行数——**流式计数、不物化**(内存 O(1))。
|
|
210
221
|
|
|
@@ -255,6 +266,7 @@ def count_jsonl(path: str, chunk: int = 1 << 20) -> int:
|
|
|
255
266
|
return total
|
|
256
267
|
|
|
257
268
|
|
|
269
|
+
# 生效条件:directory 经 abspath 后作为分片目录并 makedirs(exist_ok=True),实例分片文件名由 os.getpid() 与 uuid.uuid4().hex[:8] 拼成 "{pid}-{hex8}.log",append 时各写者只写自己这一分片,从而不共享写入点。
|
|
258
270
|
class ShardedLog:
|
|
259
271
|
"""每写者独占一个分片的 append-only 日志——不能丢记录时用它。
|
|
260
272
|
|
|
@@ -263,6 +275,7 @@ class ShardedLog:
|
|
|
263
275
|
代价是读取要合并 N 个分片,靠记录里的单调序号 (t, seq) 恢复全局写入顺序。
|
|
264
276
|
"""
|
|
265
277
|
|
|
278
|
+
# 生效条件:directory 经 abspath 存入 self.dir 并 makedirs(exist_ok=True),self.path 为 self.dir 下 "{os.getpid()}-{uuid.uuid4().hex[:8]}.log",并置 self._seq = 0、self._fh = None。
|
|
266
279
|
def __init__(self, directory: str):
|
|
267
280
|
self.dir = os.path.abspath(directory)
|
|
268
281
|
os.makedirs(self.dir, exist_ok=True)
|
|
@@ -271,6 +284,7 @@ class ShardedLog:
|
|
|
271
284
|
self._seq = 0
|
|
272
285
|
self._fh = None
|
|
273
286
|
|
|
287
|
+
# 生效条件:self._seq 先自增 1,record 被 dict(record, _t=time.time(), _s=self._seq) 复制;self._fh 为 None 时以 "a"、encoding="utf-8"、newline="\n" 打开 self.path,随后写入 json.dumps(ensure_ascii=False, separators=(",", ":")) + "\n" 并 flush。
|
|
274
288
|
def append(self, record: dict):
|
|
275
289
|
self._seq += 1
|
|
276
290
|
record = dict(record, _t=time.time(), _s=self._seq)
|
|
@@ -280,12 +294,14 @@ class ShardedLog:
|
|
|
280
294
|
separators=(",", ":")) + "\n")
|
|
281
295
|
self._fh.flush()
|
|
282
296
|
|
|
297
|
+
# 生效条件:幂等;self._fh 为真值时 flush 并关闭句柄、再把 self._fh 置 None,为 None 时直接返回不报错;
|
|
283
298
|
def close(self):
|
|
284
299
|
if self._fh:
|
|
285
300
|
self._fh.close()
|
|
286
301
|
self._fh = None
|
|
287
302
|
|
|
288
303
|
@staticmethod
|
|
304
|
+
# 生效条件:directory 是目录时,按 sorted(os.listdir(directory)) 顺序对每个以 ".log" 结尾的文件调用 read_jsonl 汇总记录,再按每条记录 r.get("_t", 0)、r.get("_s", 0)(缺键取 0)排序后返回全部记录;directory 不是目录时直接返回 []。
|
|
289
305
|
def read_all(directory: str):
|
|
290
306
|
"""按全局写入顺序回放所有分片。"""
|
|
291
307
|
if not os.path.isdir(directory):
|
|
@@ -299,6 +315,7 @@ class ShardedLog:
|
|
|
299
315
|
return recs
|
|
300
316
|
|
|
301
317
|
@staticmethod
|
|
318
|
+
# 生效条件:directory 是目录时,遍历 os.listdir(directory),对以 ".log" 结尾且不满足「keep 为真值且 os.path.abspath(p) == keep」的条目调用 os.remove(keep 为 None/空串等假值时该排除条件恒不成立,所有 ".log" 条目都会被删),删除时的 OSError 被忽略;directory 不是目录时直接返回。
|
|
302
319
|
def clear(directory: str, keep: str = None):
|
|
303
320
|
"""合并进快照后清理分片。keep 用于保留当前进程正在写的那个。"""
|
|
304
321
|
if not os.path.isdir(directory):
|
|
@@ -310,4 +327,4 @@ class ShardedLog:
|
|
|
310
327
|
try:
|
|
311
328
|
os.remove(p)
|
|
312
329
|
except OSError:
|
|
313
|
-
pass
|
|
330
|
+
pass
|