@furongjun1999/dsh-memory 0.4.7 → 0.4.8
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 +92 -37
- package/codebuddy/CODEBUDDY.md +195 -185
- package/docs/Pi/345/217/257/345/255/246/344/271/240/344/274/230/347/202/271_/347/201/265/346/236/242/345/244/247/350/204/221/346/224/271/350/277/233/344/272/244/346/216/245_20260915.md +144 -0
- package/docs/README.md +111 -0
- package/docs/discipline/harnesses.yaml +215 -152
- package/docs/discipline/templates/full.md.tmpl +60 -58
- package/docs/eval//347/254/254/344/270/211/346/226/271/351/252/214/350/257/201/346/212/245/345/221/212_LoCoMo_/347/201/265/346/236/242_.md +127 -0
- package/docs/eval//347/254/254/344/270/211/346/226/271/351/252/214/350/257/201/346/212/245/345/221/212_LoCoMo_/347/201/265/346/236/242_.png +0 -0
- package/docs/eval//347/254/254/344/270/211/346/226/271/351/252/214/350/257/201/346/212/245/345/221/212_/347/201/265/346/236/242_vs_dejavu_/347/273/237/344/270/200/350/257/204/345/210/206_v7.md +202 -0
- package/docs/experiments/m4-role-probe/census.py +86 -0
- package/docs/experiments/m4-role-probe/probe.py +89 -0
- package/docs/experiments/mapped_confidence/bench_mapped_conf.py +216 -0
- package/docs/experiments/mapped_confidence/post_check.py +75 -0
- package/docs/experiments/mapped_confidence/repro_thirdparty_tol.py +83 -0
- package/docs/experiments/mapped_confidence/result_mapped_conf.json +215 -0
- package/docs/{ → hive/}/350/234/202/345/267/242/345/267/245/344/275/234/350/256/260/345/277/206_/351/241/271/347/233/256/350/256/241/345/210/222.md +14 -2
- package/docs/{README → mdcg/README}/350/257/246/347/273/206/347/211/210_v0.4.5.md +9 -9
- package/docs/{ → mdcg/}/344/270/273/344/273/243/347/220/206/345/255/220/344/273/243/347/220/206/350/256/260/345/277/206/346/236/266/346/236/204/350/256/276/350/256/241.md +1 -1
- 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 +261 -0
- package/docs/{ → mdcg/}/345/215/225/345/205/203/350/207/252/346/210/221/351/224/232/347/202/271_/347/263/273/347/273/237/346/217/220/347/244/272/350/257/215/346/240/207/345/207/206_v0.3.md +1 -1
- 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 +148 -0
- package/docs/{ → mdcg/}/346/272/220/347/240/201/347/272/247/346/236/266/346/236/204/345/256/241/350/256/241_GPT/346/211/271/350/257/204/345/257/271/347/205/247_v1.0.md +2 -2
- package/docs/{ → mdcg/}/347/201/265/346/236/242/344/270/211/345/261/202/346/213/206/345/210/206/350/247/204/345/210/222_v0.1.md +181 -181
- package/docs/{ → mdcg/}/347/274/272/345/217/243/345/215/225_P0/346/224/266/345/217/243_v0.1.md +2 -2
- package/docs/{ → mdcg/}/350/256/244/347/237/245/345/233/276_G4-G8/347/274/272/345/217/243/350/243/201/345/256/232/345/215/225_v0.1.md +1 -1
- package/docs/{ → mdcg/}/350/256/244/347/237/245/345/233/276_/347/264/242/345/274/225/344/270/216/345/267/245/347/250/213/350/247/204/350/214/203/345/214/226_/350/256/241/345/210/222_v0.1.md +4 -4
- package/docs/{ → mdcg/}/350/256/260/345/277/206/346/223/215/344/275/234/347/263/273/347/273/237_MdCGOS/344/270/216MCP/346/216/245/345/205/245_v0.1.md +2 -2
- package/docs/plans/GridWorld/346/234/200/345/260/217/351/227/255/347/216/257/350/247/204/346/240/274_v0.1.md +176 -0
- package/docs/{ → swarm/}/350/234/202/347/276/244/344/272/222/350/201/224_v0.1.md +3 -3
- package/docs/swarm//350/234/202/347/276/244/345/220/214/351/224/231/346/243/200/346/265/213/345/256/236/351/252/214/345/215/217/350/256/256_v0.1.md +242 -0
- package/docs/theory//345/215/225/347/272/277/347/250/213/344/270/216/346/263/250/346/204/217/345/212/233/351/233/206/344/270/255_/346/227/240/344/272/211/350/256/256/347/220/206/350/256/272/346/226/207/346/241/243_v1.0.md +129 -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 +134 -0
- package/docs/theory//346/246/202/345/277/265/345/210/206/345/261/202/345/257/271/351/275/220/350/241/250_v0.1.md +145 -0
- 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/}/347/220/206/350/256/272/344/273/223/346/213/206/345/210/206/344/270/216/347/231/275/347/256/261/347/237/245/350/257/206/345/272/223/345/206/205/350/277/201_v0.1.md +1 -1
- 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 +221 -0
- package/docs/world_model//344/270/226/347/225/214/346/250/241/345/236/213_/347/245/236/347/273/217/347/275/221/347/273/234/345/272/225/345/261/202/346/236/266/346/236/204_v1.0.md +237 -0
- package/docs//345/255/230/347/256/227/344/270/200/344/275/223/346/236/266/346/236/204/345/255/246/344/271/240_/345/244/226/351/203/250/347/220/206/350/256/272/345/257/271/347/205/247/344/270/216/350/267/257/347/272/277/344/272/244/346/216/245_20260916.md +98 -0
- package/docs//345/255/230/347/256/227/344/270/200/344/275/223/347/245/236/347/273/217/347/275/221/347/273/234/346/236/266/346/236/204_/346/200/273/347/272/262/344/270/216/344/272/244/346/216/245_20260916.md +109 -0
- 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 +34 -6
- package/docs//347/231/275/347/256/261/345/214/226/347/272/262/351/242/206_/344/270/211/351/241/271/347/233/256/345/255/246/344/271/240/346/200/273/346/236/266/346/236/204/344/270/216Ghidra/344/272/244/346/216/245_20260916.md +104 -0
- package/docs//350/256/260/345/277/206/350/257/204/345/256/241/347/263/273/347/273/237_/347/253/213/351/241/271/350/256/276/350/256/241/344/270/216/346/226/275/345/267/245/344/272/244/346/216/245_20260915.md +183 -0
- package/dsh/README.md +1 -1
- package/lib/hooks.d.ts +5 -0
- package/lib/hooks.js +10 -1
- package/lib/lib/prompt_safety.d.ts +48 -0
- package/lib/lib/prompt_safety.js +62 -0
- package/md_cg/audit.py +76 -2
- package/md_cg/bench_membench.py +10 -5
- package/md_cg/bench_progressive.py +48 -9
- package/md_cg/branches.py +275 -0
- package/md_cg/ccgc.py +884 -0
- package/md_cg/conformance.py +662 -0
- package/md_cg/consistency.py +1 -1
- package/md_cg/corpus.py +1 -1
- package/md_cg/eval_common.py +6 -5
- package/md_cg/forgetting.py +11 -3
- package/md_cg/fsutil.py +63 -1
- package/md_cg/identity.py +2 -2
- package/md_cg/insight.py +2 -2
- package/md_cg/lifecycle.py +262 -0
- package/md_cg/mcp_server.py +547 -140
- package/md_cg/mdcg.py +206 -18
- package/md_cg/mdcos.py +512 -50
- package/md_cg/metacognition.py +2 -2
- package/md_cg/mreview/__init__.py +25 -0
- package/md_cg/mreview/__main__.py +107 -0
- package/md_cg/mreview/bundle.py +170 -0
- package/md_cg/mreview/candidates.py +253 -0
- package/md_cg/mreview/govern.py +674 -0
- package/md_cg/mreview/locate.py +905 -0
- package/md_cg/mreview/pipeline.py +701 -0
- package/md_cg/mreview/rules/duplication.json +21 -0
- package/md_cg/mreview/rules/field_coverage.json +54 -0
- package/md_cg/mreview/rules/source_license.json +21 -0
- package/md_cg/mreview/rules/template_flow.json +21 -0
- package/md_cg/mreview/ruleset.py +238 -0
- package/md_cg/nodefile.py +38 -0
- package/md_cg/predict.py +24 -6
- package/md_cg/protect.py +4 -4
- package/md_cg/refindex.py +41 -2
- package/md_cg/scrub.py +5 -1
- package/md_cg/self_state.py +1 -1
- package/md_cg/semantic/en_normalizer.py +85 -25
- package/md_cg/sustain.py +18 -0
- package/md_cg/tasks.py +447 -0
- package/md_cg/test_action_derive.py +203 -0
- package/md_cg/test_audit_rotate.py +270 -0
- package/md_cg/test_branches.py +249 -0
- package/md_cg/test_ccg_perturb.py +1 -1
- package/md_cg/test_ccgc.py +423 -0
- package/md_cg/test_conformance.py +343 -0
- package/md_cg/test_en_pipeline.py +24 -0
- package/md_cg/test_health_scale.py +173 -0
- package/md_cg/test_identity_attribution.py +6 -0
- package/md_cg/test_index_durability.py +224 -0
- package/md_cg/test_lifecycle.py +309 -0
- package/md_cg/test_mr_m1.py +679 -0
- package/md_cg/test_mr_m2.py +587 -0
- package/md_cg/test_mr_m3.py +703 -0
- package/md_cg/test_mr_m4.py +485 -0
- package/md_cg/test_p1.py +1 -1
- package/md_cg/test_p11_consistency.py +1 -1
- package/md_cg/test_p12_metacognition.py +2 -2
- package/md_cg/test_p16_self_state.py +1 -1
- package/md_cg/test_p26_refindex.py +1 -1
- package/md_cg/test_p27_docindex.py +22 -2
- package/md_cg/test_p28_refcheck.py +1 -1
- package/md_cg/test_p29_session_ingest_export.py +1 -1
- package/md_cg/test_p2_mcp.py +7 -1
- package/md_cg/test_p38_contextualize.py +1 -1
- package/md_cg/test_p39_vision_evidence.py +1 -1
- package/md_cg/test_p40_refine_worklist.py +1 -1
- package/md_cg/test_p41_evolve_patrol.py +1 -1
- package/md_cg/test_p42_provenance.py +1 -1
- package/md_cg/test_p43_pooling.py +41 -13
- package/md_cg/test_read_clip.py +137 -0
- package/md_cg/test_review_conformance.py +310 -0
- package/md_cg/test_tasks.py +409 -0
- package/md_cg/test_tool_face.py +189 -0
- package/md_cg/test_twophase.py +286 -0
- package/md_cg/test_writepipe.py +210 -0
- package/md_cg/theory.py +1 -1
- package/md_cg/tokens.py +95 -5
- package/md_cg/tool_face.py +250 -0
- package/md_cg/twophase.py +221 -0
- package/md_cg/units.py +546 -0
- package/md_cg/vision_evidence.py +1 -1
- package/md_cg/weights.py +1 -1
- package/md_cg/whitebox.py +1 -1
- package/md_cg/whitebox_kb/data/verify_cache.json +28 -0
- package/md_cg/whitebox_kb/data/verify_savings.jsonl +46 -0
- package/md_cg/whitebox_kb/wisdom/audit_log/chain_heat.json +10 -10
- 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/writelimit.py +19 -3
- package/md_cg/writepipe.py +372 -0
- package/package.json +1 -1
- package/src/hooks.ts +10 -1
- package/src/lib/prompt_safety.ts +62 -0
- package/zcode/AGENTS.md +195 -185
- package/docs//345/212/237/350/203/275/350/260/203/347/224/250/346/230/240/345/260/204/350/241/250_v0.1.md +0 -221
- /package/docs/{AGI → eval/AGI}/344/270/203/347/273/264/350/257/204/345/210/206/346/212/245/345/221/212_md_cg_v1.0.md" +0 -0
- /package/docs/{AGI → eval/AGI}/344/270/203/347/273/264/350/257/204/345/210/206/346/212/245/345/221/212_md_cg_v2.0.md" +0 -0
- /package/docs/{ → eval/}/345/256/236/351/252/214/346/226/271/346/241/210_/345/255/246/344/271/240/351/227/255/347/216/257AB/344/270/216/346/250/252/350/257/204_v1.0.md" +0 -0
- /package/docs/{ → eval/}/346/250/252/350/257/204_/345/205/255/345/256/266100/351/242/230/344/270/255/350/213/261/345/217/214/346/237/245_v1.0.md" +0 -0
- /package/docs/{guardrail-charter.md → mdcg/guardrail-charter.md} +0 -0
- /package/docs/{lingshu_tutorial.html → mdcg/lingshu_tutorial.html} +0 -0
- /package/docs/{memory-assessment.html → mdcg/memory-assessment.html} +0 -0
- /package/docs/{memory_score.html → mdcg/memory_score.html} +0 -0
- /package/docs/{memory_score.png → mdcg/memory_score.png} +0 -0
- /package/docs/{release_v0.3.0.md → mdcg/release_v0.3.0.md} +0 -0
- /package/docs/{release_v0.4.5.md → mdcg/release_v0.4.5.md} +0 -0
- /package/docs/{tool_table_v0.3.0.md → mdcg/tool_table_v0.3.0.md} +0 -0
- /package/docs/{ → mdcg/}/344/273/244/347/211/214/344/270/216/350/247/222/350/211/262/346/235/203/350/201/214/345/210/206/347/246/273_v0.1.md" +0 -0
- /package/docs/{ → mdcg/}/347/201/265/346/236/24282/345/267/245/345/205/267_/345/212/237/350/203/275/346/225/264/347/220/206/344/270/216/350/277/201/347/247/273/346/230/240/345/260/204_v0.1.md" +0 -0
- /package/docs/{ → mdcg/}/347/201/265/346/236/242MCP/345/267/245/345/205/267/346/200/273/350/241/250_v3.4.md" +0 -0
- /package/docs/{ → mdcg/}/347/201/265/346/236/242_/350/207/252/346/210/221/345/261/202/345/256/232/344/271/211.md" +0 -0
- /package/docs/{ → mdcg/}/350/256/244/347/237/245/345/233/276_MD/347/233/256/345/275/225/346/226/271/346/241/210_v0.1.md" +0 -0
- /package/docs/{ → mdcg/}/350/256/244/347/237/245/345/233/276_/346/235/241/344/273/266/347/251/272/351/227/264/345/220/210/346/210/220/344/270/216/347/224/237/346/225/210/346/235/241/344/273/266/345/217/243/345/276/204_v0.1.md" +0 -0
- /package/docs/{ → mdcg/}/350/256/244/347/237/245/345/233/276/344/275/234/344/270/272/350/256/260/345/277/206/346/223/215/344/275/234/347/263/273/347/273/237_/350/257/204/344/274/260/344/270/216/350/267/257/347/272/277_v0.1.md" +0 -0
- /package/docs/{ → plans/}/345/221/275/345/220/215/346/262/273/347/220/206_/351/241/271/347/233/256/350/256/241/345/210/222.md" +0 -0
- /package/docs/{ → plans/}/347/237/245/350/257/206/345/233/276/350/260/261/351/241/271/347/233/256_/344/272/244/346/216/245/346/226/207/346/241/243_20260914.md" +0 -0
- /package/docs/{ → swarm/}/350/234/202/347/276/244/345/244/232/346/231/272/350/203/275/344/275/223_/344/275/277/347/224/250/346/212/245/345/221/212_20260913.md" +0 -0
- /package/docs/{ → swarm/}/350/234/202/347/276/244/345/244/232/346/231/272/350/203/275/344/275/223_/345/212/237/350/203/275/350/257/264/346/230/216_v0.6.md" +0 -0
- /package/docs/{ → swarm/}/350/234/202/347/276/244/347/233/262/345/214/272/346/240/207/350/256/260_v1.0.md" +0 -0
- /package/docs/{ → theory/}/344/277/241/346/201/257/345/267/256/344/270/272/344/273/200/344/271/210/345/277/205/347/204/266/345/255/230/345/234/250/344/270/224/350/207/252/347/204/266/346/211/251/345/244/247.md" +0 -0
- /package/docs/{ → theory/}/346/231/272/350/203/275/347/232/204/345/205/254/347/220/206/345/214/226/345/237/272/347/237/263.md" +0 -0
- /package/docs/{ → theory/}/346/231/272/350/203/275/347/232/204/350/256/244/347/237/245/350/277/207/347/250/213.md" +0 -0
- /package/docs/{ → theory/}/346/231/272/350/203/275/350/256/2723.4.md" +0 -0
- /package/docs/{ → theory/}/347/231/275/347/256/261/346/231/272/350/203/275/346/230/257/344/273/200/344/271/210/357/274/237.md" +0 -0
- /package/docs/{ → theory/}/347/231/275/347/256/261/346/231/272/350/203/275/347/263/273/345/210/227/302/267/347/254/254/344/272/224/347/257/207/357/274/232/350/256/251AI/347/234/237/346/255/243/350/243/205/344/270/212/350/256/260/345/277/206.md" +0 -0
package/md_cg/corpus.py
CHANGED
|
@@ -84,7 +84,7 @@ def seed(cg, marks=True, layer="knowledge", verification_basis="data"):
|
|
|
84
84
|
def reset_root(root, clean=False):
|
|
85
85
|
"""确保测试根存在:保证「重跑 ≡ 首跑」,且上一轮残留节点不会被当成先验。
|
|
86
86
|
|
|
87
|
-
默认**不删目录**(对齐本仓已裁决的设计,见 docs/认知图_MD目录方案_v0.1.md
|
|
87
|
+
默认**不删目录**(对齐本仓已裁决的设计,见 docs/mdcg/认知图_MD目录方案_v0.1.md
|
|
88
88
|
「幂等性:迁移按 node id 原子覆盖写,可反复执行,**不需要先清空目录**」):
|
|
89
89
|
`seed()` 用固定 node id 原子覆盖,同一套语料重跑落在同一批文件上,集合封闭。
|
|
90
90
|
|
package/md_cg/eval_common.py
CHANGED
|
@@ -61,11 +61,12 @@ PATHS_CAL = PATHS + ("semantic",) # 口径 B:显式启用条件结构路(
|
|
|
61
61
|
def unlock_global_cap():
|
|
62
62
|
"""评测口径:解除 GLOBAL_CAP 截断(bench_membench patch_lexical_full 同法)。
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
64
|
+
【2026-09-16 后语义更新】截断依据已由「插入序」改为「相关度」(`mdcg
|
|
65
|
+
.cut_by_relevance` 与 `mdcos._lexical` 均先全量打分再排序截断,cap 值仍
|
|
66
|
+
500)——插入序 lottery 已消除。评测仍解除 cap 的理由只剩一条:避免
|
|
67
|
+
「按相关度截掉尾部」在长语料上压低召回天花板(相近似 turn 数千条时,
|
|
68
|
+
排 501 名之后仍可能是证据)——评测要测**排序质量**而非截断策略。
|
|
69
|
+
mdcg 与 mdcos 各持一份 from-import 值,必须双改(test_p43_pooling 先例)。
|
|
69
70
|
"""
|
|
70
71
|
import md_cg.mdcg as m
|
|
71
72
|
import md_cg.mdcos as mo
|
package/md_cg/forgetting.py
CHANGED
|
@@ -9,12 +9,12 @@
|
|
|
9
9
|
· `docs/白箱智能系列·第五篇:174-180`
|
|
10
10
|
「把经历兑换成结构…整理完之后记忆库变小了,但信息量反而更可用——
|
|
11
11
|
噪音被扔掉了,骨架被留下。」
|
|
12
|
-
· AEIS 工具表 `docs/tool_table_v0.3.0.md:15-19`
|
|
12
|
+
· AEIS 工具表 `docs/mdcg/tool_table_v0.3.0.md:15-19`
|
|
13
13
|
`prefeed`(H1 新奇检测 → 高新奇输入当场强化编码)、
|
|
14
14
|
`pattern_separation`(H3 扫描相似节点对)、
|
|
15
15
|
`nightly_cleanup`(知识层夜间整理、无边孤岛降级)。
|
|
16
16
|
本模块 = 这三件事的**写入侧前置版**:不等夜间整理,写之前就裁决。
|
|
17
|
-
· `docs/智能的公理化基石.md:758-763` —— **诚实边界**
|
|
17
|
+
· `docs/theory/智能的公理化基石.md:758-763` —— **诚实边界**
|
|
18
18
|
「信息差与热力学熵之间只能进行结构类比,不应宣称数学同构」。
|
|
19
19
|
故本模块一律称「自信息代理 / 惊奇度」,**不称香农熵**,也不做熵的物理断言。
|
|
20
20
|
|
|
@@ -47,7 +47,7 @@ import math
|
|
|
47
47
|
import os
|
|
48
48
|
import time
|
|
49
49
|
|
|
50
|
-
from . import nodefile
|
|
50
|
+
from . import lifecycle, nodefile
|
|
51
51
|
from .fsutil import append_jsonl, atomic_write, read_jsonl
|
|
52
52
|
from .mdcg import bigrams
|
|
53
53
|
|
|
@@ -256,11 +256,19 @@ def reinforce(cg, node_id, delta=0.05):
|
|
|
256
256
|
fm["protected"] = True
|
|
257
257
|
fm["protection_reason"] = (f"importance={imp:.2f}≥{PROTECT_IMPORTANCE}"
|
|
258
258
|
f"(重复强化)")
|
|
259
|
+
# ② 显式状态机收口(2026-09-16):MERGE 的语义是「又一次见到」= **重新激活**
|
|
260
|
+
# 信号——已降权(demoted)/已定型(converged)的节点经状态机**逐级回升**到
|
|
261
|
+
# active(archived→active 亦合法,归档节点被再次见到即恢复参与);active 为
|
|
262
|
+
# 幂等 no-op(不写字段、不留痕)。protected 只豁免**降级**,回升不受限。
|
|
263
|
+
lifecycle.stamp(fm, "active", reason="MERGE 重复强化(回升)",
|
|
264
|
+
actor="forgetting:reinforce")
|
|
259
265
|
cg._write_node(node_id, os.path.join(cg.root, node["path"]),
|
|
260
266
|
fm, node.get("content") or "")
|
|
261
267
|
e = ((getattr(cg, "index", None) or {}).get("nodes") or {}).get(node_id)
|
|
262
268
|
if e is not None:
|
|
263
269
|
e["importance"] = imp
|
|
270
|
+
if fm.get(lifecycle.STATE_FIELD):
|
|
271
|
+
e[lifecycle.STATE_FIELD] = fm[lifecycle.STATE_FIELD]
|
|
264
272
|
if fm.get("protected"):
|
|
265
273
|
e["protected"] = True
|
|
266
274
|
e["protection_reason"] = fm["protection_reason"]
|
package/md_cg/fsutil.py
CHANGED
|
@@ -96,12 +96,18 @@ class FileLock:
|
|
|
96
96
|
|
|
97
97
|
超时后放弃并放行(best-effort):写被拒绝的代价大于一次竞态——这与
|
|
98
98
|
deja-vu 对 usage 日志锁的取舍一致("a racing write beats a lost injection")。
|
|
99
|
+
|
|
100
|
+
strict=True 反转该取舍:超时抛 TimeoutError 而非放行。适用于**不能丢的写**
|
|
101
|
+
(审核队列 inbox/decisions、裁决记录)——锁竞争失败时显式报错让调用方重试,
|
|
102
|
+
好过静默放行后退化为无锁并发(丢一条提案/裁决比让写入者等一下代价大)。
|
|
99
103
|
"""
|
|
100
104
|
|
|
101
|
-
def __init__(self, path: str, timeout: float = 10.0, poll: float = 0.01
|
|
105
|
+
def __init__(self, path: str, timeout: float = 10.0, poll: float = 0.01,
|
|
106
|
+
strict: bool = False):
|
|
102
107
|
self.path = path + ".lock"
|
|
103
108
|
self.timeout = timeout
|
|
104
109
|
self.poll = poll
|
|
110
|
+
self.strict = strict
|
|
105
111
|
self._f = None
|
|
106
112
|
self.acquired = False
|
|
107
113
|
|
|
@@ -122,6 +128,9 @@ class FileLock:
|
|
|
122
128
|
if e.errno not in (errno.EACCES, errno.EAGAIN, errno.EDEADLK):
|
|
123
129
|
raise
|
|
124
130
|
if time.time() > deadline:
|
|
131
|
+
if self.strict:
|
|
132
|
+
raise TimeoutError(
|
|
133
|
+
f"FileLock 超时未获锁(strict):{self.path}")
|
|
125
134
|
return self # 放行,不阻断写路径
|
|
126
135
|
time.sleep(self.poll)
|
|
127
136
|
|
|
@@ -193,6 +202,59 @@ def read_jsonl(path: str):
|
|
|
193
202
|
continue
|
|
194
203
|
|
|
195
204
|
|
|
205
|
+
_COUNT_CACHE = {} # abspath -> (bytes_scanned, mtime_ns, lines)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def count_jsonl(path: str, chunk: int = 1 << 20) -> int:
|
|
209
|
+
"""数 append-only 日志的行数——**流式计数、不物化**(内存 O(1))。
|
|
210
|
+
|
|
211
|
+
存在的唯一理由:`len(list(read_jsonl(p)))` 是**危险的默认写法**。它把整份
|
|
212
|
+
日志解析成对象列表,日志一长就是灾难——本机实测(2026-09-16):审计日志
|
|
213
|
+
4.3 GB,一次 health/盘点调用解析速率 ~1 MB/s、RSS 涨到 5 GB+ 仍在涨、
|
|
214
|
+
数十分钟不返回;调用方超时重试又在别的进程里再排一次队,最终把整条 MCP
|
|
215
|
+
通道堵死(一个只读的「体检」把服务打死,代价与收益完全不成比例)。
|
|
216
|
+
这里只数字节里的换行:不解析、不驻留,速度只受磁盘限制;进程内按
|
|
217
|
+
(已扫字节数, mtime) 缓存,文件只增长时只扫新增字节(O(增量))。
|
|
218
|
+
|
|
219
|
+
语义边界(诚实声明):数的是**换行符**,不是 JSON 记录——
|
|
220
|
+
① 完整写入的日志(`append_jsonl` 每条尾带 `\\n`)换行数 == 记录数,与
|
|
221
|
+
`len(list(read_jsonl(p)))` 逐位相等(test_health_scale ⑦ 守卫);
|
|
222
|
+
② 末尾**未终止的半截行**不计入(下界,最多差 1 行;见 append_jsonl 的
|
|
223
|
+
ends_mid_line 修补分支——并发交错被杀的进程会留下这种尾巴);
|
|
224
|
+
③ 非法 JSON 行计入行数而 `read_jsonl` 会跳过——健康度是量级指标,不做
|
|
225
|
+
逐行校验,逐行解析正是上面那场事故的根因。
|
|
226
|
+
"""
|
|
227
|
+
key = os.path.abspath(path or "")
|
|
228
|
+
try:
|
|
229
|
+
st = os.stat(key)
|
|
230
|
+
except OSError:
|
|
231
|
+
return 0
|
|
232
|
+
start, total = 0, 0
|
|
233
|
+
prev = _COUNT_CACHE.get(key)
|
|
234
|
+
if prev:
|
|
235
|
+
p_scan, p_mtime, p_count = prev
|
|
236
|
+
if p_scan == st.st_size and p_mtime == st.st_mtime_ns:
|
|
237
|
+
return p_count # 完全未变:零 IO
|
|
238
|
+
if p_scan < st.st_size and p_mtime != st.st_mtime_ns:
|
|
239
|
+
start, total = p_scan, p_count # 只增长:扫增量
|
|
240
|
+
scanned, lines = start, 0
|
|
241
|
+
try:
|
|
242
|
+
with open(key, "rb") as f:
|
|
243
|
+
if start:
|
|
244
|
+
f.seek(start)
|
|
245
|
+
while True:
|
|
246
|
+
buf = f.read(chunk)
|
|
247
|
+
if not buf:
|
|
248
|
+
break
|
|
249
|
+
scanned += len(buf)
|
|
250
|
+
lines += buf.count(b"\n")
|
|
251
|
+
except OSError:
|
|
252
|
+
return total or 0
|
|
253
|
+
total += lines
|
|
254
|
+
_COUNT_CACHE[key] = (scanned, st.st_mtime_ns, total)
|
|
255
|
+
return total
|
|
256
|
+
|
|
257
|
+
|
|
196
258
|
class ShardedLog:
|
|
197
259
|
"""每写者独占一个分片的 append-only 日志——不能丢记录时用它。
|
|
198
260
|
|
package/md_cg/identity.py
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
理论出处
|
|
5
5
|
--------
|
|
6
|
-
· 智能论 v3.4 §十三「五大单元」·位置效应(`docs/智能的公理化基石.md:540-556`):
|
|
6
|
+
· 智能论 v3.4 §十三「五大单元」·位置效应(`docs/theory/智能的公理化基石.md:540-556`):
|
|
7
7
|
|
|
8
8
|
单元 位置效应 核心职责
|
|
9
9
|
记录单元 全 保存观测、过程、结果和误差
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
所以「画像」不该只刻画用户,而应识别**每个主体的身份锚点 + 位置 + 特征**,
|
|
17
17
|
其中也包括智能体自身(self)。
|
|
18
18
|
|
|
19
|
-
· 扮演论 v3.3(`README.md:231-271`、`docs/tool_table_v0.3.0.md:86-92`):
|
|
19
|
+
· 扮演论 v3.3(`README.md:231-271`、`docs/mdcg/tool_table_v0.3.0.md:86-92`):
|
|
20
20
|
智能即扮演;角色卡 = 条件空间声明起点;三接口 =
|
|
21
21
|
memory(历史 → 知识层)/
|
|
22
22
|
anchor(自我锚点 → SELF 层,不可遗忘)/
|
package/md_cg/insight.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
"""洞察条件层(P2 · insight):洞见事件四件套。
|
|
3
3
|
|
|
4
|
-
对齐 `docs/tool_table_v0.3.0.md`(insight_record / insight_verify /
|
|
5
|
-
insight_report / insight_window)与 `docs/灵枢82工具_功能整理与迁移映射_v0.1.md`
|
|
4
|
+
对齐 `docs/mdcg/tool_table_v0.3.0.md`(insight_record / insight_verify /
|
|
5
|
+
insight_report / insight_window)与 `docs/mdcg/灵枢82工具_功能整理与迁移映射_v0.1.md`
|
|
6
6
|
§五 P2 缺口。
|
|
7
7
|
|
|
8
8
|
四件套
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""md_cg · 节点显式生命周期状态机(可学习优点 ②·显式状态机)
|
|
3
|
+
|
|
4
|
+
出处(pi):
|
|
5
|
+
运行操作 = 13 个**叶子显式状态机**(`session/types.ts:315-329`),drive 循环按
|
|
6
|
+
`at` 分发;一轮无任何进展即抛 `SessionInvariantError`(`drive.ts:101-104`)
|
|
7
|
+
——非法迁移在**结构上不可能**,而不是靠调用方自觉。
|
|
8
|
+
|
|
9
|
+
灵枢现状(本模块要收口的四处散落状态):
|
|
10
|
+
· `writelimit._demote` 用 **tag** `tidy:converged` 记「同构聚合已定型」
|
|
11
|
+
· `forgetting.reinforce` 用 **importance** 升降记「强化 / 弱化」
|
|
12
|
+
· `protect` 用 **fm.protected** 记「不可遗忘」
|
|
13
|
+
· `_evolution/ledger.md` 记账本
|
|
14
|
+
四种载体各说各话:谁都能把已定型节点再降一次权、把归档节点悄悄覆写成新的,
|
|
15
|
+
而**没有任何地方校验「这一步迁移合不合法」**。
|
|
16
|
+
|
|
17
|
+
本模块是该问题的唯一真源:状态集 `STATES` + 合法迁移表 `TRANSITIONS` + 单点裁决
|
|
18
|
+
`check()` + 单点推进 `set_state()`。口径:
|
|
19
|
+
|
|
20
|
+
· 状态落 **frontmatter.lifecycle_state**(节点 md 是唯一真源);索引快照带同名字
|
|
21
|
+
段 →「免读文件可查」(`_index.json` 即可回答「这节点什么状态」)。
|
|
22
|
+
**为何不叫 `state`**(对交接文档建议集的偏离,取证理由):`state` 在 md_cg 内
|
|
23
|
+
已被**裁决四态**(ACCEPT/REJECT/DEFER/BLINDSPOT)占用——`audit._verdict` 的返
|
|
24
|
+
回体、`judge_qualification()` 的返回、`mdcos.search` 结果项、检索结果条目的
|
|
25
|
+
`entry["state"]` 全都用它。若生命周期也叫 `state`,则同一外层结构里
|
|
26
|
+
`entry["state"]`(裁决)与 `entry["frontmatter"]["state"]`(生命周期)并存,
|
|
27
|
+
人类与 agent 读面必生歧义;本仓既有 `self_state` 即为同一动机的先例。
|
|
28
|
+
故取 `lifecycle_state`:语义自明、零歧义。
|
|
29
|
+
· 存量节点**缺 state 即视为 active**(`state_of` 的缺省),故不回填也能工作;
|
|
30
|
+
`backfill()` 提供一次性回填(幂等、可 dry-run)。
|
|
31
|
+
· **受保护节点豁免降级**:`protected`/`immutable` 节点不接受降级迁移,需
|
|
32
|
+
`override=True` 显式放行——「保护 = 不可遗忘」在状态层的落点。
|
|
33
|
+
· 非法迁移**拒绝**(负路由):`check()` 返回 `(ok, code, reason)`,`set_state()`
|
|
34
|
+
返回 `ok=False` + 机器可读 `error` 而不抛;写路径要硬拒用 `require_transition()`。
|
|
35
|
+
· 永不删除:`archived` 只是状态,节点文件仍在(可逆、可审计)。
|
|
36
|
+
· 幂等:同状态迁移是 no-op(不写盘、不留痕)——重复发起不会把历史刷爆。
|
|
37
|
+
|
|
38
|
+
状态语义(建议集,逐级降级 / 逐级回升):
|
|
39
|
+
active 正常:参与检索与写入面
|
|
40
|
+
converged 已定型:同构聚合的成员(writelimit tidy 的落点)
|
|
41
|
+
demoted 已降权:importance 被下调(forgetting 弱化的落点)
|
|
42
|
+
archived 已归档:不再参与默认检索,但文件保留、可显式恢复
|
|
43
|
+
"""
|
|
44
|
+
from __future__ import annotations
|
|
45
|
+
|
|
46
|
+
import os
|
|
47
|
+
import time
|
|
48
|
+
|
|
49
|
+
from .fsutil import append_jsonl
|
|
50
|
+
|
|
51
|
+
#: 状态全集。顺序即「降级方向」:active → converged → demoted → archived。
|
|
52
|
+
STATES = ("active", "converged", "demoted", "archived")
|
|
53
|
+
#: frontmatter 字段名(索引快照同名透出)。刻意避开 `state`——见模块 docstring
|
|
54
|
+
#: 「为何不叫 state」:该名已被裁决四态占用,同名会让读面产生歧义。
|
|
55
|
+
STATE_FIELD = "lifecycle_state"
|
|
56
|
+
#: 迁移留痕字段(frontmatter 侧历史,滚动保留最近 HISTORY_KEEP 条)
|
|
57
|
+
HISTORY_FIELD = "state_history"
|
|
58
|
+
HISTORY_KEEP = 20
|
|
59
|
+
#: 状态迁移审计(append-only,与 _forgetting.jsonl / _maintain.jsonl 同风格)
|
|
60
|
+
AUDIT_FILE = "_lifecycle.jsonl"
|
|
61
|
+
|
|
62
|
+
#: 降级序位(用于「受保护豁免降级」的判定)
|
|
63
|
+
_RANK = {"active": 0, "converged": 1, "demoted": 2, "archived": 3}
|
|
64
|
+
|
|
65
|
+
#: 合法迁移表——**逐级降级、逐级回升**,不跳级:
|
|
66
|
+
#: active ⇄ converged 定型 / 定型撤销
|
|
67
|
+
#: active → demoted 直接弱化(forgetting 降权)
|
|
68
|
+
#: converged → demoted 定型后再弱化
|
|
69
|
+
#: demoted ⇄ active 被强化回升 / 再弱化
|
|
70
|
+
#: demoted → archived 归档
|
|
71
|
+
#: archived → active 显式恢复
|
|
72
|
+
#: 刻意不收录的(即拒绝的):active→archived(跳级归档)、archived→converged /
|
|
73
|
+
#: archived→demoted(跳级回落)、converged→archived(跳级归档)。
|
|
74
|
+
TRANSITIONS = frozenset({
|
|
75
|
+
("active", "converged"),
|
|
76
|
+
("active", "demoted"),
|
|
77
|
+
("converged", "active"),
|
|
78
|
+
("converged", "demoted"),
|
|
79
|
+
("demoted", "active"),
|
|
80
|
+
("demoted", "archived"),
|
|
81
|
+
("archived", "active"),
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class TransitionError(ValueError):
|
|
86
|
+
"""非法生命周期迁移(写路径用 `require_transition` 直接抛这个)。"""
|
|
87
|
+
|
|
88
|
+
def __init__(self, src, dst, code, reason):
|
|
89
|
+
super().__init__(f"非法状态迁移 {src}→{dst}({code}):{reason}")
|
|
90
|
+
self.src, self.dst, self.code, self.reason = src, dst, code, reason
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# ---------------------------------------------------------------- 纯函数裁决
|
|
94
|
+
|
|
95
|
+
def state_of(fm) -> str:
|
|
96
|
+
"""frontmatter → 状态;缺字段或未知值 → "active"(存量兼容,不猜测)。"""
|
|
97
|
+
if not isinstance(fm, dict):
|
|
98
|
+
return "active"
|
|
99
|
+
s = fm.get(STATE_FIELD)
|
|
100
|
+
return s if s in STATES else "active"
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def is_downgrade(src: str, dst: str) -> bool:
|
|
104
|
+
"""是否向「更低」的状态迁移(active < converged < demoted < archived)。"""
|
|
105
|
+
return _RANK.get(dst, 0) > _RANK.get(src, 0)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def can_transition(src, dst) -> bool:
|
|
109
|
+
"""迁移是否合法(含幂等;未知 src 按 active 处理)。"""
|
|
110
|
+
src = src if src in STATES else "active"
|
|
111
|
+
if dst not in STATES:
|
|
112
|
+
return False
|
|
113
|
+
if src == dst:
|
|
114
|
+
return True # 幂等:同状态是 no-op
|
|
115
|
+
return (src, dst) in TRANSITIONS
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def check(src, dst, protected: bool = False, override: bool = False):
|
|
119
|
+
"""迁移合法性裁决 → `(ok, code, reason)`。**唯一裁决点**(纯函数,无 IO)。
|
|
120
|
+
|
|
121
|
+
code 取值:`ok` / `noop`(同状态,不写盘)/ `unknown_state` /
|
|
122
|
+
`illegal_transition` / `protected`。
|
|
123
|
+
"""
|
|
124
|
+
src = src if src in STATES else "active"
|
|
125
|
+
if dst not in STATES:
|
|
126
|
+
return False, "unknown_state", f"未知状态 {dst!r}(允许:{STATES})"
|
|
127
|
+
if src == dst:
|
|
128
|
+
return True, "noop", "状态未变(幂等,不写盘)"
|
|
129
|
+
if (src, dst) not in TRANSITIONS:
|
|
130
|
+
legal = "、".join(f"{a}→{b}" for a, b in sorted(TRANSITIONS))
|
|
131
|
+
return False, "illegal_transition", (
|
|
132
|
+
f"{src}→{dst} 不在合法迁移表内(允许:{legal})——"
|
|
133
|
+
"降级与回升都须逐级,跨级迁移一律拒绝")
|
|
134
|
+
if is_downgrade(src, dst) and protected and not override:
|
|
135
|
+
return False, "protected", (
|
|
136
|
+
f"受保护节点不接受降级迁移 {src}→{dst}(保护 = 不可遗忘);"
|
|
137
|
+
"确需降级请显式 override=True")
|
|
138
|
+
return True, "ok", f"{src}→{dst} 合法"
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def require_transition(src, dst, protected: bool = False, override: bool = False):
|
|
142
|
+
"""合法则返回 code,非法则抛 `TransitionError`(写路径的硬拒入口)。"""
|
|
143
|
+
ok, code, why = check(src, dst, protected=protected, override=override)
|
|
144
|
+
if not ok:
|
|
145
|
+
raise TransitionError(src if src in STATES else "active", dst, code, why)
|
|
146
|
+
return code
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def stamp(fm: dict, dst: str, reason: str = None, actor: str = None,
|
|
150
|
+
override: bool = False):
|
|
151
|
+
"""在给定 frontmatter 上**就地**推进状态(无 IO)→ `(ok, code, why)`。
|
|
152
|
+
|
|
153
|
+
与 `set_state()` 共用同一裁决点(`check`),供**需与其他字段同批落盘**的收
|
|
154
|
+
口点使用——`writelimit` 的降权要一次写盘改 tags/importance/状态,`forgetting`
|
|
155
|
+
的强化同理;否则状态得单独再写一次盘(同一节点两倍 IO)。
|
|
156
|
+
合法且非幂等时写入 `STATE_FIELD` 并追加 `HISTORY_FIELD`;失败/幂等不动 fm。
|
|
157
|
+
"""
|
|
158
|
+
src = state_of(fm)
|
|
159
|
+
prot = bool(fm.get("protected") or fm.get("immutable"))
|
|
160
|
+
ok, code, why = check(src, dst, protected=prot, override=override)
|
|
161
|
+
if not ok or code == "noop":
|
|
162
|
+
return ok, code, why
|
|
163
|
+
fm[STATE_FIELD] = dst
|
|
164
|
+
hist = list(fm.get(HISTORY_FIELD) or [])
|
|
165
|
+
hist.append({"at": time.time(), "from": src, "to": dst,
|
|
166
|
+
"reason": reason, "actor": actor})
|
|
167
|
+
fm[HISTORY_FIELD] = hist[-HISTORY_KEEP:]
|
|
168
|
+
return True, code, why
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
# ---------------------------------------------------------------- 索引同步
|
|
172
|
+
|
|
173
|
+
def _sync_index(cg, node_id, fm) -> None:
|
|
174
|
+
"""把 state 同步进索引快照(免读文件可查);无索引实现时静默跳过。
|
|
175
|
+
|
|
176
|
+
只同步 `state` 一个键:迁移历史已在 frontmatter 与 `_lifecycle.jsonl` 留痕,
|
|
177
|
+
放进索引会让快照无谓膨胀。
|
|
178
|
+
"""
|
|
179
|
+
idx = getattr(cg, "index", None)
|
|
180
|
+
if not isinstance(idx, dict):
|
|
181
|
+
return
|
|
182
|
+
e = (idx.get("nodes") or {}).get(node_id)
|
|
183
|
+
if e is None:
|
|
184
|
+
return
|
|
185
|
+
e[STATE_FIELD] = fm.get(STATE_FIELD)
|
|
186
|
+
dirty = getattr(cg, "_dirty", None)
|
|
187
|
+
if isinstance(dirty, dict):
|
|
188
|
+
dirty[node_id] = e
|
|
189
|
+
flush = getattr(cg, "flush", None)
|
|
190
|
+
if callable(flush):
|
|
191
|
+
flush()
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
# ---------------------------------------------------------------- 推进 / 回填
|
|
195
|
+
|
|
196
|
+
def set_state(cg, node_id: str, dst: str, reason: str = None,
|
|
197
|
+
actor: str = None, override: bool = False) -> dict:
|
|
198
|
+
"""推进一个节点的生命周期状态(**唯一推进入口**)。
|
|
199
|
+
|
|
200
|
+
非法迁移不走异常而是返回 `{"ok": False, "error": <code>, ...}`(负路由:
|
|
201
|
+
调用方自行决定降级处理;要硬拒请用 `require_transition`)。幂等迁移返回
|
|
202
|
+
`changed=False` 且不写盘。返回体始终带 `from` / `to` / `code` 便于审计。
|
|
203
|
+
"""
|
|
204
|
+
node = cg.get(node_id)
|
|
205
|
+
if not node:
|
|
206
|
+
return {"ok": False, "error": "node_not_found", "node_id": node_id}
|
|
207
|
+
fm = dict(node.get("frontmatter") or {})
|
|
208
|
+
src = state_of(fm)
|
|
209
|
+
ok, code, why = stamp(fm, dst, reason=reason, actor=actor, override=override)
|
|
210
|
+
base = {"node_id": node_id, "from": src, "to": dst, "code": code}
|
|
211
|
+
if not ok:
|
|
212
|
+
return {**base, "ok": False, "error": code, "reason": why}
|
|
213
|
+
if code == "noop":
|
|
214
|
+
return {**base, "ok": True, "changed": False, "reason": why}
|
|
215
|
+
at = fm[HISTORY_FIELD][-1]["at"]
|
|
216
|
+
cg._write_node(node_id, os.path.join(cg.root, node["path"]), fm,
|
|
217
|
+
node.get("content") or "")
|
|
218
|
+
_sync_index(cg, node_id, fm)
|
|
219
|
+
try:
|
|
220
|
+
append_jsonl(os.path.join(cg.root, AUDIT_FILE), {
|
|
221
|
+
"t": at, "action": "set_state", "node_id": node_id,
|
|
222
|
+
"from": src, "to": dst, "reason": reason, "actor": actor,
|
|
223
|
+
"override": bool(override)})
|
|
224
|
+
except Exception: # 审计失败不阻断状态推进
|
|
225
|
+
pass
|
|
226
|
+
return {**base, "ok": True, "changed": True, "reason": why, "at": at}
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
def backfill(cg, apply: bool = False, limit: int = 5000) -> dict:
|
|
230
|
+
"""存量回填:把缺 `state` 的节点显式补成 `active`(幂等)。
|
|
231
|
+
|
|
232
|
+
缺字段本就按 active 解释(`state_of`),故回填**不是让功能工作的前提**,
|
|
233
|
+
而是把「缺省」变成「显式」——索引/审计里从此可直接读到状态。
|
|
234
|
+
`apply=False`(默认)只盘点。写不动(文件取不回)的节点进 `failed` 如实上报。
|
|
235
|
+
"""
|
|
236
|
+
nodes = (getattr(cg, "index", None) or {}).get("nodes") or {}
|
|
237
|
+
missing = []
|
|
238
|
+
for nid, e in list(nodes.items()):
|
|
239
|
+
if len(missing) >= limit:
|
|
240
|
+
break
|
|
241
|
+
if (e or {}).get(STATE_FIELD) in STATES:
|
|
242
|
+
continue
|
|
243
|
+
missing.append(nid)
|
|
244
|
+
missing.sort()
|
|
245
|
+
if not apply:
|
|
246
|
+
return {"ok": True, "dry_run": True, "scanned": len(nodes),
|
|
247
|
+
"missing": len(missing), "planned": missing[:50]}
|
|
248
|
+
done, failed = [], []
|
|
249
|
+
for nid in missing:
|
|
250
|
+
node = cg.get(nid)
|
|
251
|
+
if not node:
|
|
252
|
+
failed.append(nid)
|
|
253
|
+
continue
|
|
254
|
+
fm = dict(node.get("frontmatter") or {})
|
|
255
|
+
fm[STATE_FIELD] = state_of(fm) # 缺省 active → 显式落盘
|
|
256
|
+
cg._write_node(nid, os.path.join(cg.root, node["path"]), fm,
|
|
257
|
+
node.get("content") or "")
|
|
258
|
+
_sync_index(cg, nid, fm)
|
|
259
|
+
done.append(nid)
|
|
260
|
+
return {"ok": True, "dry_run": False, "scanned": len(nodes),
|
|
261
|
+
"missing": len(missing), "backfilled": len(done),
|
|
262
|
+
"planned": done[:50], "failed": failed[:20]}
|