@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/metacognition.py
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
理论出处(本仓原文,非外部知识)
|
|
5
5
|
------------------------------
|
|
6
|
-
· **情绪 = 信息差的二阶变化 d²D/dt²**(`docs/智能的公理化基石.md` §十一,:412-510):
|
|
6
|
+
· **情绪 = 信息差的二阶变化 d²D/dt²**(`docs/theory/智能的公理化基石.md` §十一,:412-510):
|
|
7
7
|
「情绪不是状态本身,而是状态变化的变化」。元认知正是对认知过程做
|
|
8
8
|
二阶观测的单元:一阶 = 检索结果好坏,二阶 = 认知状态在如何变化。
|
|
9
9
|
· **情感 = 信任的二阶变化 d²T/dt²**(同文档 :469-508)。
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
· **推论三 局部不可知**(同文档 :277-288):
|
|
13
13
|
「知道什么不知道,同样属于知识」——盲区地图是法定输出,不是失败记录。
|
|
14
14
|
· **信任 P_trust / P_gap**(同文档 :393-408)。
|
|
15
|
-
· **导航税**(`docs/智能的认知过程.md`:84-86):维持认知过程一致性需要显式
|
|
15
|
+
· **导航税**(`docs/theory/智能的认知过程.md`:84-86):维持认知过程一致性需要显式
|
|
16
16
|
状态记录——元认知就是付这笔税的单元。
|
|
17
17
|
|
|
18
18
|
独立性的三重含义(本模块的硬约束)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""记忆评审流水线(M1 机械层 / M2 蜂巢并发 / M3 定位)。
|
|
3
|
+
|
|
4
|
+
真源:docs/记忆评审系统_立项设计与施工交接_20260915.md §3 / §5 / §6。
|
|
5
|
+
|
|
6
|
+
[1 候选生成] → [2 捆绑] → [3 规则装配] → [4 蜂巢评审] → [5 落库治理]
|
|
7
|
+
确定性 确定性 确定性 LLM(M2) 确定性(M4)
|
|
8
|
+
|
|
9
|
+
* candidates.py 级 1:四源候选(断言超限 / 闸门 DEFER / 冷节点 / 轮巡)
|
|
10
|
+
* bundle.py 级 2:按模板签名/血缘/主题捆绑(同模板组一包一评)
|
|
11
|
+
* ruleset.py 级 3:D3 规则装配(规则全落 rules/*.json,引擎零改动)
|
|
12
|
+
* pipeline.py M2 级 4+5:投递收卷(蜂巢)+ 意见落库(verify 令牌 + writepipe)
|
|
13
|
+
* locate.py M3 D1:字段级定位——意见说「有问题」,定位说「在第几个字段/哪一段」
|
|
14
|
+
* govern.py M4 落库治理(确定性部分):规则驱动 · 只回填 · 永不删除
|
|
15
|
+
plan(预演)/ apply(执行 + 留痕)/ rollback(按留痕回滚)/ history / stats
|
|
16
|
+
|
|
17
|
+
边界:M1/M3 只读不写;机械可算的在蜂巢 job 前跑完(LLM 只看语义问题);
|
|
18
|
+
本条流水线不产出「删除」动作(治理只有合并/降权/回填/移层,M4 终裁)。
|
|
19
|
+
M4 只做**确定性**部分;依赖语义判断的合并/降权建议(LLM 面)不在本模块。
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
__all__ = ["candidates", "bundle", "ruleset", "pipeline", "locate", "govern",
|
|
23
|
+
"SPEC_VERSION"]
|
|
24
|
+
|
|
25
|
+
SPEC_VERSION = 1
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""M1 机械层入口(零写入):
|
|
3
|
+
|
|
4
|
+
python -m md_cg.mreview [--root R] [--out dump.json] [--full] [--limit N]
|
|
5
|
+
|
|
6
|
+
默认只打印/落盘**摘要 + 包清单 + 机械聚合**;``--full`` 追加条目明细与逐条机械 issue
|
|
7
|
+
(真源全量 dump 会很大,默认裁剪是为可审查性服务,不是隐藏内容)。
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import argparse
|
|
12
|
+
import collections
|
|
13
|
+
import json
|
|
14
|
+
import os
|
|
15
|
+
|
|
16
|
+
from .. import conformance as CF
|
|
17
|
+
from ..datapath import mdcg_root
|
|
18
|
+
from . import bundle as BD
|
|
19
|
+
from . import candidates as CD
|
|
20
|
+
from . import ruleset as RS
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def run(root=None, *, sources=CD.SOURCES, cold_limit=500, patrol_window=200,
|
|
24
|
+
max_per_bundle=50, limit=None, patrol_offset=None, rules_dir=None,
|
|
25
|
+
full=False) -> dict:
|
|
26
|
+
root = root or mdcg_root()
|
|
27
|
+
nodes = CF.load_index(root) or {} # load_index 直接返回 nodes 字典
|
|
28
|
+
gen = CD.generate(root, sources=sources, nodes=nodes, limit=limit,
|
|
29
|
+
cold_limit=cold_limit, patrol_window=patrol_window,
|
|
30
|
+
patrol_offset=patrol_offset, with_report=True)
|
|
31
|
+
bd = BD.bundle(gen["candidates"], nodes=nodes, root=root,
|
|
32
|
+
max_per_bundle=max_per_bundle)
|
|
33
|
+
asm = RS.assemble_all(bd, rules_dir=rules_dir)
|
|
34
|
+
by_rule = collections.Counter(i["rule_id"] for p in asm["packages"] for i in p["mechanical"])
|
|
35
|
+
out = {
|
|
36
|
+
"root": root,
|
|
37
|
+
"candidates": {"by_source": gen["by_source"], "total_raw": gen["total_raw"],
|
|
38
|
+
"total": gen["total"], "skipped": gen["skipped"]},
|
|
39
|
+
"report": gen.get("report"),
|
|
40
|
+
"assertions": gen.get("assertions"),
|
|
41
|
+
"bundles": [{"bundle_id": b["bundle_id"], "group_kind": b["group_kind"],
|
|
42
|
+
"group_key": b["group_key"], "size": b["size"], "refs": b["refs"]}
|
|
43
|
+
for b in bd["bundles"]],
|
|
44
|
+
"bundle_stats": bd["stats"],
|
|
45
|
+
"content_missing": bd["content_missing"],
|
|
46
|
+
"assemble_stats": asm["stats"],
|
|
47
|
+
"mechanical_by_rule": dict(by_rule),
|
|
48
|
+
"llm_checks": sorted({(c["rule_id"], c["check"]) for p in asm["packages"]
|
|
49
|
+
for c in p["llm"]}),
|
|
50
|
+
}
|
|
51
|
+
if full:
|
|
52
|
+
out["candidates_full"] = gen["candidates"]
|
|
53
|
+
out["bundles_full"] = bd["bundles"]
|
|
54
|
+
out["packages_full"] = asm["packages"]
|
|
55
|
+
return out
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _fmt_summary(d: dict) -> str:
|
|
59
|
+
c, b, a = d["candidates"], d["bundle_stats"], d["assemble_stats"]
|
|
60
|
+
lines = ["", "== M1 机械层(候选 → 捆绑 → 规则装配) ==",
|
|
61
|
+
"真源:%s" % d["root"],
|
|
62
|
+
"候选:原始 %d 条 → 去重后 %d 条(按源:%s)"
|
|
63
|
+
% (c["total_raw"], c["total"],
|
|
64
|
+
", ".join("%s=%d" % kv for kv in sorted(c["by_source"].items()))),
|
|
65
|
+
"跳过(未落盘/无节点):%d 条" % len(c["skipped"]),
|
|
66
|
+
"捆绑:%d 包(最大 %d 条/包;分组类型 %s)"
|
|
67
|
+
% (b["bundles"], b["largest"], b["by_group_kind"]),
|
|
68
|
+
"正文缺失:%d 条(读盘失败计入,不冒充内容)" % d["content_missing"],
|
|
69
|
+
"规则:%d 条(%s)" % (len(a["rules"]), ", ".join(a["rules"])),
|
|
70
|
+
"机械检查:%d 条 issue(按类 %s)" % (a["mechanical_total"], a["mechanical_by_kind"]),
|
|
71
|
+
"待 LLM 检查项:%d 条(%s)"
|
|
72
|
+
% (a["llm_checks"], ", ".join("%s:%s" % t for t in d["llm_checks"]))]
|
|
73
|
+
asr = d.get("assertions") or {}
|
|
74
|
+
if asr:
|
|
75
|
+
lines.append("断言集交叉核对:verdict=%s,未达标项 %s"
|
|
76
|
+
% (asr.get("verdict"), ", ".join(asr.get("failed") or []) or "无"))
|
|
77
|
+
return "\n".join(lines)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def main(argv=None) -> int:
|
|
81
|
+
ap = argparse.ArgumentParser(prog="python -m md_cg.mreview",
|
|
82
|
+
description="M1 机械层:候选生成→捆绑→规则装配(只读零写入)")
|
|
83
|
+
ap.add_argument("--root", default=None, help="认知图根(缺省 MDCG_ROOT/默认路径)")
|
|
84
|
+
ap.add_argument("--sources", default=",".join(CD.SOURCES))
|
|
85
|
+
ap.add_argument("--limit", type=int, default=None)
|
|
86
|
+
ap.add_argument("--cold-limit", type=int, default=500)
|
|
87
|
+
ap.add_argument("--patrol-window", type=int, default=200)
|
|
88
|
+
ap.add_argument("--patrol-offset", type=int, default=None)
|
|
89
|
+
ap.add_argument("--max-per-bundle", type=int, default=50)
|
|
90
|
+
ap.add_argument("--rules-dir", default=None)
|
|
91
|
+
ap.add_argument("--out", default=None, help="落盘 dump(JSON)")
|
|
92
|
+
ap.add_argument("--full", action="store_true", help="含条目明细与逐条 issue")
|
|
93
|
+
a = ap.parse_args(argv)
|
|
94
|
+
d = run(a.root, sources=tuple(s for s in a.sources.split(",") if s),
|
|
95
|
+
cold_limit=a.cold_limit, patrol_window=a.patrol_window, limit=a.limit,
|
|
96
|
+
patrol_offset=a.patrol_offset, max_per_bundle=a.max_per_bundle,
|
|
97
|
+
rules_dir=a.rules_dir, full=a.full)
|
|
98
|
+
print(_fmt_summary(d))
|
|
99
|
+
if a.out:
|
|
100
|
+
with open(os.path.abspath(a.out), "w", encoding="utf-8") as f:
|
|
101
|
+
json.dump(d, f, ensure_ascii=False, indent=1)
|
|
102
|
+
print("已落盘:%s" % os.path.abspath(a.out))
|
|
103
|
+
return 0
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
if __name__ == "__main__":
|
|
107
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""记忆评审流水线 · 级 2:捆绑(确定性,零写入)。
|
|
3
|
+
|
|
4
|
+
真源:docs/记忆评审系统_立项设计与施工交接_20260915.md §3 第 2 级。
|
|
5
|
+
|
|
6
|
+
**同模板组一包一评**:批次报告类节点(「批次N收官记忆」)正文各不相同但同模板,
|
|
7
|
+
逐条评审=重复劳动且立场漂移;成组评审=一次裁决整组。分组键优先级:
|
|
8
|
+
|
|
9
|
+
1. ``template`` 模板签名——复用 :func:`writelimit.template_signature`(强信号 ``t:``);
|
|
10
|
+
长文(≥ MAX_CONTENT)无标题签名时回退**首行骨架**(弱信号 ``h:``)——真源知识节点
|
|
11
|
+
正文多为长文四要素,标题/首行才是同模板流水的载体(该回退只在评审分组内生效,
|
|
12
|
+
不改写侧聚合口径)。
|
|
13
|
+
2. ``lineage`` 血缘——branch_id / branched_from / derived_from[0](同源迁移一起看)。
|
|
14
|
+
3. ``topic`` 标签前缀集合(无标签回退层)。
|
|
15
|
+
4. ``batch`` 无任何分组特征的孤立候选,按确定性顺序成批(不误聚、不丢弃)。
|
|
16
|
+
|
|
17
|
+
组内条数 < ``min_group`` 的组降级并入 batch;组内超 ``max_per_bundle`` 切分多包。
|
|
18
|
+
包号确定性:``b1_<kind>_<sha1(key)[:8]>_c<idx>``;同一输入两次运行逐字节一致。
|
|
19
|
+
"""
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import collections
|
|
23
|
+
import hashlib
|
|
24
|
+
import os
|
|
25
|
+
|
|
26
|
+
from .. import conformance as CF
|
|
27
|
+
from .. import writelimit as WL
|
|
28
|
+
|
|
29
|
+
GROUP_TEMPLATE = "template"
|
|
30
|
+
GROUP_LINEAGE = "lineage"
|
|
31
|
+
GROUP_TOPIC = "topic"
|
|
32
|
+
GROUP_BATCH = "batch"
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _sig_of(content: str):
|
|
36
|
+
"""模板签名(强/弱)。返回 (sig, kind) 或 (None, None)。"""
|
|
37
|
+
if not content:
|
|
38
|
+
return None, None
|
|
39
|
+
s = WL.template_signature(content)
|
|
40
|
+
if s:
|
|
41
|
+
return s, "strong"
|
|
42
|
+
for line in content.splitlines():
|
|
43
|
+
t = line.strip()
|
|
44
|
+
if not t:
|
|
45
|
+
continue
|
|
46
|
+
sk = WL._skeleton(t)
|
|
47
|
+
if len(sk) >= WL.MIN_SKELETON:
|
|
48
|
+
return "h:" + sk, "head"
|
|
49
|
+
return None, None
|
|
50
|
+
return None, None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _lineage_key(meta: dict):
|
|
54
|
+
for f in ("branched_from", "branch_id"):
|
|
55
|
+
v = meta.get(f)
|
|
56
|
+
if v and str(v) != "None":
|
|
57
|
+
return str(v)
|
|
58
|
+
d = meta.get("derived_from")
|
|
59
|
+
if isinstance(d, (list, tuple)) and d:
|
|
60
|
+
return str(d[0])
|
|
61
|
+
if isinstance(d, str) and d and d not in ("[]", "None"):
|
|
62
|
+
return d
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _topic_key(meta: dict, tags: list):
|
|
67
|
+
pref = sorted(CF._tag_prefixes(meta)) if meta else []
|
|
68
|
+
if not pref and tags:
|
|
69
|
+
pref = sorted({str(t).split(":", 1)[0] for t in tags if t})
|
|
70
|
+
if pref:
|
|
71
|
+
return "+".join(pref)
|
|
72
|
+
layer = str((meta or {}).get("layer") or "")
|
|
73
|
+
return ("layer:" + layer) if layer else None
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _group_of(meta: dict, tags: list, content: str):
|
|
77
|
+
sig, kind = _sig_of(content)
|
|
78
|
+
if sig:
|
|
79
|
+
return GROUP_TEMPLATE, sig, kind
|
|
80
|
+
lin = _lineage_key(meta)
|
|
81
|
+
if lin:
|
|
82
|
+
return GROUP_LINEAGE, lin, None
|
|
83
|
+
top = _topic_key(meta, tags)
|
|
84
|
+
if top:
|
|
85
|
+
return GROUP_TOPIC, top, None
|
|
86
|
+
return GROUP_BATCH, "misc", None
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _load_content(root: str, meta: dict, limit: int):
|
|
90
|
+
if not root or not meta:
|
|
91
|
+
return None
|
|
92
|
+
rel = str(meta.get("path") or "")
|
|
93
|
+
if not rel:
|
|
94
|
+
return None
|
|
95
|
+
p = os.path.join(root, rel.replace("\\", os.sep).replace("/", os.sep))
|
|
96
|
+
try:
|
|
97
|
+
with open(p, encoding="utf-8", errors="replace") as f:
|
|
98
|
+
return f.read(int(limit))
|
|
99
|
+
except OSError:
|
|
100
|
+
return None
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _entry(cand: dict, meta: dict, content: str, limit: int) -> dict:
|
|
104
|
+
return {"ref": cand.get("key"), "node_id": cand.get("node_id"),
|
|
105
|
+
"proposal_id": cand.get("proposal_id"), "origin": cand.get("origin"),
|
|
106
|
+
"layer": cand.get("layer"), "tags": list(cand.get("tags") or []),
|
|
107
|
+
"role": (meta or {}).get("role"),
|
|
108
|
+
"importance": (meta or {}).get("importance"),
|
|
109
|
+
"evidence_count": (meta or {}).get("evidence_count"),
|
|
110
|
+
"verification_basis": (meta or {}).get("verification_basis"),
|
|
111
|
+
"lifecycle_state": (meta or {}).get("lifecycle_state"),
|
|
112
|
+
"content_hash": cand.get("content_hash") or (meta or {}).get("content_hash"),
|
|
113
|
+
"issue_kinds": list(cand.get("issue_kinds") or []),
|
|
114
|
+
"evidence": list(cand.get("evidence") or []),
|
|
115
|
+
"excerpt": (content or "")[:int(limit)] or None}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def _chunks(ids: list, size: int) -> list:
|
|
119
|
+
size = max(1, int(size))
|
|
120
|
+
return [ids[i:i + size] for i in range(0, len(ids), size)]
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def bundle(candidates: list, nodes=None, root=None, *, max_per_bundle=50,
|
|
124
|
+
min_group=2, read_content=True, content_limit=3000) -> dict:
|
|
125
|
+
"""候选清单 → 待评包(含条目上下文与机械字段,供级 3 装配与级 4 spec)。"""
|
|
126
|
+
nodes = nodes or {}
|
|
127
|
+
bykey = {c.get("key"): c for c in candidates}
|
|
128
|
+
groups, contents = {}, {}
|
|
129
|
+
for c in candidates:
|
|
130
|
+
nid = c.get("node_id")
|
|
131
|
+
meta = (nodes.get(nid) or {}) if nid else {}
|
|
132
|
+
content = c.get("content") or ""
|
|
133
|
+
if not content and read_content and nid:
|
|
134
|
+
content = _load_content(root, meta, content_limit) or ""
|
|
135
|
+
content = (content or "")[:int(content_limit)]
|
|
136
|
+
kind, key, sig_kind = _group_of(meta, c.get("tags") or [], content)
|
|
137
|
+
g = groups.get((kind, key))
|
|
138
|
+
if g is None:
|
|
139
|
+
g = groups[(kind, key)] = {"ids": [], "sig_kind": sig_kind}
|
|
140
|
+
g["ids"].append(c.get("key"))
|
|
141
|
+
contents[c.get("key")] = content
|
|
142
|
+
|
|
143
|
+
# 小组降级并入 batch(不误聚、不丢弃)
|
|
144
|
+
demoted = []
|
|
145
|
+
for k in [k for k, g in groups.items()
|
|
146
|
+
if k[0] != GROUP_BATCH and len(g["ids"]) < int(min_group)]:
|
|
147
|
+
demoted += groups.pop(k)["ids"]
|
|
148
|
+
for k in [k for k in groups if k[0] == GROUP_BATCH]:
|
|
149
|
+
demoted += groups.pop(k)["ids"]
|
|
150
|
+
demoted = sorted(demoted)
|
|
151
|
+
if demoted:
|
|
152
|
+
groups[(GROUP_BATCH, "misc")] = {"ids": demoted, "sig_kind": None}
|
|
153
|
+
|
|
154
|
+
bundles = []
|
|
155
|
+
for (kind, key) in sorted(groups):
|
|
156
|
+
g = groups[(kind, key)]
|
|
157
|
+
for i, chunk in enumerate(_chunks(sorted(g["ids"]), int(max_per_bundle))):
|
|
158
|
+
bid = "b1_%s_%s_c%d" % (kind, hashlib.sha1(str(key).encode("utf-8")).hexdigest()[:8], i)
|
|
159
|
+
bundles.append({
|
|
160
|
+
"bundle_id": bid, "group_kind": kind, "group_key": str(key),
|
|
161
|
+
"group_sig_kind": g["sig_kind"], "size": len(chunk), "refs": chunk,
|
|
162
|
+
"entries": [_entry(bykey[r], nodes.get(bykey[r].get("node_id") or "") or {},
|
|
163
|
+
contents.get(r) or "", content_limit)
|
|
164
|
+
for r in chunk]})
|
|
165
|
+
stats = {"candidates": len(candidates), "bundles": len(bundles),
|
|
166
|
+
"largest": max([b["size"] for b in bundles] or [0]),
|
|
167
|
+
"by_group_kind": dict(collections.Counter(b["group_kind"] for b in bundles))}
|
|
168
|
+
return {"bundles": bundles, "stats": stats,
|
|
169
|
+
"content_missing": sum(1 for c in candidates
|
|
170
|
+
if not contents.get(c.get("key")))}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""记忆评审流水线 · 级 1:候选生成(确定性,零写入)。
|
|
3
|
+
|
|
4
|
+
真源:docs/记忆评审系统_立项设计与施工交接_20260915.md §3 第 1 级。
|
|
5
|
+
|
|
6
|
+
四源(可单独关闭):
|
|
7
|
+
|
|
8
|
+
* ``assertion`` 断言集超限项的**条目级展开**——复用 conformance 的**同一判据函数**
|
|
9
|
+
(`_coverage_metrics` / `unanalyzed` / `_dup_metrics` / `_edge_metrics`),
|
|
10
|
+
保证「报告聚合数」与「条目清单」两个入口同口径(见 test_mr_m1 T4)。
|
|
11
|
+
* ``defer_queue`` 闸门待裁决提案(inbox 未被 decisions 覆盖)+遗忘日志 DEFER 留痕;
|
|
12
|
+
未落盘的 node_id 计入 ``skipped``,**如实上报不冒充候选**。
|
|
13
|
+
* ``cold`` access log 触达集合之外、且重要度不低的节点(久未触达者更值得评审)。
|
|
14
|
+
* ``patrol`` sha1(node_id) 排序切片轮巡(offset 决定窗口位置),多轮不重不漏。
|
|
15
|
+
|
|
16
|
+
纪律:零写入;输出顺序由 (优先级, node_id) 决定;每条候选必带证据。
|
|
17
|
+
"""
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import collections
|
|
21
|
+
import hashlib
|
|
22
|
+
import json
|
|
23
|
+
import os
|
|
24
|
+
import time
|
|
25
|
+
|
|
26
|
+
from .. import conformance as CF
|
|
27
|
+
from ..datapath import mdcg_root
|
|
28
|
+
|
|
29
|
+
SOURCES = ("assertion", "defer_queue", "cold", "patrol")
|
|
30
|
+
_PRIORITY = {"assertion": 0, "defer_queue": 1, "cold": 2, "patrol": 3}
|
|
31
|
+
FORGET_LOG = "_forgetting.jsonl"
|
|
32
|
+
PATROL_EPOCH = 3600.0 # 轮巡窗口推进周期(秒):默认每窗口周期轮转一格
|
|
33
|
+
COLD_IMPORTANCE_MIN = 0.6 # 冷节点抽样门槛(importance 解析失败按 0 计)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _read_jsonl(path: str) -> list:
|
|
37
|
+
if not os.path.exists(path):
|
|
38
|
+
return []
|
|
39
|
+
out = []
|
|
40
|
+
with open(path, encoding="utf-8", errors="replace") as f:
|
|
41
|
+
for line in f:
|
|
42
|
+
line = line.strip()
|
|
43
|
+
if not line:
|
|
44
|
+
continue
|
|
45
|
+
try:
|
|
46
|
+
out.append(json.loads(line))
|
|
47
|
+
except json.JSONDecodeError:
|
|
48
|
+
continue
|
|
49
|
+
return out
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _touched(root: str, nodes: dict) -> set:
|
|
53
|
+
"""access log 触达集(与 conformance._reach_metrics 同一解析口径:ids / id / node_id / nid)。"""
|
|
54
|
+
p = os.path.join(root, CF.ACCESS_LOG)
|
|
55
|
+
seen = set()
|
|
56
|
+
if not os.path.exists(p):
|
|
57
|
+
return seen
|
|
58
|
+
with open(p, encoding="utf-8", errors="replace") as f:
|
|
59
|
+
for line in f:
|
|
60
|
+
line = line.strip()
|
|
61
|
+
if not line:
|
|
62
|
+
continue
|
|
63
|
+
try:
|
|
64
|
+
rec = json.loads(line)
|
|
65
|
+
except json.JSONDecodeError:
|
|
66
|
+
continue
|
|
67
|
+
got = rec.get("ids")
|
|
68
|
+
if isinstance(got, (list, tuple)):
|
|
69
|
+
seen.update(str(x) for x in got if x)
|
|
70
|
+
nid = rec.get("id") or rec.get("node_id") or rec.get("nid")
|
|
71
|
+
if nid:
|
|
72
|
+
seen.add(str(nid))
|
|
73
|
+
return seen & set(nodes)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _assertion_items(nodes: dict) -> list:
|
|
77
|
+
"""断言集超限项 → 条目级(判据函数与 conformance 同源,不另立口径)。"""
|
|
78
|
+
out = []
|
|
79
|
+
kn = {nid: r for nid, r in nodes.items() if str(r.get("layer")) == "knowledge"}
|
|
80
|
+
for nid, r in kn.items():
|
|
81
|
+
if not r.get("role"):
|
|
82
|
+
out.append({"node_id": nid, "issue_kind": "missing_field",
|
|
83
|
+
"evidence": "role 为空(口径=conformance.stratum.role_coverage,knowledge 层分母)"})
|
|
84
|
+
if CF._as_int(r.get("evidence_count")) == 0:
|
|
85
|
+
out.append({"node_id": nid, "issue_kind": "missing_field",
|
|
86
|
+
"evidence": "evidence_count=0(口径=conformance.stratum.evidence_coverage)"})
|
|
87
|
+
if {"doc", "code"} & CF._tag_prefixes(r):
|
|
88
|
+
out.append({"node_id": nid, "issue_kind": "mixed_layer",
|
|
89
|
+
"evidence": "knowledge 层带 doc/code 标签(口径=conformance.stratum.mixed_ratio)"})
|
|
90
|
+
for nid, r in nodes.items():
|
|
91
|
+
if (CF._as_int(r.get("evidence_count")) == 0 and not CF._basis_of(r)
|
|
92
|
+
and not r.get("lifecycle_state")):
|
|
93
|
+
out.append({"node_id": nid, "issue_kind": "unanalyzed",
|
|
94
|
+
"evidence": "无一维分析痕迹(口径=conformance.unanalyzed 派生判据)"})
|
|
95
|
+
ch = collections.Counter(str(r.get("content_hash")) for r in nodes.values())
|
|
96
|
+
dup = {h for h, c in ch.items() if c > 1 and h not in ("None", "")}
|
|
97
|
+
if dup:
|
|
98
|
+
for nid, r in nodes.items():
|
|
99
|
+
h = str(r.get("content_hash"))
|
|
100
|
+
if h in dup:
|
|
101
|
+
out.append({"node_id": nid, "issue_kind": "dup_content",
|
|
102
|
+
"evidence": "content_hash 重复组 %s(%d 条同指纹;口径=conformance.dup.content)" % (h, ch[h])})
|
|
103
|
+
for nid, r in nodes.items():
|
|
104
|
+
es = r.get("edges") or []
|
|
105
|
+
if not isinstance(es, (list, tuple)):
|
|
106
|
+
continue
|
|
107
|
+
for e in es:
|
|
108
|
+
if not isinstance(e, dict):
|
|
109
|
+
continue
|
|
110
|
+
keys = [k for k in CF.CANONICAL_EDGE_KEYS + ("type",) if e.get(k)]
|
|
111
|
+
if not any(k in CF.CANONICAL_EDGE_KEYS for k in keys):
|
|
112
|
+
out.append({"node_id": nid, "issue_kind": "edge_non_canonical",
|
|
113
|
+
"evidence": "边键非规范:%s(口径=conformance.edge.key.canonical)"
|
|
114
|
+
% ("+".join(keys) or "<无关系键>")})
|
|
115
|
+
break
|
|
116
|
+
return out
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _defer_items(root: str, nodes: dict):
|
|
120
|
+
"""闸门待裁决提案 + 遗忘日志 DEFER 留痕。返回 (items, skipped)。"""
|
|
121
|
+
ib = _read_jsonl(os.path.join(root, CF.INBOX_LOG))
|
|
122
|
+
dc = _read_jsonl(os.path.join(root, CF.DECISION_LOG))
|
|
123
|
+
done = {str(x.get("pid")) for x in dc if x.get("pid")}
|
|
124
|
+
out, skipped = [], []
|
|
125
|
+
for r in ib:
|
|
126
|
+
pid = str(r.get("pid") or "")
|
|
127
|
+
if not pid or pid in done:
|
|
128
|
+
continue
|
|
129
|
+
out.append({"node_id": str(r.get("id") or "") or None, "origin": "proposal",
|
|
130
|
+
"proposal_id": pid, "issue_kind": "gate_defer",
|
|
131
|
+
"layer": r.get("layer"), "content": r.get("content") or "",
|
|
132
|
+
"tags": r.get("tags") or [], "content_hash": r.get("payload_hash") or "",
|
|
133
|
+
"evidence": "闸门待裁决提案(inbox 中 %s 未被 decisions 覆盖)" % pid})
|
|
134
|
+
for r in _read_jsonl(os.path.join(root, FORGET_LOG)):
|
|
135
|
+
if str(r.get("verdict")) != "DEFER":
|
|
136
|
+
continue
|
|
137
|
+
nid = str(r.get("node_id") or "")
|
|
138
|
+
if nid and nid in nodes:
|
|
139
|
+
out.append({"node_id": nid, "issue_kind": "gate_defer",
|
|
140
|
+
"evidence": "遗忘闸门 DEFER 留痕:%s" % str(r.get("reason"))})
|
|
141
|
+
else:
|
|
142
|
+
skipped.append({"source": "defer_queue", "reason": "node_not_landed",
|
|
143
|
+
"detail": nid or str(r.get("reason"))})
|
|
144
|
+
return out, skipped
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _cold_items(root: str, nodes: dict, limit: int) -> list:
|
|
148
|
+
seen = _touched(root, nodes)
|
|
149
|
+
|
|
150
|
+
def _imp(r):
|
|
151
|
+
try:
|
|
152
|
+
return float(r.get("importance") or 0.0)
|
|
153
|
+
except (TypeError, ValueError):
|
|
154
|
+
return 0.0
|
|
155
|
+
|
|
156
|
+
rows = [(nid, r) for nid, r in nodes.items() if nid not in seen]
|
|
157
|
+
rows.sort(key=lambda kv: (-_imp(kv[1]), kv[0]))
|
|
158
|
+
picked = [(nid, r) for nid, r in rows if _imp(r) >= COLD_IMPORTANCE_MIN][:int(limit)]
|
|
159
|
+
return [{"node_id": nid, "issue_kind": "cold",
|
|
160
|
+
"evidence": "access log 未触达(importance=%.2f,排名第 %d)" % (_imp(r), i + 1)}
|
|
161
|
+
for i, (nid, r) in enumerate(picked)]
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _patrol_items(nodes: dict, window: int, offset: int) -> list:
|
|
165
|
+
if int(window) <= 0 or not nodes:
|
|
166
|
+
return []
|
|
167
|
+
ordered = sorted(nodes, key=lambda nid: hashlib.sha1(nid.encode("utf-8")).hexdigest())
|
|
168
|
+
n = len(ordered)
|
|
169
|
+
start = (int(offset) * int(window)) % n
|
|
170
|
+
picked = ordered[start:start + int(window)]
|
|
171
|
+
if len(picked) < int(window): # 环绕补足:多轮不重不漏
|
|
172
|
+
picked = picked + ordered[:int(window) - len(picked)]
|
|
173
|
+
return [{"node_id": nid, "issue_kind": "patrol",
|
|
174
|
+
"evidence": "轮巡窗口 #%d(窗口 %d 条,按 sha1(node_id) 排序切片)" % (int(offset), int(window))}
|
|
175
|
+
for nid in picked]
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _merge(items: list, nodes: dict) -> list:
|
|
179
|
+
"""按条目(节点/提案)聚合:一条候选 = 一个待评条目,多个问题挂在 issue_kinds 上。"""
|
|
180
|
+
acc = {}
|
|
181
|
+
for it in items:
|
|
182
|
+
nid = it.get("node_id")
|
|
183
|
+
key = str(nid) if nid else "pid:" + str(it.get("proposal_id"))
|
|
184
|
+
rec = acc.get(key)
|
|
185
|
+
if rec is None:
|
|
186
|
+
meta = nodes.get(nid) or {} if nid else {}
|
|
187
|
+
rec = acc[key] = {
|
|
188
|
+
"key": key, "node_id": nid or None, "proposal_id": it.get("proposal_id") or None,
|
|
189
|
+
"origin": it.get("origin") or "node",
|
|
190
|
+
"layer": it.get("layer") or meta.get("layer"),
|
|
191
|
+
"sources": [], "issue_kinds": [], "evidence": [],
|
|
192
|
+
"content": it.get("content") or "",
|
|
193
|
+
"tags": list(it.get("tags") or meta.get("tags") or []),
|
|
194
|
+
"content_hash": it.get("content_hash") or str(meta.get("content_hash") or ""),
|
|
195
|
+
}
|
|
196
|
+
src = it.get("source")
|
|
197
|
+
if src and src not in rec["sources"]:
|
|
198
|
+
rec["sources"].append(src)
|
|
199
|
+
ik = it.get("issue_kind")
|
|
200
|
+
if ik and ik not in rec["issue_kinds"]:
|
|
201
|
+
rec["issue_kinds"].append(ik)
|
|
202
|
+
ev = it.get("evidence")
|
|
203
|
+
if ev and len(rec["evidence"]) < 8:
|
|
204
|
+
rec["evidence"].append(ev)
|
|
205
|
+
out = list(acc.values())
|
|
206
|
+
for r in out:
|
|
207
|
+
r["sources"] = sorted(set(r["sources"])) or ["-"]
|
|
208
|
+
r["priority"] = min(_PRIORITY.get(s, 9) for s in r["sources"])
|
|
209
|
+
r["issue_kinds"] = sorted(r["issue_kinds"])
|
|
210
|
+
out.sort(key=lambda r: (r["priority"], str(r["key"])))
|
|
211
|
+
return out
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
def generate(root=None, *, sources=SOURCES, nodes=None, limit=None, cold_limit=500,
|
|
215
|
+
patrol_window=200, patrol_offset=None, with_report=False, now=None) -> dict:
|
|
216
|
+
"""四源候选生成。nodes 可注入(测试用合成库,免读真源)。"""
|
|
217
|
+
root = root or mdcg_root()
|
|
218
|
+
if nodes is None:
|
|
219
|
+
nodes = CF.load_index(root) or {} # load_index 直接返回 nodes 字典
|
|
220
|
+
wanted = [s for s in SOURCES if s in (sources or SOURCES)]
|
|
221
|
+
items, skipped, by_source = [], [], {}
|
|
222
|
+
if "assertion" in wanted:
|
|
223
|
+
got = _assertion_items(nodes)
|
|
224
|
+
by_source["assertion"] = len(got)
|
|
225
|
+
items += [dict(x, source="assertion") for x in got]
|
|
226
|
+
if "defer_queue" in wanted:
|
|
227
|
+
got, sk = _defer_items(root, nodes)
|
|
228
|
+
by_source["defer_queue"] = len(got)
|
|
229
|
+
items += [dict(x, source="defer_queue") for x in got]
|
|
230
|
+
skipped += sk
|
|
231
|
+
if "cold" in wanted:
|
|
232
|
+
got = _cold_items(root, nodes, cold_limit)
|
|
233
|
+
by_source["cold"] = len(got)
|
|
234
|
+
items += [dict(x, source="cold") for x in got]
|
|
235
|
+
if "patrol" in wanted:
|
|
236
|
+
off = int(patrol_offset) if patrol_offset is not None \
|
|
237
|
+
else int((now if now is not None else time.time()) // PATROL_EPOCH)
|
|
238
|
+
got = _patrol_items(nodes, patrol_window, off)
|
|
239
|
+
by_source["patrol"] = len(got)
|
|
240
|
+
items += [dict(x, source="patrol") for x in got]
|
|
241
|
+
merged = _merge(items, nodes)
|
|
242
|
+
if limit:
|
|
243
|
+
merged = merged[:int(limit)]
|
|
244
|
+
rep = {"root": root, "generated_at": float(now if now is not None else time.time()),
|
|
245
|
+
"sources": wanted, "by_source": by_source, "total_raw": len(items),
|
|
246
|
+
"total": len(merged), "candidates": merged, "skipped": skipped}
|
|
247
|
+
if with_report:
|
|
248
|
+
rep0 = CF.check(root, check_paths=False)
|
|
249
|
+
rep["report"] = CF.metrics_of(rep0)
|
|
250
|
+
rep["assertions"] = {"verdict": rep0.get("verdict"), "counts": rep0.get("counts"),
|
|
251
|
+
"failed": [c.get("id") for c in rep0.get("checks") or []
|
|
252
|
+
if not c.get("ok")]}
|
|
253
|
+
return rep
|