@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/sustain.py
CHANGED
|
@@ -80,16 +80,19 @@ _POLL = 0.2 # 循环轮询步长(常驻进程 CPU 可忽
|
|
|
80
80
|
# 心跳
|
|
81
81
|
# --------------------------------------------------------------------------
|
|
82
82
|
|
|
83
|
+
# 生效条件:d 为真值时返回 d,d 为 None/空串时回落 MDCG_SUSTAIN_DIR,该环境变量也未设或为空串时返回 os.path.join(os.path.expanduser("~"), ".mdcg", "sustain");
|
|
83
84
|
def net_dir(d: str = None) -> str:
|
|
84
85
|
"""心跳戳目录:显式 → MDCG_SUSTAIN_DIR → ~/.mdcg/sustain(仓库外)。"""
|
|
85
86
|
return (d or os.environ.get("MDCG_SUSTAIN_DIR")
|
|
86
87
|
or os.path.join(os.path.expanduser("~"), ".mdcg", "sustain"))
|
|
87
88
|
|
|
88
89
|
|
|
90
|
+
# 生效条件:给定必填 name,返回 net_dir(d) 下 heartbeat.<name>.stamp 的拼接路径。
|
|
89
91
|
def stamp_path(name: str, d: str = None) -> str:
|
|
90
92
|
return os.path.join(net_dir(d), f"heartbeat.{name}.stamp")
|
|
91
93
|
|
|
92
94
|
|
|
95
|
+
# 生效条件:name 必填,组装含模块常量 STAMP_VERSION 与 task_running 的戳记录、原子写入 stamp_path(name,d) 后返回该 rec。
|
|
93
96
|
def write_stamp(name: str, d: str = None, **extra) -> dict:
|
|
94
97
|
"""写一次心跳(原子替换)。extra 会并入戳内容(None 值丢弃)。"""
|
|
95
98
|
rec = {"v": STAMP_VERSION, "name": name, "ts": time.time(),
|
|
@@ -104,6 +107,7 @@ def write_stamp(name: str, d: str = None, **extra) -> dict:
|
|
|
104
107
|
return rec
|
|
105
108
|
|
|
106
109
|
|
|
110
|
+
# 生效条件:stamp_path(name, d) 所得路径上 os.path.exists 为真且 json.load 结果为 dict 时,返回该 rec 并附加 age=max(0.0, time.time() - float(rec.get("ts") or 0));该路径不可读、json.load 抛 ValueError/OSError 或 rec 非 dict 时返回 None;
|
|
107
111
|
def read_stamp(name: str, d: str = None):
|
|
108
112
|
"""读心跳戳并附 age(秒);不存在 / 损坏 → None。"""
|
|
109
113
|
p = stamp_path(name, d)
|
|
@@ -120,6 +124,7 @@ def read_stamp(name: str, d: str = None):
|
|
|
120
124
|
return rec
|
|
121
125
|
|
|
122
126
|
|
|
127
|
+
# 生效条件:对 stamp_path(name,d) 执行 os.remove,OSError 被静默忽略,无返回值。
|
|
123
128
|
def clear_stamp(name: str, d: str = None):
|
|
124
129
|
try:
|
|
125
130
|
os.remove(stamp_path(name, d))
|
|
@@ -127,6 +132,7 @@ def clear_stamp(name: str, d: str = None):
|
|
|
127
132
|
pass
|
|
128
133
|
|
|
129
134
|
|
|
135
|
+
# 生效条件:age 为 None 返回 'absent';否则以 factor=working if task_running else 1.0 比较 age 与 interval*warn*factor、interval*dead*factor,分别返回 'ok'/'warning'/'dead'。
|
|
130
136
|
def judge(age, *, interval: float = DEFAULT_BEAT_INTERVAL,
|
|
131
137
|
task_running: bool = False, warn: float = DEFAULT_WARN_FACTOR,
|
|
132
138
|
dead: float = DEFAULT_DEAD_FACTOR,
|
|
@@ -146,6 +152,7 @@ def judge(age, *, interval: float = DEFAULT_BEAT_INTERVAL,
|
|
|
146
152
|
return "dead"
|
|
147
153
|
|
|
148
154
|
|
|
155
|
+
# 生效条件:net_dir(d) 可列为目录时遍历 heartbeat.*.stamp 并返回含 age 与 state 的记录列表,否则返回 []。
|
|
149
156
|
def peers(d: str = None):
|
|
150
157
|
"""列出本机所有心跳戳(含状态)。"""
|
|
151
158
|
base = net_dir(d)
|
|
@@ -171,6 +178,7 @@ DEFAULT_RESTART_COOLDOWN = 300.0 # 同一对端两次拉起的最小间隔(
|
|
|
171
178
|
DEFAULT_FRESH_TIMEOUT = 60.0 # 拉起后等待对端戳变新鲜的窗口
|
|
172
179
|
|
|
173
180
|
|
|
181
|
+
# 生效条件:pid 可转 int 且 >0 时按探活(os.name=='nt' 用 OpenProcess+WaitForSingleObject,否则 os.kill(pid,0))返回 True,转 int 失败或 pid<=0 或探活失败返回 False。
|
|
174
182
|
def pid_alive(pid) -> bool:
|
|
175
183
|
"""进程探活(零第三方依赖):Windows=OpenProcess+WaitForSingleObject;
|
|
176
184
|
posix=os.kill(pid,0)。pid 无效 / 已退出 / 权限外 → False(诚实)。"""
|
|
@@ -199,6 +207,7 @@ def pid_alive(pid) -> bool:
|
|
|
199
207
|
return False
|
|
200
208
|
|
|
201
209
|
|
|
210
|
+
# 生效条件:cmd 为 argv 列表时以 stdout/stderr=DEVNULL、不经 shell 的 subprocess.Popen 拉起(nt 附加 DETACHED|NEW_GROUP creationflags),返回该 Popen 对象。
|
|
202
211
|
def _default_spawner(cmd):
|
|
203
212
|
"""分离式拉起:argv 列表、不经 shell、输出弃置(Windows 完全脱离父控制台)。"""
|
|
204
213
|
import subprocess
|
|
@@ -209,6 +218,7 @@ def _default_spawner(cmd):
|
|
|
209
218
|
stderr=subprocess.DEVNULL, **kw)
|
|
210
219
|
|
|
211
220
|
|
|
221
|
+
# 生效条件:root 与 peer 必填,扫描 root/SUSTAIN_LOG(模块常量)返回 op='mutual'、action='restart' 且 detail 以 peer 开头的记录中最大 t,无匹配返回 0.0。
|
|
212
222
|
def _last_mutual_restart(root: str, peer: str) -> float:
|
|
213
223
|
"""_sustain.jsonl 里该对端最近一次互维拉起时间(无 → 0.0,审计即状态)。"""
|
|
214
224
|
last = 0.0
|
|
@@ -227,6 +237,7 @@ def _last_mutual_restart(root: str, peer: str) -> float:
|
|
|
227
237
|
return last
|
|
228
238
|
|
|
229
239
|
|
|
240
|
+
# 生效条件:cg 与 peer 必填,读对端戳判状态:ok/warning 直接返回 action='none';dead/absent 时依次按 pid_alive、restart_cooldown、restart_cmd 有无走 alive_but_stale/restart_cooldown/dead_unhandled/拉起并验戳,返回 out。
|
|
230
241
|
def mutual_watch(cg, peer: str, *, restart_cmd=None, d: str = None,
|
|
231
242
|
interval: float = DEFAULT_BEAT_INTERVAL,
|
|
232
243
|
restart_cooldown: float = DEFAULT_RESTART_COOLDOWN,
|
|
@@ -294,6 +305,7 @@ def mutual_watch(cg, peer: str, *, restart_cmd=None, d: str = None,
|
|
|
294
305
|
return out
|
|
295
306
|
|
|
296
307
|
|
|
308
|
+
# 生效条件:cg 与 peer 必填,分别按 name 与 peer 读戳判状态,仅当自身与对端状态均属 ('dead','absent') 时返回 ok=False、both_dead=True。
|
|
297
309
|
def mutual_status(cg, peer: str, *, d: str = None, name: str = "md_cg",
|
|
298
310
|
interval: float = DEFAULT_BEAT_INTERVAL) -> dict:
|
|
299
311
|
"""互维状态 + 双亡检测:自己也失联时互维不可信 → 显式外部告警语义。"""
|
|
@@ -316,6 +328,7 @@ def mutual_status(cg, peer: str, *, d: str = None, name: str = "md_cg",
|
|
|
316
328
|
# 诊断(只读)
|
|
317
329
|
# --------------------------------------------------------------------------
|
|
318
330
|
|
|
331
|
+
# 生效条件:root 必填,对模块常量 LAYERS 各层 os.walk 统计以 .md 结尾的文件名个数并返回 n(不解析正文)。
|
|
319
332
|
def _count_node_files(root: str) -> int:
|
|
320
333
|
"""轻量统计磁盘节点数(只数文件名,不解析正文)。"""
|
|
321
334
|
n = 0
|
|
@@ -325,6 +338,7 @@ def _count_node_files(root: str) -> int:
|
|
|
325
338
|
return n
|
|
326
339
|
|
|
327
340
|
|
|
341
|
+
# 生效条件:root 与 older_than 必填,返回 root 及其 LAYERS 各层下以 '.' 开头、名含 '.tmp-' 且 mtime 早于 now-older_than 的路径列表。
|
|
328
342
|
def _list_stale_temps(root: str, older_than: float):
|
|
329
343
|
now, out = time.time(), []
|
|
330
344
|
bases = [root] + [os.path.join(root, l) for l in LAYERS]
|
|
@@ -343,6 +357,7 @@ def _list_stale_temps(root: str, older_than: float):
|
|
|
343
357
|
return out
|
|
344
358
|
|
|
345
359
|
|
|
360
|
+
# 生效条件:root 必填,返回 root 顶层以 .jsonl/.log 结尾且 ends_mid_line 为真的路径列表;root 非目录返回 []。
|
|
346
361
|
def _half_line_logs(root: str):
|
|
347
362
|
"""顶层行式日志里末尾不是换行的(写者被杀留下的半截记录)。"""
|
|
348
363
|
out = []
|
|
@@ -357,6 +372,7 @@ def _half_line_logs(root: str):
|
|
|
357
372
|
return out
|
|
358
373
|
|
|
359
374
|
|
|
375
|
+
# 生效条件:root/_index_log 可列为目录时返回其中 .log 结尾的文件名列表,否则返回 []。
|
|
360
376
|
def _index_log_shards(root: str):
|
|
361
377
|
d = os.path.join(root, "_index_log")
|
|
362
378
|
if not os.path.isdir(d):
|
|
@@ -364,6 +380,7 @@ def _index_log_shards(root: str):
|
|
|
364
380
|
return [f for f in os.listdir(d) if f.endswith(".log")]
|
|
365
381
|
|
|
366
382
|
|
|
383
|
+
# 生效条件:cg 具备 crypto_status 且其返回 unlocked 为假时,返回 cg.index['nodes'] 中 sensitivity 属 crypto.ENCRYPTED_LEVELS 的条目数;属性缺失、调用异常或已解锁时返回 0。
|
|
367
384
|
def _locked_nodes(cg) -> int:
|
|
368
385
|
"""加密引擎在「未解锁」状态下被隔离的私有节点数(缺密钥 = 权限事实)。"""
|
|
369
386
|
if not hasattr(cg, "crypto_status"):
|
|
@@ -388,6 +405,7 @@ EVOLVE_FIXES = {"ccg_backlog": "consolidate_run",
|
|
|
388
405
|
"importance_drift": "importance"}
|
|
389
406
|
|
|
390
407
|
|
|
408
|
+
# 生效条件:nodes 与 top 必填,遍历索引条目统计缺 verification_basis 或 has_neg_conditions 的候补数 n、no_basis、no_neg 并取最多 top 个 sample,返回含 proxy=True 与 EVOLVE_FIXES['ccg_backlog'] 的字典。
|
|
391
409
|
def _ccg_backlog(nodes: dict, top: int) -> dict:
|
|
392
410
|
"""固化候补量(**索引代理指标**:零读节点文件、确定性、O(N))。
|
|
393
411
|
|
|
@@ -412,6 +430,7 @@ def _ccg_backlog(nodes: dict, top: int) -> dict:
|
|
|
412
430
|
"proxy": True, "fix": EVOLVE_FIXES["ccg_backlog"]}
|
|
413
431
|
|
|
414
432
|
|
|
433
|
+
# 生效条件:cg 必填,取 cg.index['nodes'](layer 非空时按 layer 过滤),经 weights.recalc(apply=False) 与 _ccg_backlog 汇总,返回 ok=True、readonly=True、dry_run=True 的候选盘点字典。
|
|
415
434
|
def evolution_candidates(cg, *, layer: str = None, top: int = 8,
|
|
416
435
|
min_delta: float = None) -> dict:
|
|
417
436
|
"""演化候选盘点(G7):**只读、零读节点文件、确定性、不写盘**。
|
|
@@ -446,6 +465,7 @@ def evolution_candidates(cg, *, layer: str = None, top: int = 8,
|
|
|
446
465
|
"重要性重算为确定性动作(有 rollback)")}
|
|
447
466
|
|
|
448
467
|
|
|
468
|
+
# 生效条件:给定 cg 后只读汇总(nodes 取自 cg.index、disk 计数、refindex.check_refs),stale_temp_age 传入 _list_stale_temps;check_heartbeat / check_evolution(evolve_top) / check_provenance(provenance_top) 为真时分别追加对应 issue,返回 ok = 无 severity=="warning" 的 issue 连同 stats。
|
|
449
469
|
def diagnose(cg, *, name: str = "md_cg", stale_temp_age: float = STALE_TEMP_AGE,
|
|
450
470
|
check_heartbeat: bool = True, check_evolution: bool = True,
|
|
451
471
|
evolve_top: int = 5, check_provenance: bool = True,
|
|
@@ -575,17 +595,20 @@ def diagnose(cg, *, name: str = "md_cg", stale_temp_age: float = STALE_TEMP_AGE,
|
|
|
575
595
|
# 自愈
|
|
576
596
|
# --------------------------------------------------------------------------
|
|
577
597
|
|
|
598
|
+
# 生效条件:path 必填,以 'ab' 模式向该路径追加写入单个换行 b'\n',无返回值。
|
|
578
599
|
def _seal_half_line(path: str):
|
|
579
600
|
with open(path, "ab") as f:
|
|
580
601
|
f.write(b"\n")
|
|
581
602
|
|
|
582
603
|
|
|
604
|
+
# 生效条件:root/op/action 必填,向 root/SUSTAIN_LOG(模块常量)追加一行含 t/op/action/detail(截断 200)/pid 的 JSONL,无返回值。
|
|
583
605
|
def _audit(root: str, op: str, action: str, detail: str = ""):
|
|
584
606
|
append_jsonl(os.path.join(root, SUSTAIN_LOG),
|
|
585
607
|
{"t": time.time(), "op": op, "action": action,
|
|
586
608
|
"detail": str(detail)[:200], "pid": os.getpid()})
|
|
587
609
|
|
|
588
610
|
|
|
611
|
+
# 生效条件:按 diagnose(cg, name=name, stale_temp_age=stale_temp_age) 的 issues code 集合分派——命中 index_drift/index_orphan 重建索引、ref_stale/ref_dangling 按 ref 重建源索引、index_log_backlog 合并索引分片、stale_temps 清理陈旧临时文件、half_line_logs 修补半截日志;ccg_backlog 仅 allow_evolve=True 且 reflect_fn 非 None 时才 consolidate(否则记 needs_llm),importance_drift 仅 allow_evolve=True 时才重算重要性(否则记 evolve_disabled);dry_run=True 时各动作只记入 actions 不落盘,返回含 after["ok"]、dry_run、actions、before/after 的 stats 与 t 的 dict;
|
|
589
612
|
def heal(cg, *, name: str = "md_cg", dry_run: bool = False,
|
|
590
613
|
stale_temp_age: float = STALE_TEMP_AGE,
|
|
591
614
|
allow_evolve: bool = False, reflect_fn=None, verify_fn=None) -> dict:
|
|
@@ -599,6 +622,7 @@ def heal(cg, *, name: str = "md_cg", dry_run: bool = False,
|
|
|
599
622
|
root = cg.root
|
|
600
623
|
actions = []
|
|
601
624
|
|
|
625
|
+
# 生效条件:闭包 dry_run 为真时向 actions 追加 {"code": code, "detail": detail, "applied": False} 并返回;否则调用 fn(),成功追加 applied=True/ok=True,抛异常时追加 applied=True/ok=False 与 error,最后执行 _audit(root, "heal", code, detail);
|
|
602
626
|
def act(code: str, detail: str, fn):
|
|
603
627
|
if dry_run:
|
|
604
628
|
actions.append({"code": code, "detail": detail, "applied": False})
|
|
@@ -624,6 +648,7 @@ def heal(cg, *, name: str = "md_cg", dry_run: bool = False,
|
|
|
624
648
|
if "stale_temps" in codes:
|
|
625
649
|
paths = _list_stale_temps(root, stale_temp_age)
|
|
626
650
|
|
|
651
|
+
# 生效条件:对闭包变量 paths 中每个路径尝试 os.remove(p),单个路径的 OSError 被吞掉后继续处理后续路径;
|
|
627
652
|
def _sweep():
|
|
628
653
|
for p in paths:
|
|
629
654
|
try:
|
|
@@ -643,6 +668,7 @@ def heal(cg, *, name: str = "md_cg", dry_run: bool = False,
|
|
|
643
668
|
if allow_evolve and reflect_fn is not None:
|
|
644
669
|
from . import consolidate as _cd
|
|
645
670
|
|
|
671
|
+
# 生效条件:调用时返回 _cd.consolidate(cg.root, apply=True, reflect_fn=reflect_fn, verify_fn=verify_fn);
|
|
646
672
|
def _consolidate():
|
|
647
673
|
return _cd.consolidate(cg.root, apply=True,
|
|
648
674
|
reflect_fn=reflect_fn,
|
|
@@ -655,6 +681,7 @@ def heal(cg, *, name: str = "md_cg", dry_run: bool = False,
|
|
|
655
681
|
if allow_evolve:
|
|
656
682
|
from . import weights
|
|
657
683
|
|
|
684
|
+
# 生效条件:调用时返回 weights.recalc(cg, apply=True, actor="sustain_evolve");
|
|
658
685
|
def _importance():
|
|
659
686
|
return weights.recalc(cg, apply=True, actor="sustain_evolve")
|
|
660
687
|
act("importance", "重算结构重要性(确定性;可 rollback)", _importance)
|
|
@@ -674,6 +701,7 @@ def heal(cg, *, name: str = "md_cg", dry_run: bool = False,
|
|
|
674
701
|
# 会话水位(重启续接)
|
|
675
702
|
# --------------------------------------------------------------------------
|
|
676
703
|
|
|
704
|
+
# 生效条件:以 root 实例化后账本固定指向 os.path.join(root, LEDGER_FILE),其 _load/_save/note/resume_point/sessions/summary 均以该路径为唯一读写对象;
|
|
677
705
|
class SessionLedger:
|
|
678
706
|
"""会话水位账本:<root>/_sessions.json。
|
|
679
707
|
|
|
@@ -681,10 +709,12 @@ class SessionLedger:
|
|
|
681
709
|
这里记「某个会话发生过什么」——重启后按会话给出续接点。
|
|
682
710
|
"""
|
|
683
711
|
|
|
712
|
+
# 生效条件:传入 root 时置 self.root=root 且 self.path=os.path.join(root, LEDGER_FILE),不做其他校验;
|
|
684
713
|
def __init__(self, root: str):
|
|
685
714
|
self.root = root
|
|
686
715
|
self.path = os.path.join(root, LEDGER_FILE)
|
|
687
716
|
|
|
717
|
+
# 生效条件:self.path(root/LEDGER_FILE)可读且 json 结果为 dict、且 d.get("sessions") 也是 dict 时返回该 d;self.path 不可读、json.load 抛 ValueError/OSError、或 d 非 dict / sessions 非 dict 时返回 {"schema": 1, "sessions": {}};
|
|
688
718
|
def _load(self):
|
|
689
719
|
if os.path.exists(self.path):
|
|
690
720
|
try:
|
|
@@ -696,9 +726,11 @@ class SessionLedger:
|
|
|
696
726
|
pass
|
|
697
727
|
return {"schema": 1, "sessions": {}}
|
|
698
728
|
|
|
729
|
+
# 生效条件:传入 d 时执行 atomic_write(self.path, json.dumps(d, ensure_ascii=False, indent=1)),自身无返回值;
|
|
699
730
|
def _save(self, d):
|
|
700
731
|
atomic_write(self.path, json.dumps(d, ensure_ascii=False, indent=1))
|
|
701
732
|
|
|
733
|
+
# 生效条件:以 session 为键 setdefault 会话记录后写 events=int(s.get("events") or 0)+int(n)(n 默认 1,n=0 时计数不变),t 非 None 时写 last_t 且 first_t 为空时一并写入,seq 非 None 时写 last_seq,actor 为真值时写 actor,最后写 updated_at 并 _save,返回 dict(s, session=session);
|
|
702
734
|
def note(self, session: str, *, t=None, seq=None, n: int = 1,
|
|
703
735
|
actor: str = None) -> dict:
|
|
704
736
|
"""记一笔会话活动(事件计数 + 最后 (t, seq) 水位)。"""
|
|
@@ -718,6 +750,7 @@ class SessionLedger:
|
|
|
718
750
|
self._save(d)
|
|
719
751
|
return dict(s, session=session)
|
|
720
752
|
|
|
753
|
+
# 生效条件:_load()["sessions"].get(session) 为假值(键缺失或值为空 dict)时返回 {"session": session, "resume": None, "events": 0};否则返回 {"session": session, "events": s.get("events", 0), "resume": {"t": s.get("last_t"), "seq": s.get("last_seq")}, "last_t": s.get("last_t"), "actor": s.get("actor")};
|
|
721
754
|
def resume_point(self, session: str) -> dict:
|
|
722
755
|
"""重启续接点:(t, seq) 之后的事件才是新的。"""
|
|
723
756
|
s = self._load()["sessions"].get(session)
|
|
@@ -727,9 +760,11 @@ class SessionLedger:
|
|
|
727
760
|
"resume": {"t": s.get("last_t"), "seq": s.get("last_seq")},
|
|
728
761
|
"last_t": s.get("last_t"), "actor": s.get("actor")}
|
|
729
762
|
|
|
763
|
+
# 生效条件:无参调用时返回 dict(self._load()["sessions"]) 的浅拷贝,账本缺失/损坏时 _load 回落默认值故此处为 {};
|
|
730
764
|
def sessions(self) -> dict:
|
|
731
765
|
return dict(self._load()["sessions"])
|
|
732
766
|
|
|
767
|
+
# 生效条件:取 d=_load()["sessions"] 后返回 {"sessions": len(d), "events": sum(int(v.get("events") or 0)), "latest": max(v.get("last_t") or 0) if d else None},d 为空时 latest 为 None;
|
|
733
768
|
def summary(self) -> dict:
|
|
734
769
|
d = self._load()["sessions"]
|
|
735
770
|
return {"sessions": len(d),
|
|
@@ -738,6 +773,7 @@ class SessionLedger:
|
|
|
738
773
|
if d else None}
|
|
739
774
|
|
|
740
775
|
|
|
776
|
+
# 生效条件:os.path.exists(os.path.join(cg.root, "_sources.json")) 为真且 json.load 成功时返回 dict((d or {}).get("sources") or {})(d 或 sources 为假值即回落 {});该路径不可读或 json.load 抛 ValueError/OSError 时返回 {};
|
|
741
777
|
def watermarks(cg) -> dict:
|
|
742
778
|
"""源视角水位快照(读 sources.Ingestor 的 _sources.json)。"""
|
|
743
779
|
p = os.path.join(cg.root, "_sources.json")
|
|
@@ -755,6 +791,7 @@ def watermarks(cg) -> dict:
|
|
|
755
791
|
# 常驻循环
|
|
756
792
|
# --------------------------------------------------------------------------
|
|
757
793
|
|
|
794
|
+
# 生效条件:传入 cg 即构造实例并把 self.cg 指向它,name/beat_interval/heal_interval/auto_heal/scrub_interval/auto_scrub/evolve_interval/auto_evolve/tidy_interval/auto_tidy 用各默认值(DEFAULT_* 与 False/True)经 float()/bool() 落为 self 属性,ledger 为假值(默认 None)时回落 SessionLedger(cg.root),d 经 net_dir(d) 赋值,其余运行态字段初始化为 False/None/空列表/空 Event/Lock
|
|
758
795
|
class SustainLoop:
|
|
759
796
|
"""常驻自维持循环:后台线程周期心跳 + 周期巡检 + 必要时自愈。
|
|
760
797
|
|
|
@@ -762,6 +799,7 @@ class SustainLoop:
|
|
|
762
799
|
让对端立刻看到「正常下线」而不是「失联」。
|
|
763
800
|
"""
|
|
764
801
|
|
|
802
|
+
# 生效条件:传入 cg 时按 name 与各 DEFAULT_* 默认值初始化——self.d=net_dir(d)(d 假值时回落 MDCG_SUSTAIN_DIR/~/ .mdcg/sustain)、self.ledger=ledger or SessionLedger(cg.root)(ledger 假值时新建),beat/heal/scrub/evolve/tidy 间隔 float() 化、auto_heal/auto_scrub/auto_evolve/auto_tidy bool() 化后存为实例属性;
|
|
765
803
|
def __init__(self, cg, name: str = "md_cg", *,
|
|
766
804
|
beat_interval: float = DEFAULT_BEAT_INTERVAL,
|
|
767
805
|
heal_interval: float = DEFAULT_HEAL_INTERVAL,
|
|
@@ -806,6 +844,7 @@ class SustainLoop:
|
|
|
806
844
|
|
|
807
845
|
# ---- 心跳 ----
|
|
808
846
|
|
|
847
|
+
# 生效条件:task_running 非 None 时先置 self.task_running=bool(task_running),再 write_stamp(self.name, self.d, task_running=self.task_running, root=self.cg.root, uptime=... if self._started_at else 0.0),beats 自增并记 last_beat=rec["ts"],返回该 rec;
|
|
809
848
|
def beat(self, task_running: bool = None) -> dict:
|
|
810
849
|
if task_running is not None:
|
|
811
850
|
self.task_running = bool(task_running)
|
|
@@ -819,6 +858,7 @@ class SustainLoop:
|
|
|
819
858
|
|
|
820
859
|
# ---- 生命周期 ----
|
|
821
860
|
|
|
861
|
+
# 生效条件:不适用(无必需形参与模块级常量)
|
|
822
862
|
def start(self):
|
|
823
863
|
if self._th is not None and self._th.is_alive():
|
|
824
864
|
return self
|
|
@@ -830,6 +870,7 @@ class SustainLoop:
|
|
|
830
870
|
self._th.start()
|
|
831
871
|
return self
|
|
832
872
|
|
|
873
|
+
# 生效条件:调用时置 _stop 事件,self._th 非 None 时 join(timeout)(默认 3.0)后置为 None,随后 clear_stamp(self.name, self.d),返回 self;
|
|
833
874
|
def stop(self, timeout: float = 3.0):
|
|
834
875
|
self._stop.set()
|
|
835
876
|
if self._th is not None:
|
|
@@ -838,6 +879,7 @@ class SustainLoop:
|
|
|
838
879
|
clear_stamp(self.name, self.d)
|
|
839
880
|
return self
|
|
840
881
|
|
|
882
|
+
# 生效条件:self._stop 未置位期间轮询,按 beat_interval/heal_interval/scrub_interval/evolve_interval/tidy_interval 到期分别执行 beat 与 _tick_heal/_tick_scrub/_tick_evolve/_tick_tidy,各 tick 抛出的异常被吞掉不中断循环,末尾以 _stop.wait(_POLL) 休眠;
|
|
841
883
|
def _run(self):
|
|
842
884
|
next_beat = time.time() + self.beat_interval
|
|
843
885
|
next_heal = time.time() + self.heal_interval
|
|
@@ -878,6 +920,7 @@ class SustainLoop:
|
|
|
878
920
|
next_tidy = now + self.tidy_interval
|
|
879
921
|
self._stop.wait(_POLL)
|
|
880
922
|
|
|
923
|
+
# 生效条件:以 apply=self.auto_tidy 调 writelimit.tidy_contextual(self.cg, actor="sustain_tidy"),把 t/scanned/groups/members/applied_count/auto_tidy 记入 self.last_tidy 与 tidys(仅保留最近 20 条),随后调 _tick_conformance();auto_tidy=False(默认)时只盘点不落盘;
|
|
881
924
|
def _tick_tidy(self):
|
|
882
925
|
"""整理巡检(contextual 流水治理·读侧):同构组聚合 + 成员降权。
|
|
883
926
|
|
|
@@ -898,6 +941,7 @@ class SustainLoop:
|
|
|
898
941
|
self.tidys = self.tidys[-20:]
|
|
899
942
|
self._tick_conformance()
|
|
900
943
|
|
|
944
|
+
# 生效条件:把 conformance.report_summary(self.cg) 的结论写入 self.last_conformance;该调用抛异常时写入 {"ok": False, "verdict": "BLINDSPOT", "error": "<类型名>: <消息>"};
|
|
901
945
|
def _tick_conformance(self):
|
|
902
946
|
"""数据健康不变量断言集(Pi⑤)——与 tidy 同节奏,**只取结论**。
|
|
903
947
|
|
|
@@ -912,6 +956,7 @@ class SustainLoop:
|
|
|
912
956
|
self.last_conformance = {"ok": False, "verdict": "BLINDSPOT",
|
|
913
957
|
"error": f"{type(e).__name__}: {e}"}
|
|
914
958
|
|
|
959
|
+
# 生效条件:恒以 evolution_candidates(self.cg) 只读盘点并记入 last_evolve 与 evolves(保留最近 20 条);仅当 self.auto_evolve 为真且 ev["importance_drift"]["n"] 为真时才额外执行 weights.recalc(self.cg, apply=True, actor="sustain_evolve"),其异常写入 rec["applied"];
|
|
915
960
|
def _tick_evolve(self):
|
|
916
961
|
"""演化巡检(G7):盘点固化/重要性候选 —— 让「有能力」变成「有驱动」。
|
|
917
962
|
|
|
@@ -938,6 +983,7 @@ class SustainLoop:
|
|
|
938
983
|
self.evolves.append(rec)
|
|
939
984
|
self.evolves = self.evolves[-20:]
|
|
940
985
|
|
|
986
|
+
# 生效条件:以 dry_run=not self.auto_scrub 调 scrub.sweep(self.cg),把 t/ok/n_issues/n_high_medium/applied/dry_run 记入 last_scrub 与 scrubs(保留最近 20 条);auto_scrub=False(默认)时 dry_run=True 只读巡检;
|
|
941
987
|
def _tick_scrub(self):
|
|
942
988
|
"""记忆自净:抽查 → 联想 → 去污染 → 校准偏差。
|
|
943
989
|
|
|
@@ -956,6 +1002,7 @@ class SustainLoop:
|
|
|
956
1002
|
self.scrubs.append(rec)
|
|
957
1003
|
self.scrubs = self.scrubs[-20:]
|
|
958
1004
|
|
|
1005
|
+
# 生效条件:先 diagnose(self.cg, name=self.name) 并将 ok 与 issues code 记入 last_diagnose;仅当 self.auto_heal 为真且 rep["ok"] 为假时才调 heal(self.cg, name=self.name),其 actions 非空时把各 action 的 code 记入 heals(保留最近 20 条);
|
|
959
1006
|
def _tick_heal(self):
|
|
960
1007
|
rep = diagnose(self.cg, name=self.name)
|
|
961
1008
|
self.last_diagnose = {"t": time.time(), "ok": rep["ok"],
|
|
@@ -971,6 +1018,7 @@ class SustainLoop:
|
|
|
971
1018
|
|
|
972
1019
|
# ---- 状态 ----
|
|
973
1020
|
|
|
1021
|
+
# 生效条件:以 read_stamp(self.name, self.d) 判定 state(有戳走 judge(age, interval=self.beat_interval, task_running=...),无戳为 "stopped"),返回含 name/running/pid/uptime(无 _started_at 时为 0.0)/beats/last_beat/各 interval 与 auto_* 开关/heals[-5:]/evolves[-5:]/tidys[-5:]/peers(self.d)/ledger.summary() 的 dict;
|
|
974
1022
|
def status(self) -> dict:
|
|
975
1023
|
st = read_stamp(self.name, self.d)
|
|
976
1024
|
state = (judge(st["age"], interval=self.beat_interval,
|
|
@@ -1006,6 +1054,7 @@ _LOOPS = {}
|
|
|
1006
1054
|
_LOOPS_LOCK = threading.Lock()
|
|
1007
1055
|
|
|
1008
1056
|
|
|
1057
|
+
# 生效条件:cg 必填,以 (os.path.abspath(cg.root), name) 为键在模块级 _LOOPS 中复用已有实例或新建 SustainLoop 并返回该实例。
|
|
1009
1058
|
def ensure_loop(cg, name: str = "md_cg", **kw) -> SustainLoop:
|
|
1010
1059
|
"""按 (root, name) 复用同一个常驻循环(避免重复线程 / 重复戳)。"""
|
|
1011
1060
|
key = (os.path.abspath(cg.root), name)
|
|
@@ -1017,10 +1066,12 @@ def ensure_loop(cg, name: str = "md_cg", **kw) -> SustainLoop:
|
|
|
1017
1066
|
return lp
|
|
1018
1067
|
|
|
1019
1068
|
|
|
1069
|
+
# 生效条件:cg 必填,返回 _LOOPS[(os.path.abspath(cg.root), name)],该键未登记时返回 None。
|
|
1020
1070
|
def get_loop(cg, name: str = "md_cg"):
|
|
1021
1071
|
return _LOOPS.get((os.path.abspath(cg.root), name))
|
|
1022
1072
|
|
|
1023
1073
|
|
|
1074
|
+
# 生效条件:模块级 _LOOPS 非空时清空该表并对每个循环调用 stop()(异常被吞),无返回值。
|
|
1024
1075
|
def stop_all():
|
|
1025
1076
|
"""测试 / 进程退出用:停掉本进程创建的所有常驻循环。"""
|
|
1026
1077
|
with _LOOPS_LOCK:
|
|
@@ -1033,6 +1084,7 @@ def stop_all():
|
|
|
1033
1084
|
pass
|
|
1034
1085
|
|
|
1035
1086
|
|
|
1087
|
+
# 生效条件:以 read_stamp(name) 有戳时返回 heartbeat={'age': round(st["age"],1), 'state': judge(...), 'pid', 'task_running'}、无戳时 {'state':'absent'},loop 取 get_loop(cg, name) 有值时给 running/beats/state、无值时 {'running': False},并附带 SessionLedger(cg.root).summary()、scrub_summary(cg)、evolve_summary(cg)、provenance_summary(cg);
|
|
1036
1088
|
def summary(cg, name: str = "md_cg") -> dict:
|
|
1037
1089
|
"""并入 health_os 的轻量摘要(只读戳与计数,不做巡检)。"""
|
|
1038
1090
|
st = read_stamp(name)
|
|
@@ -1054,6 +1106,7 @@ def summary(cg, name: str = "md_cg") -> dict:
|
|
|
1054
1106
|
}
|
|
1055
1107
|
|
|
1056
1108
|
|
|
1109
|
+
# 生效条件:cg 必填,可导入 provenance 且其 summary(cg) 调用成功时返回该摘要,异常时返回 {}。
|
|
1057
1110
|
def provenance_summary(cg) -> dict:
|
|
1058
1111
|
"""派生溯源摘要(G8,只读;失败不抛,避免拖垮 health_os)。"""
|
|
1059
1112
|
try:
|
|
@@ -1063,6 +1116,7 @@ def provenance_summary(cg) -> dict:
|
|
|
1063
1116
|
return {}
|
|
1064
1117
|
|
|
1065
1118
|
|
|
1119
|
+
# 生效条件:cg 必填,evolution_candidates(cg, top=3) 成功时返回 candidates/ccg_backlog/importance_drift 计数摘要,异常时返回 {}。
|
|
1066
1120
|
def evolve_summary(cg) -> dict:
|
|
1067
1121
|
"""演化巡检摘要(只读;失败不抛,避免拖垮 health_os)。"""
|
|
1068
1122
|
try:
|
|
@@ -1075,10 +1129,11 @@ def evolve_summary(cg) -> dict:
|
|
|
1075
1129
|
return {}
|
|
1076
1130
|
|
|
1077
1131
|
|
|
1132
|
+
# 生效条件:cg 必填,可导入 scrub 且其 summary(cg) 调用成功时返回该摘要,异常时返回 {}。
|
|
1078
1133
|
def scrub_summary(cg) -> dict:
|
|
1079
1134
|
"""记忆自净摘要(惰性导入,避免模块加载顺序耦合)。"""
|
|
1080
1135
|
try:
|
|
1081
1136
|
from . import scrub
|
|
1082
1137
|
return scrub.summary(cg)
|
|
1083
1138
|
except Exception: # noqa: BLE001
|
|
1084
|
-
return {}
|
|
1139
|
+
return {}
|
package/md_cg/tasks.py
CHANGED
|
@@ -75,6 +75,7 @@ _ILLEGAL_RE = re.compile(r"[^0-9A-Za-z\u4e00-\u9fff_.-]+")
|
|
|
75
75
|
|
|
76
76
|
# ---------------------------------------------------------------- 命名与解析
|
|
77
77
|
|
|
78
|
+
# 生效条件:name 为 None/空串或 strip 后为空时返回空串,否则把 `/`、`\`、`..` 及 _ILLEGAL_RE 命中字符折叠为 `-`、压缩连续 `-` 并去首尾 `-.` 后取前 64 字符再去首尾 `-.` 返回;
|
|
78
79
|
def slugify(name: str) -> str:
|
|
79
80
|
"""任务名 → 语义 slug(稳定标识;同 slug 即同任务)。
|
|
80
81
|
|
|
@@ -91,6 +92,7 @@ def slugify(name: str) -> str:
|
|
|
91
92
|
return s[:64].strip("-.")
|
|
92
93
|
|
|
93
94
|
|
|
95
|
+
# 生效条件:name(`name or ""` 后 strip)先剥掉已有的 TASK_PREFIX 再 slugify,结果非空且被 _SLUG_RE.match 命中时返回 TASK_PREFIX + slug;name 为假值或归一化后为空、不匹配时返回 ""。
|
|
94
96
|
def task_node_id(name: str) -> str:
|
|
95
97
|
"""任务名或节点 id → 规范节点 id(`task_<slug>`)。非法名返回空串。"""
|
|
96
98
|
s = (name or "").strip()
|
|
@@ -102,15 +104,18 @@ def task_node_id(name: str) -> str:
|
|
|
102
104
|
return TASK_PREFIX + slug
|
|
103
105
|
|
|
104
106
|
|
|
107
|
+
# 生效条件:v 为 None 或 str(v).strip() 为 "" 时返回 False,否则返回 True。
|
|
105
108
|
def _has(v) -> bool:
|
|
106
109
|
"""「本次调用是否提供了该字段」——空串/None 一律视为未提供(不静默清空)。"""
|
|
107
110
|
return v is not None and str(v).strip() != ""
|
|
108
111
|
|
|
109
112
|
|
|
113
|
+
# 生效条件:无必需形参且无模块级常量约束,恒返回 time.strftime("%Y-%m-%d %H:%M") 的当前时间文本。
|
|
110
114
|
def _today() -> str:
|
|
111
115
|
return time.strftime("%Y-%m-%d %H:%M")
|
|
112
116
|
|
|
113
117
|
|
|
118
|
+
# 生效条件:content(`content or ""` 后 splitlines)中有某行 strip 后以 `# ` + field + `:` 开头时,返回该行该前缀之后的去空白内容;content 为假值或无此匹配行时返回 ""。
|
|
114
119
|
def _field_line(content: str, field: str) -> str:
|
|
115
120
|
"""从正文抽 `# <字段>:` 行的值(与 `mdcos._ccg_field` 同源口径,
|
|
116
121
|
本模块不反向 import mdcos,避免包内循环依赖)。"""
|
|
@@ -122,6 +127,7 @@ def _field_line(content: str, field: str) -> str:
|
|
|
122
127
|
return ""
|
|
123
128
|
|
|
124
129
|
|
|
130
|
+
# 生效条件:(content or "") 的行中 strip 后以 "## " 开头者成为节名 s[3:].strip() 并切换当前节,其余行累入当前节,返回各节内容以 "\n" join 后 strip 的字典;无任何标题行时仅返回 {"__body__": 全篇 strip};content 为 None/空串时返回 {"__body__": ""}。
|
|
125
131
|
def sections(content: str) -> dict:
|
|
126
132
|
"""正文 → `{节名: 节内容}`;无标题部分归入 `__body__`。"""
|
|
127
133
|
out: dict = {"__body__": []}
|
|
@@ -136,6 +142,7 @@ def sections(content: str) -> dict:
|
|
|
136
142
|
return {k: "\n".join(v).strip() for k, v in out.items()}
|
|
137
143
|
|
|
138
144
|
|
|
145
|
+
# 生效条件:t = (text or "").strip(),t 为 ""/"(无)"/"(未填)" 时返回 "",否则 len(t) <= n(默认 200)时返回 t,超出时返回 t[:n].rstrip() + "…"。
|
|
139
146
|
def _brief(text: str, n: int = 200) -> str:
|
|
140
147
|
t = (text or "").strip()
|
|
141
148
|
if t in ("", "(无)", "(未填)"):
|
|
@@ -143,6 +150,7 @@ def _brief(text: str, n: int = 200) -> str:
|
|
|
143
150
|
return t if len(t) <= n else t[:n].rstrip() + "…"
|
|
144
151
|
|
|
145
152
|
|
|
153
|
+
# 生效条件:以 (old_text or "").strip() 为 base(base 为「(无)」或「(未填)」时置空),_has(change) 判定为假时返回 base or "(无)",为真时拼出 `- [今日] change.strip()`,base 非空返回 base+"\n"+该行再 strip,base 为空只返回该行;
|
|
146
154
|
def _append_change(old_text: str, change: str) -> str:
|
|
147
155
|
"""「计划变更」节追加一行(累积式,不覆盖历史)。"""
|
|
148
156
|
base = (old_text or "").strip()
|
|
@@ -154,6 +162,7 @@ def _append_change(old_text: str, change: str) -> str:
|
|
|
154
162
|
return (base + "\n" + line).strip() if base else line
|
|
155
163
|
|
|
156
164
|
|
|
165
|
+
# 生效条件:name 为必需形参(`name or ""` 后 strip 填 `# 功能名:` 行);condition、goal、acceptance、boundary、plan、changes、result 各经 _has 判定,未提供时分别落「无条件」「(未填:任务目标待补)」「other」「任务转 done/dropped 终态后不再作为进行中任务参与装配」「(未填)」「(无)」与空串;status 经 STATUS_ZH.get(status, status) 映射、未命中时原样输出,note 经 _has 为真时以 `|` 拼在执行行后。
|
|
157
166
|
def render(name: str, *, condition: str = "", goal: str = "", status: str = "active",
|
|
158
167
|
note: str = "", acceptance: str = "", boundary: str = "",
|
|
159
168
|
plan: str = "", changes: str = "", result: str = "") -> str:
|
|
@@ -184,6 +193,7 @@ def render(name: str, *, condition: str = "", goal: str = "", status: str = "act
|
|
|
184
193
|
|
|
185
194
|
# ---------------------------------------------------------------- 读写
|
|
186
195
|
|
|
196
|
+
# 生效条件:nid 为假值(None/空串)时返回 None;否则 cg.get(nid) 命中且其 frontmatter.layer == "structural"、TASK_TAG 在 frontmatter.tags(`or []`)中时,返回 {id, fm, content, path, sec}(sec 为 sections(content));记录缺失或层/标签不符时返回 None。
|
|
187
197
|
def _read_task(cg, nid: str):
|
|
188
198
|
"""读回任务卡;层或标签不符一律视为不存在(防串号:别的节点占用了同 id)。"""
|
|
189
199
|
if not nid:
|
|
@@ -199,6 +209,7 @@ def _read_task(cg, nid: str):
|
|
|
199
209
|
"path": rec.get("path"), "sec": sections(content)}
|
|
200
210
|
|
|
201
211
|
|
|
212
|
+
# 生效条件:以 layer="structural"、tags=list(tags)、importance=float(importance)、override=True、task_name=name、task_status=status、task_updated_at=time.time()、actor=actor or "task" 等构成 kw,extra 为真值时经 kw.update(extra) 追加覆盖,随后调用 cg.add(nid, content, **kw) 并返回其结果;
|
|
202
213
|
def _write(cg, nid: str, name: str, content: str, status: str, tags,
|
|
203
214
|
importance: float, actor, extra: dict = None):
|
|
204
215
|
"""唯一写盘点——走 `cg.add`(权限闸 / 归属注入全部复用既有链路)。
|
|
@@ -217,6 +228,7 @@ def _write(cg, nid: str, name: str, content: str, status: str, tags,
|
|
|
217
228
|
return cg.add(nid, content, **kw)
|
|
218
229
|
|
|
219
230
|
|
|
231
|
+
# 生效条件:cg 具有 _audit 属性(getattr(cg, "_audit", None) 非 None)时以 (op, nid, **meta) 调用它,且其中抛出的任何异常被吞掉;cg 无该属性时不调用,两种路径均不返回内容。
|
|
220
232
|
def _audit(cg, op: str, nid: str, **meta) -> None:
|
|
221
233
|
"""生命周期留痕;审计失败绝不阻断主流程(与 branches._audit 同哲学)。"""
|
|
222
234
|
a = getattr(cg, "_audit", None)
|
|
@@ -228,6 +240,7 @@ def _audit(cg, op: str, nid: str, **meta) -> None:
|
|
|
228
240
|
pass
|
|
229
241
|
|
|
230
242
|
|
|
243
|
+
# 生效条件:_read_task(cg, nid) 命中任务卡时返回摘要条目——id 取 rec['id'],name 取 fm.task_name 或(缺失/假值时)rec['id'],status 取 fm.task_status 或 "active",plan/changes/result 取对应节的 _brief 摘要(缺节回落 ""),并带 created_at/updated_at/path;未命中时返回 None。
|
|
231
244
|
def _entry(cg, nid: str):
|
|
232
245
|
"""节点 id → 对外任务条目(摘要形态;全字段查 `get_task`)。"""
|
|
233
246
|
rec = _read_task(cg, nid)
|
|
@@ -247,6 +260,7 @@ def _entry(cg, nid: str):
|
|
|
247
260
|
|
|
248
261
|
# ---------------------------------------------------------------- 写操作
|
|
249
262
|
|
|
263
|
+
# 生效条件:slugify(name) 为空串或不匹配 _SLUG_RE 时返回 {'ok': False, 含 slug 的非法名 error};否则 st = str(status if _has(status) else (旧卡 task_status or "active")).strip().lower(),st 不在 TASK_STATUSES 时返回未知状态错误,st == "done" 且合并后结果节 _has(new_res) 为假时返回「转 done 必须填结果」拒收,其余情况渲染写入并返回按 old 是否为 None 区分新建/更新的 out。
|
|
250
264
|
def upsert(cg, name: str, *, plan: str = None, status: str = None,
|
|
251
265
|
result: str = None, condition: str = None, goal: str = None,
|
|
252
266
|
acceptance: str = None, boundary: str = None, change: str = None,
|
|
@@ -313,12 +327,14 @@ def upsert(cg, name: str, *, plan: str = None, status: str = None,
|
|
|
313
327
|
return out
|
|
314
328
|
|
|
315
329
|
|
|
330
|
+
# 生效条件:content 中 `# 执行:` 行的值含 "|" 时返回第一个 "|" 之后去空白的内容;content 为假值、无该行或该行值不含 "|" 时返回 ""。
|
|
316
331
|
def _exec_note(content: str) -> str:
|
|
317
332
|
"""从 `# 执行:` 行取 `|` 之后的进度说明。"""
|
|
318
333
|
val = _field_line(content, "执行")
|
|
319
334
|
return val.split("|", 1)[1].strip() if "|" in val else ""
|
|
320
335
|
|
|
321
336
|
|
|
337
|
+
# 生效条件:node_id 经 task_node_id 得到非空 nid 且 _read_task 命中该卡时,以旧卡 task_name(缺失/假值回落 nid)、status、result、note、actor 转调 upsert 并返回其结果;node_id 非法时返回 ok=False「非法任务标识」,卡不存在时返回 ok=False「任务不存在」。
|
|
322
338
|
def set_status(cg, node_id: str, status: str, *, result: str = None,
|
|
323
339
|
note: str = None, actor: str = None) -> dict:
|
|
324
340
|
"""任务状态迁移(进行中/受阻/完成/放弃)。迁 done 且无结果 → 拒收。"""
|
|
@@ -332,6 +348,7 @@ def set_status(cg, node_id: str, status: str, *, result: str = None,
|
|
|
332
348
|
result=result, note=note, actor=actor)
|
|
333
349
|
|
|
334
350
|
|
|
351
|
+
# 生效条件:change 经 _has 判定为已提供、且 node_id 经 task_node_id 非空、_read_task 命中该卡时,以旧卡 task_name(缺失/假值回落 nid)与 change、actor 转调 upsert;change 为 None/空串/纯空白时返回 ok=False「计划变更内容为空」,node_id 非法或卡不存在时返回 ok=False 对应错误。
|
|
335
352
|
def plan_add(cg, node_id: str, change: str, *, actor: str = None) -> dict:
|
|
336
353
|
"""计划变更追加——执行中发现的错误/新问题/偏差,累积进「计划变更」节。"""
|
|
337
354
|
if not _has(change):
|
|
@@ -347,6 +364,7 @@ def plan_add(cg, node_id: str, change: str, *, actor: str = None) -> dict:
|
|
|
347
364
|
|
|
348
365
|
# ---------------------------------------------------------------- 读操作
|
|
349
366
|
|
|
367
|
+
# 生效条件:node_id 经 task_node_id 得到的 nid 对应一张 _read_task 命中的任务卡时返回 ok=True 的全字段(condition/goal/acceptance/boundary 由 _field_line 抽取、note 由 _exec_note 抽取、plan/changes/result 取节原文不截断、tags 取 fm.tags 或 []);否则返回 ok=False,error 中的标识取 nid 或原 node_id。
|
|
350
368
|
def get_task(cg, node_id: str) -> dict:
|
|
351
369
|
"""单卡全字段读回(计划/变更/结果不截断)。"""
|
|
352
370
|
nid = task_node_id(node_id)
|
|
@@ -370,6 +388,7 @@ def get_task(cg, node_id: str) -> dict:
|
|
|
370
388
|
"path": rec["path"]}
|
|
371
389
|
|
|
372
390
|
|
|
391
|
+
# 生效条件:status 经 `str(status or "").strip().lower()` 为非空且不在 TASK_STATUSES 时返回 ok=False 未知状态;否则收集 cg.index["nodes"] 中各 _entry 可读任务、按 updated_at 倒序,status 非空时再按该状态过滤,total 为过滤后截断前的条数,limit 为真值时取前 max(int(limit), 1) 条——limit 为 None、0 或空串(假值)时不截断而返回全量 filtered 列表。
|
|
373
392
|
def list_tasks(cg, status: str = None, limit: int = None) -> dict:
|
|
374
393
|
"""任务清单(按 updated_at 倒序);status 可选 active/blocked/done/dropped。"""
|
|
375
394
|
st = str(status or "").strip().lower() or None
|
|
@@ -389,6 +408,7 @@ def list_tasks(cg, status: str = None, limit: int = None) -> dict:
|
|
|
389
408
|
return {"ok": True, "count": len(items), "total": total, "tasks": items}
|
|
390
409
|
|
|
391
410
|
|
|
411
|
+
# 生效条件:遍历 cg.index["nodes"],把 _entry 可读且 status 为 active/blocked 的归入 active、status 为 done 的归入 done,各自按 updated_at 倒序;返回 active[:max(int(active_limit), 0)] 与 done[:max(int(done_limit), 0)](active_limit/done_limit 传 0 或负数时对应列表为空)以及两组截断前的 active_total/done_total。
|
|
392
412
|
def session_tasks(cg, active_limit: int = 5, done_limit: int = 5) -> dict:
|
|
393
413
|
"""会话装配用:进行中(active/blocked)+ 近期完成(done,按 updated_at 倒序)。
|
|
394
414
|
|
|
@@ -404,7 +424,10 @@ def session_tasks(cg, active_limit: int = 5, done_limit: int = 5) -> dict:
|
|
|
404
424
|
active.append(t)
|
|
405
425
|
elif t["status"] == "done":
|
|
406
426
|
done.append(t)
|
|
407
|
-
|
|
427
|
+
# 终键 id:同 updated_at 并列时定序,否则顺序回落到 cg.index["nodes"]
|
|
428
|
+
# 的物理序(增量路径=写入序,重建路径=nid 序)。
|
|
429
|
+
key = lambda x: (-(x.get("updated_at") or 0), # noqa: E731
|
|
430
|
+
str(x.get("id") or ""))
|
|
408
431
|
active.sort(key=key)
|
|
409
432
|
done.sort(key=key)
|
|
410
433
|
return {"ok": True,
|
|
@@ -413,6 +436,7 @@ def session_tasks(cg, active_limit: int = 5, done_limit: int = 5) -> dict:
|
|
|
413
436
|
"active_total": len(active), "done_total": len(done)}
|
|
414
437
|
|
|
415
438
|
|
|
439
|
+
# 生效条件:name 经 task_node_id 得 nid,exists 为同 id 任务卡是否被 _read_task 命中;k 经 `int(k or 5)` 再 max(…, 1)(k 为 None/0/空串回落 5,负数取 1)得到 kk,cg.search 抛异常时按无结果处理,只保留元素长度 ≥3、frontmatter.tags 含 TASK_TAG 且 node.id != nid 的条目,返回 out[:kk] 与 note。
|
|
416
440
|
def find_similar(cg, name: str, k: int = 5) -> dict:
|
|
417
441
|
"""同族任务提示(**只提示,不自动合并**——是否同一任务由调用方裁决)。
|
|
418
442
|
|
|
@@ -444,4 +468,4 @@ def find_similar(cg, name: str, k: int = 5) -> dict:
|
|
|
444
468
|
"score": round(float(score), 4),
|
|
445
469
|
"qualification": (qual or {}).get("state") if isinstance(qual, dict) else None})
|
|
446
470
|
return {"ok": True, "node_id": nid, "exists": exact, "similar": out[:kk],
|
|
447
|
-
"note": "仅提示疑似同族任务,不自动合并——终裁权在调用方"}
|
|
471
|
+
"note": "仅提示疑似同族任务,不自动合并——终裁权在调用方"}
|
package/md_cg/test_autonomy.py
CHANGED
|
@@ -7,6 +7,8 @@
|
|
|
7
7
|
提案携带证据明细
|
|
8
8
|
③ 全链路:提案 → learn_blindspots 五态终判 → 留痕 _explore.jsonl
|
|
9
9
|
④ 幂等:重复 explore 终态一致、gap 回写不重复(gap_<sha1(bid)> 幂等)
|
|
10
|
+
⑤ D_meta 排序加分:只进 sort_score 与证据,不改 score、不改资格
|
|
11
|
+
(零信号查询仍不提案;加分每轮一次、循环内复用)
|
|
10
12
|
|
|
11
13
|
运行:python -m md_cg.test_autonomy
|
|
12
14
|
"""
|
|
@@ -103,6 +105,30 @@ def main():
|
|
|
103
105
|
"④b gap 回写幂等(第二次 written=None,节点已存在)")
|
|
104
106
|
else:
|
|
105
107
|
ok(True, "④b本终态无回写动作,幂等性由判定确定性保证")
|
|
108
|
+
|
|
109
|
+
# ---------- ⑤ D_meta 排序加分(只影响排序,不改资格) ----------
|
|
110
|
+
root5 = os.path.join(tmp, "root5")
|
|
111
|
+
os.makedirs(root5, exist_ok=True)
|
|
112
|
+
cg5 = MdCGOS(root5)
|
|
113
|
+
for i in range(50):
|
|
114
|
+
append_jsonl(cg5.recent_log, {"i": i})
|
|
115
|
+
_sig(cg5, 1.0, "零信号查询", 0.0, {})
|
|
116
|
+
_sig(cg5, 2.0, "反应堆冷却方案", 0.9, {"BLINDSPOT": 2})
|
|
117
|
+
pr5 = autonomy.proposals(cg5, limit=3)
|
|
118
|
+
ps5 = pr5["proposals"]
|
|
119
|
+
ok(len(ps5) == 1 and pr5["n_signals"] == 2,
|
|
120
|
+
"⑤零信号查询仍不提案、n_signals 按原始信号计(加分不改资格)")
|
|
121
|
+
p0 = ps5[0]
|
|
122
|
+
ok(p0["score"] == round(autonomy.W_D2 * p0["d2_abs"]
|
|
123
|
+
+ autonomy.W_BLINDSPOT * p0["blindspot"]
|
|
124
|
+
+ autonomy.W_DEFER * p0["defer"], 4),
|
|
125
|
+
"⑤bscore 仍是纯 ΔD 定价(口径零变更)")
|
|
126
|
+
ok(p0["sort_score"] == round(p0["score"] + p0["d_meta_bonus"], 4)
|
|
127
|
+
and p0["d_meta_bonus"] == pr5["d_meta"]["bonus"],
|
|
128
|
+
"⑤c排序键 = ΔD + D_meta 加分(每轮一次、循环内复用)")
|
|
129
|
+
ok(p0["d_meta_bonus"] > 0 and "D_meta" in p0["reason"]
|
|
130
|
+
and "不合成" in p0["reason"],
|
|
131
|
+
"⑤d加分生效且 reason 追加证据并声明不合成(可审计)")
|
|
106
132
|
finally:
|
|
107
133
|
shutil.rmtree(tmp, ignore_errors=True)
|
|
108
134
|
|