@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
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""comment_gate · 代码符号「条件化注释」抽样闸门(计划四环之环二入口)。
|
|
3
|
+
|
|
4
|
+
**换对象不换机制**:复用 refine.py 的闸门范式(分层抽样 → 只读工单 → 人工裁决留痕 →
|
|
5
|
+
通过率放行),把处理对象从「node_ 前缀的记忆节点 CCG 字段缺口」换成
|
|
6
|
+
「code_ 前缀的代码符号注释缺口」。
|
|
7
|
+
|
|
8
|
+
候选判据(唯一、机械、不做语义猜测):**代码节点且正文缺『生效条件』**。
|
|
9
|
+
依 Phase 0 契约,代码节点的生效条件只能来自源码人工声明——缺它即 BLINDSPOT
|
|
10
|
+
(缺证据),正是本闸门要治理的对象。落点按使用者裁决 q-0:写进源码、定义行紧邻的
|
|
11
|
+
井号注释(**两个窗口**,见 docs/mdcg/代码评审与条件化注释_契约_v0.1.md §三.2)。
|
|
12
|
+
|
|
13
|
+
本模块只做**候选池 / 工单 / 留痕 / 闸门**:不生成注释、不改任何节点。
|
|
14
|
+
生成与评审方式按 q-2(LLM 生成 + 人工抽检,复用 0.90 闸门)。
|
|
15
|
+
|
|
16
|
+
诚实边界(honest_limits 已随 SPEC 一并输出):
|
|
17
|
+
· 分层抽样的**分配**沿用 refine.sample_ids 对 code_ 节点的口径(家族=大域),
|
|
18
|
+
而非对候选池重新分配——故 sample_adequacy 会如实报出样本里的候选产出率;
|
|
19
|
+
若产出率不足,应先扩大 n 或改用候选池重分配(已知待办,不在本片)。
|
|
20
|
+
"""
|
|
21
|
+
from __future__ import annotations
|
|
22
|
+
|
|
23
|
+
import json
|
|
24
|
+
import os
|
|
25
|
+
import time
|
|
26
|
+
|
|
27
|
+
from . import codeindex, nodefile, refine
|
|
28
|
+
|
|
29
|
+
#: 放行阈值**唯一真源在 refine**(不在此处再定义一份,防口径漂移)
|
|
30
|
+
GATE_MIN_PASS_RATE = refine.GATE_MIN_PASS_RATE
|
|
31
|
+
SAMPLE_N = refine.SAMPLE_N
|
|
32
|
+
#: 本闸门自己的抽样种子(与 refine 的 g6-sample-v1 分离,样本可各自复算)
|
|
33
|
+
SAMPLE_SEED = "comment-gate-v1"
|
|
34
|
+
#: 代码节点 id 前缀(见 codeindex.node_id = code_ + sha1 前 12 位)
|
|
35
|
+
PREFIX = "code_"
|
|
36
|
+
LOG_NAME = "_comment_gate.jsonl"
|
|
37
|
+
|
|
38
|
+
#: 人工核对清单(与工单逐项对应;对照 refine.SPEC.review_items 的同构位置)
|
|
39
|
+
REVIEW_ITEMS = (
|
|
40
|
+
"生效条件是否为**功能前置条件**(何种输入/状态下正确),而非索引元条件",
|
|
41
|
+
"条件可否被机械复核(有输入/状态判据;不是「常用条件默认省略」)",
|
|
42
|
+
"落点是否为源码定义行紧邻的井号注释(两窗口之一),且不覆盖既有实现说明",
|
|
43
|
+
"公开仓合规:过第 14 条「内容政策 + 隐私」双清单(补写内容随源码公开)",
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
SPEC = {
|
|
47
|
+
"goal": "为代码符号补写功能级生效条件注释,使其在检索侧可判(不再因缺证据恒 BLINDSPOT)",
|
|
48
|
+
"target": "code_ 前缀节点中正文缺『生效条件』者",
|
|
49
|
+
"landing": "源码定义行紧邻的井号注释(leading/body 两窗口,物理序合并)",
|
|
50
|
+
"review_items": list(REVIEW_ITEMS),
|
|
51
|
+
"gate": {"min_pass_rate": GATE_MIN_PASS_RATE, "basis": "人工核对忠实比例",
|
|
52
|
+
"rule": "低于阈值不得扩批"},
|
|
53
|
+
"honest_limits": [
|
|
54
|
+
"本闸门只覆盖已索引的代码节点;未索引的源码不在面内(先跑 index_code)",
|
|
55
|
+
"分层分配沿用 code_ 节点口径而非候选池,样本候选产出率由 sample_adequacy 如实上报",
|
|
56
|
+
"不生成注释、不改节点:生成与评审属另一环节(LLM 生成 + 人工抽检)",
|
|
57
|
+
],
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# 生效条件:cg 传入后返回 os.path.join(cg.root, LOG_NAME),即以 cg.root 与模块级常量 LOG_NAME 拼接的日志路径(cg 缺 root 属性时抛 AttributeError)。
|
|
62
|
+
def _log_path(cg) -> str:
|
|
63
|
+
return os.path.join(cg.root, LOG_NAME)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# 生效条件:cg 与 nid 传入后,先看 str(nid) 是否以模块级常量 PREFIX 开头(不以则返回 False);以 PREFIX 开头时取 cg.get(nid)(缺节点按空字典)的 content(假值按空串)经 nodefile.ccg_completeness 判断,若 "生效条件" 不在其 required_present 中则返回 True,否则 False。
|
|
67
|
+
def is_candidate(cg, nid) -> bool:
|
|
68
|
+
"""候选判据:代码节点且正文缺『生效条件』(机械判据,唯一)。"""
|
|
69
|
+
if not str(nid).startswith(PREFIX):
|
|
70
|
+
return False
|
|
71
|
+
node = cg.get(nid) or {}
|
|
72
|
+
comp = nodefile.ccg_completeness(node.get("content") or "")
|
|
73
|
+
return "生效条件" not in comp["required_present"]
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# 生效条件:cg 与 nid 传入后,从 cg.get(nid)(缺节点按空字典)的 frontmatter(假值按空字典)的 code_ref(假值按空字典)中按 path/name/kind/lineno/end/lang/precise 各键取 ref.get(k)(缺键为 None),返回含 code_ref 与固定 landing_rule、landing_note 字段的落点字典。
|
|
77
|
+
def _landing(cg, nid) -> dict:
|
|
78
|
+
"""工单条目 → 源码落点(坐标 + 落点规则),供补写者直接定位。"""
|
|
79
|
+
node = cg.get(nid) or {}
|
|
80
|
+
fm = node.get("frontmatter") or {}
|
|
81
|
+
ref = fm.get("code_ref") or {}
|
|
82
|
+
return {
|
|
83
|
+
"code_ref": {k: ref.get(k) for k in
|
|
84
|
+
("path", "name", "kind", "lineno", "end", "lang", "precise")},
|
|
85
|
+
"landing_rule": "定义行紧邻上方连续井号注释(leading)或定义行紧邻下方、体首语句之前(body)",
|
|
86
|
+
"landing_note": "两窗口按源码物理行序合并;靠前者胜出(契约 §三.2)",
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# 生效条件:cg 与 nid 传入后,取 cg.get(nid)(缺节点按空字典)的 content(假值按空串)算长度与 refine._sha(content)[:16] 作为 source_sha,并结合 refine._entry(cg, nid) 的 layer/tags(tags 假值按空列表)与 nodefile.CCG_MARKS 得出 ccg_missing,最后合并 _landing(cg, nid) 的返回构成工单条目字典。
|
|
91
|
+
def _item(cg, nid) -> dict:
|
|
92
|
+
node = cg.get(nid) or {}
|
|
93
|
+
content = node.get("content") or ""
|
|
94
|
+
comp = nodefile.ccg_completeness(content)
|
|
95
|
+
e = refine._entry(cg, nid)
|
|
96
|
+
it = {
|
|
97
|
+
"id": nid, "family": refine._family(cg, nid),
|
|
98
|
+
"layer": e.get("layer"), "tags": list(e.get("tags") or []),
|
|
99
|
+
"ccg_present": comp["present"],
|
|
100
|
+
"ccg_missing": [m for m in nodefile.CCG_MARKS if m not in comp["present"]],
|
|
101
|
+
"body_len": len(content),
|
|
102
|
+
"source_sha": refine._sha(content)[:16],
|
|
103
|
+
}
|
|
104
|
+
it.update(_landing(cg, nid))
|
|
105
|
+
return it
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
# 生效条件:ids 非空时返回经 is_candidate(cg, i) 过滤后的显式候选与 meta.source='explicit_ids';ids 为空时经 refine._pool(cg, PREFIX) 扫描返回候选与 meta.source='scan'(含 skipped_protected)。
|
|
109
|
+
def candidates(cg, ids=None):
|
|
110
|
+
"""候选池(只读)→ (ids, meta)。"""
|
|
111
|
+
if ids:
|
|
112
|
+
pool = sorted({str(i) for i in ids if is_candidate(cg, i)})
|
|
113
|
+
return pool, {"source": "explicit_ids", "pool": len(pool),
|
|
114
|
+
"requested": len(pool), "candidates": len(pool)}
|
|
115
|
+
all_ids, protected = refine._pool(cg, PREFIX)
|
|
116
|
+
cands = sorted(i for i in all_ids if is_candidate(cg, i))
|
|
117
|
+
return cands, {"source": "scan", "prefix": PREFIX, "pool": len(all_ids),
|
|
118
|
+
"skipped_protected": protected, "candidates": len(cands)}
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
# 生效条件:seed 为假值(None/空串)时回落模块常量 SAMPLE_SEED,n 仅当为 None 时取 SAMPLE_N、否则 int(n)(n=0 保留 0);ids 为真值时样本取全部候选,为假值(含空列表)时经 refine.sample_ids 抽样后按 is_candidate 过滤。
|
|
122
|
+
def plan(x, ids=None, n=None, seed=None) -> dict:
|
|
123
|
+
"""抽检工单(只读):确定性样本 + 源码落点 + 口径声明 + 样本充分性。"""
|
|
124
|
+
cg = refine._as_cg(x)
|
|
125
|
+
seed = seed or SAMPLE_SEED
|
|
126
|
+
n = SAMPLE_N if n is None else int(n)
|
|
127
|
+
cands, cmeta = candidates(cg, ids=ids)
|
|
128
|
+
if ids:
|
|
129
|
+
sample = sorted(cands)
|
|
130
|
+
smeta = {"pool": len(cands), "sampled": len(cands), "families": 0, "strata": {}}
|
|
131
|
+
else:
|
|
132
|
+
picked, smeta = refine.sample_ids(cg, n=n, seed=seed, prefix=PREFIX)
|
|
133
|
+
sample = sorted(i for i in picked if is_candidate(cg, i))
|
|
134
|
+
items = [_item(cg, nid) for nid in sample]
|
|
135
|
+
strata = {}
|
|
136
|
+
for it in items:
|
|
137
|
+
s = strata.setdefault(it["family"], {"picked": 0, "missing_fields": {}})
|
|
138
|
+
s["picked"] += 1
|
|
139
|
+
for m in it["ccg_missing"]:
|
|
140
|
+
s["missing_fields"][m] = s["missing_fields"].get(m, 0) + 1
|
|
141
|
+
adequacy = {
|
|
142
|
+
"sample_of_code_nodes": smeta.get("sampled", len(sample)),
|
|
143
|
+
"candidates_in_sample": len(sample),
|
|
144
|
+
"candidate_yield": round(len(sample) / float(smeta.get("sampled") or 1), 4),
|
|
145
|
+
"note": "分配沿用 code_ 节点口径;产出率不足时应扩大 n 或改候选池重分配",
|
|
146
|
+
}
|
|
147
|
+
return {
|
|
148
|
+
"root": cg.root, "dry_run": True, "readonly": True,
|
|
149
|
+
"action": "comment_gate", "op": "maintain",
|
|
150
|
+
"prefix": PREFIX, "seed": seed, "requested": n,
|
|
151
|
+
"pool": cmeta, "sampled": len(items), "families": len(strata),
|
|
152
|
+
"sample": sample, "sample_sha": refine._sha(*sample)[:16] if sample else "",
|
|
153
|
+
"strata": strata, "items": items, "worklist": items,
|
|
154
|
+
"spec": SPEC, "gate_rule": SPEC["gate"], "sample_adequacy": adequacy,
|
|
155
|
+
"note": ("代码注释补写工单(只读):供人工核对补写口径;"
|
|
156
|
+
"核对通过率未达阈值前不得扩批。本动作不改任何节点。"),
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
# 生效条件:verdicts(None 按 [])中可哈希且属于 okv 字面集合(True/"1"/"true"/"True"/"pass"/"PASS"/"faithful"/"忠实"/"accept"/"ACCEPT")的项计 passed,dict 项在 `v in okv` 处不可哈希先抛 TypeError,故源码里 v.get("verdict") 的计数分支不可达;reviewed 为 0 时 rate=0.0、expand_allowed=False、reason="no_review",否则按 rate 与模块常量 GATE_MIN_PASS_RATE 比较给出 expand_allowed 与 reason。
|
|
161
|
+
def _stats(verdicts) -> dict:
|
|
162
|
+
"""通过率(只认忠实/pass/True),阈值取 refine 唯一真源。"""
|
|
163
|
+
vs = list(verdicts or [])
|
|
164
|
+
okv = {True, "1", "true", "True", "pass", "PASS", "faithful", "忠实",
|
|
165
|
+
"accept", "ACCEPT"}
|
|
166
|
+
passed = 0
|
|
167
|
+
for v in vs:
|
|
168
|
+
if v in okv:
|
|
169
|
+
passed += 1
|
|
170
|
+
elif isinstance(v, dict) and v.get("verdict") in okv:
|
|
171
|
+
passed += 1
|
|
172
|
+
reviewed = len(vs)
|
|
173
|
+
rate = (passed / float(reviewed)) if reviewed else 0.0
|
|
174
|
+
allowed = reviewed > 0 and rate >= GATE_MIN_PASS_RATE
|
|
175
|
+
return {"reviewed": reviewed, "passed": passed, "pass_rate": round(rate, 4),
|
|
176
|
+
"min_pass_rate": GATE_MIN_PASS_RATE, "expand_allowed": allowed,
|
|
177
|
+
"reason": ("" if allowed else
|
|
178
|
+
("no_review" if reviewed == 0 else
|
|
179
|
+
"rate %.4f < %.2f" % (rate, GATE_MIN_PASS_RATE)))}
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
# 生效条件:x 传入后经 refine._as_cg 得到 cg,若 cg.principal 非 None 则调用其 require_admin("maintain_comment_gate");随后调用 plan(cg, ids=ids, n=n, seed=seed) 取工单、调用 _stats(verdicts) 取闸门统计,batch 假值回落 time.strftime("%Y%m%d-%H%M%S"),os.makedirs(cg.root, exist_ok=True) 后把含 batch/actor/seed/pool/sample/verdicts/gate 等字段的记录以追加方式写入 _log_path(cg),并返回 ok=True 的留痕结果。
|
|
183
|
+
def apply(x, ids=None, n=None, seed=None, verdicts=None, actor=None, note=None,
|
|
184
|
+
batch=None) -> dict:
|
|
185
|
+
"""落抽检批次 + 人工裁决到 _comment_gate.jsonl。**不改写任何节点**。
|
|
186
|
+
|
|
187
|
+
权限**自持在模块内**(与 refine 同档):apply 虽只落留痕,但它决定后续是否放行
|
|
188
|
+
**扩批**,故按管理面处理。放在此处而非分发层,是为避免「分发面漏挂一道闸」
|
|
189
|
+
这类易失同步的权限缺口。
|
|
190
|
+
"""
|
|
191
|
+
cg = refine._as_cg(x)
|
|
192
|
+
_principal = getattr(cg, "principal", None)
|
|
193
|
+
if _principal is not None:
|
|
194
|
+
_principal.require_admin("maintain_comment_gate")
|
|
195
|
+
p = plan(cg, ids=ids, n=n, seed=seed)
|
|
196
|
+
batch = batch or time.strftime("%Y%m%d-%H%M%S")
|
|
197
|
+
stats = _stats(verdicts)
|
|
198
|
+
rec = {"t": time.time(), "action": "comment_gate", "batch": batch, "actor": actor,
|
|
199
|
+
"seed": p["seed"], "requested": p["requested"], "pool": p["pool"],
|
|
200
|
+
"sampled": p["sampled"], "sample": p["sample"], "sample_sha": p["sample_sha"],
|
|
201
|
+
"sample_adequacy": p["sample_adequacy"], "verdicts": list(verdicts or []),
|
|
202
|
+
"gate": stats, "note": note}
|
|
203
|
+
os.makedirs(cg.root, exist_ok=True)
|
|
204
|
+
with open(_log_path(cg), "a", encoding="utf-8") as f:
|
|
205
|
+
f.write(json.dumps(rec, ensure_ascii=False) + chr(10))
|
|
206
|
+
return {"ok": True, "action": "comment_gate", "op": "maintain", "batch": batch,
|
|
207
|
+
"root": cg.root, "sampled": p["sampled"], "gate": stats, "log": LOG_NAME,
|
|
208
|
+
"note": ("抽检留痕已落盘(未改任何节点);" +
|
|
209
|
+
("闸门放行扩批" if stats["expand_allowed"] else
|
|
210
|
+
"闸门未放行:" + stats["reason"]))}
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
# 生效条件:x 传入后经 refine._as_cg 得到 cg,读取 _log_path(cg) 且仅当 os.path.exists 为真时逐行解析(空行跳过、json.loads 抛 ValueError 的行跳过),batch 为真时仅保留 batch 字段匹配的记录;recs 为空时返回 expand_allowed=False、reason="no_batch",否则取 recs[-1] 的 verdicts 经 _stats 复算并返回。
|
|
214
|
+
def gate(x, batch=None) -> dict:
|
|
215
|
+
"""扩批闸门:读留痕复算通过率(不写盘)。"""
|
|
216
|
+
cg = refine._as_cg(x)
|
|
217
|
+
recs = []
|
|
218
|
+
path = _log_path(cg)
|
|
219
|
+
if os.path.exists(path):
|
|
220
|
+
with open(path, encoding="utf-8", errors="replace") as f:
|
|
221
|
+
for line in f:
|
|
222
|
+
line = line.strip()
|
|
223
|
+
if not line:
|
|
224
|
+
continue
|
|
225
|
+
try:
|
|
226
|
+
recs.append(json.loads(line))
|
|
227
|
+
except ValueError:
|
|
228
|
+
continue
|
|
229
|
+
if batch:
|
|
230
|
+
recs = [r for r in recs if r.get("batch") == batch]
|
|
231
|
+
if not recs:
|
|
232
|
+
return {"ok": True, "action": "comment_gate_gate", "op": "maintain",
|
|
233
|
+
"root": cg.root, "batch": batch, "batches": 0,
|
|
234
|
+
"expand_allowed": False, "reason": "no_batch"}
|
|
235
|
+
rec = recs[-1]
|
|
236
|
+
stats = _stats(rec.get("verdicts"))
|
|
237
|
+
return {"ok": True, "action": "comment_gate_gate", "op": "maintain",
|
|
238
|
+
"root": cg.root, "batch": rec.get("batch"),
|
|
239
|
+
"batches": len({r.get("batch") for r in recs}), **stats}
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
# 生效条件:root 传入后调用 codeindex.index_dir 扫描(max_files 假值回落 500,max_items 假值回落 2000),对返回 items 的每条 comments(假值按空列表)逐条先 str(c).lstrip("#").strip() 再判断是否以 "生效条件" 开头,全部不以该串开头的项进入候选并返回含 items/errors/stats/examined/candidates 的字典。
|
|
243
|
+
def candidates_from_sources(root, patterns=None, max_files=None, max_items=None,
|
|
244
|
+
skip_dirs=None):
|
|
245
|
+
"""**源级**候选枚举(不依赖认知图):扫源码树,筛出注释里缺『生效条件』的符号。
|
|
246
|
+
|
|
247
|
+
为什么需要它(2026-09-17 实测):真实库 2934 条 code_ 节点的生效条件 **100% 是旧 render
|
|
248
|
+
的合成值**,故**图内候选池为 0**;而「源码里哪些符号缺条件注释」是**源侧事实**,
|
|
249
|
+
无须先重索引(重索引会让 2934 条同时失去条件、判 BLINDSPOT)。本入口绕开该一次性代价。
|
|
250
|
+
|
|
251
|
+
判据唯一且机械:符号的 **井号注释窗口**(leading + body,见 codeindex)中不存在以
|
|
252
|
+
『生效条件』开头的行;只认井号注释、不认 docstring——落点按 q-0 裁决。
|
|
253
|
+
"""
|
|
254
|
+
items, errors, stats = codeindex.index_dir(
|
|
255
|
+
root, patterns=patterns, max_files=int(max_files or 500),
|
|
256
|
+
max_items=int(max_items or 2000), skip_dirs=skip_dirs)
|
|
257
|
+
cands = []
|
|
258
|
+
for it in items:
|
|
259
|
+
has = any(str(c).lstrip("#").strip().startswith("生效条件")
|
|
260
|
+
for c in (it.get("comments") or []))
|
|
261
|
+
if not has:
|
|
262
|
+
cands.append(it)
|
|
263
|
+
return {"root": root, "items": cands, "errors": errors, "stats": stats,
|
|
264
|
+
"examined": len(items), "candidates": len(cands),
|
|
265
|
+
"note": ("源级枚举:判据=井号注释窗口内无『生效条件』行;"
|
|
266
|
+
"不含图内节点状态,故不受存量渲染影响")}
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
# 生效条件:it 传入后取 it.get("path")(假值按空串)按 "/" 分割首段作为 family(首段为空则 family="."),并返回含 id=codeindex.node_id(it)、name/kind、code_ref(path/lineno/end/lang/precise 各缺省 None)、comments(假值按空列表逐项 str)、以及固定 landing_rule 与 landing_note 的字典。
|
|
270
|
+
def _landing_src(it) -> dict:
|
|
271
|
+
return {"id": codeindex.node_id(it), "name": it.get("name"),
|
|
272
|
+
"kind": it.get("kind"),
|
|
273
|
+
"family": (it.get("path") or "").split("/")[0] or ".",
|
|
274
|
+
"code_ref": {"path": it.get("path"), "lineno": it.get("lineno"),
|
|
275
|
+
"end": it.get("end"), "lang": it.get("lang"),
|
|
276
|
+
"precise": it.get("precise")},
|
|
277
|
+
"comments": [str(c) for c in (it.get("comments") or [])],
|
|
278
|
+
"landing_rule": ("定义行紧邻上方连续井号注释(leading)或定义行紧邻下方、"
|
|
279
|
+
"体首语句之前(body)"),
|
|
280
|
+
"landing_note": "两窗口按源码物理行序合并;靠前者胜出(契约 §三.2)"}
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
# 生效条件:root 传入后调用 candidates_from_sources(root, patterns=patterns, skip_dirs=skip_dirs, max_files=max_files, max_items=max_items) 得候选,seed 假值回落 SAMPLE_SEED,n 为 None 时取 SAMPLE_N 否则 int(n)(0 保持 0),按 path 首段(空则 ".")分族与 sha(seed, node_id) 排序后做族间确定性轮转取至多 n 项,返回只读源级工单。
|
|
284
|
+
def plan_sources(root, n=None, seed=None, patterns=None, skip_dirs=None,
|
|
285
|
+
max_files=None, max_items=None) -> dict:
|
|
286
|
+
"""**源级**工单(只读):确定性抽样 + 源码落点;不依赖认知图、不改任何节点。
|
|
287
|
+
|
|
288
|
+
抽样口径:按大域(顶层目录)**确定性轮转**(族内按 sha(seed,id) 排序)。
|
|
289
|
+
刻意**不复制** refine 的浮点最大余数分配——那是第二份实现,会引入漂移;
|
|
290
|
+
源级候选列表是精确的(无「先抽样再过滤」的口径损失),轮转已给出跨族覆盖。
|
|
291
|
+
"""
|
|
292
|
+
seed = seed or SAMPLE_SEED
|
|
293
|
+
n = SAMPLE_N if n is None else int(n)
|
|
294
|
+
got = candidates_from_sources(root, patterns=patterns, skip_dirs=skip_dirs,
|
|
295
|
+
max_files=max_files, max_items=max_items)
|
|
296
|
+
fams = {}
|
|
297
|
+
for it in got["items"]:
|
|
298
|
+
top = (it.get("path") or "").split("/")[0] or "."
|
|
299
|
+
fams.setdefault(top, []).append(it)
|
|
300
|
+
pools = {f: len(v) for f, v in fams.items()}
|
|
301
|
+
names = sorted(fams)
|
|
302
|
+
for f in names:
|
|
303
|
+
fams[f].sort(key=lambda it: refine._sha(seed, codeindex.node_id(it)))
|
|
304
|
+
picked = []
|
|
305
|
+
while len(picked) < n and any(fams[f] for f in names):
|
|
306
|
+
for f in names:
|
|
307
|
+
if fams[f] and len(picked) < n:
|
|
308
|
+
picked.append(fams[f].pop(0))
|
|
309
|
+
work = [_landing_src(it) for it in picked]
|
|
310
|
+
return {"root": root, "dry_run": True, "readonly": True, "mode": "source_level",
|
|
311
|
+
"action": "comment_gate_sources", "op": "maintain",
|
|
312
|
+
"seed": seed, "requested": n, "examined": got["examined"],
|
|
313
|
+
"candidates": got["candidates"], "sampled": len(work),
|
|
314
|
+
"families": len(pools), "strata": pools,
|
|
315
|
+
"items": work, "worklist": work, "spec": SPEC,
|
|
316
|
+
"scan_errors": list(got["errors"])[:10], "scan_stats": got["stats"],
|
|
317
|
+
"gate_rule": SPEC["gate"],
|
|
318
|
+
"note": ("源级工单(只读):不依赖认知图、不改任何节点;"
|
|
319
|
+
"抽样为按大域的确定性轮转。")}
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
# 生效条件:x 与 action 传入后,action=="comment_gate" 时若 kw.get("apply") 为真则转 apply(x, ids,n,seed,verdicts,actor,note,batch),否则转 plan(x, ids,n,seed);action=="comment_gate_verdict" 时转 gate(x, batch);action 为 "comment_gate_sources" 或 "comment_gate_source_plan" 时以 root=kw.get("root") or getattr(x,"root",None) or str(x) 转 plan_sources(...);其余 action 抛 ValueError。
|
|
323
|
+
def run(x, action, **kw) -> dict:
|
|
324
|
+
"""maintain op 分派入口(与 backfill.run 同形,便于 mcp_server 侧并列分派)。"""
|
|
325
|
+
if action == "comment_gate":
|
|
326
|
+
if kw.get("apply"):
|
|
327
|
+
return apply(x, ids=kw.get("ids"), n=kw.get("n"), seed=kw.get("seed"),
|
|
328
|
+
verdicts=kw.get("verdicts"), actor=kw.get("actor"),
|
|
329
|
+
note=kw.get("note"), batch=kw.get("batch"))
|
|
330
|
+
return plan(x, ids=kw.get("ids"), n=kw.get("n"), seed=kw.get("seed"))
|
|
331
|
+
if action == "comment_gate_verdict":
|
|
332
|
+
return gate(x, batch=kw.get("batch"))
|
|
333
|
+
if action in ("comment_gate_sources", "comment_gate_source_plan"):
|
|
334
|
+
root = kw.get("root") or getattr(x, "root", None) or str(x)
|
|
335
|
+
return plan_sources(root, n=kw.get("n"), seed=kw.get("seed"),
|
|
336
|
+
patterns=kw.get("patterns"), skip_dirs=kw.get("skip_dirs"))
|
|
337
|
+
raise ValueError("未知 comment_gate action:%s(允许 comment_gate / "
|
|
338
|
+
"comment_gate_verdict)" % action)
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""白箱条件填充器:把 cond_facts 的事实按 LLM 模板填成「功能级生效条件」(确定性、零 LLM)。
|
|
3
|
+
|
|
4
|
+
# 功能名:生效条件白箱填充
|
|
5
|
+
# 生效条件:已取得模板(md_cg/cond_template.json,由 LLM 产出并留痕)且有 cond_facts 事实时;用于批量生成候选注释文本
|
|
6
|
+
# 子功能:按模板槽位 symbol/required/optional/externals/guards/returns/doc 组句;externals 剔除 import 来源与内置名;证据不足按模板规则输出 BLINDSPOT
|
|
7
|
+
# 执行:python -X utf8 -m md_cg.cond_compose <file>... [--terse] [--json];库内调用 compose_file(path)
|
|
8
|
+
# 验证方式:md_cg/test_cond_compose.py;生成文本须过 condition_anchor.judge(ANCHORED 或 BLINDSPOT,不得 WEAK/REJECT_META)
|
|
9
|
+
# 不适用条件:①本器只按事实填模板,**不新增语义**;证据不足即 BLINDSPOT ②--terse 会省略空槽位(与模板逐字填充不同,属受控偏离,见 README)③模板变更须同步本器槽位
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import ast
|
|
14
|
+
import builtins
|
|
15
|
+
import json
|
|
16
|
+
import os
|
|
17
|
+
import sys
|
|
18
|
+
|
|
19
|
+
from . import condition_anchor, cond_facts
|
|
20
|
+
|
|
21
|
+
BUILTINS = set(dir(builtins))
|
|
22
|
+
TPL_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "cond_template.json")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# 生效条件:调用 load_template 时可选实参 path 为假值(None/空串等)则打开并 json.load 模块级常量 TPL_PATH,否则打开并解析 path,返回 json.load 得到的对象;
|
|
26
|
+
def load_template(path=None):
|
|
27
|
+
return json.load(open(path or TPL_PATH, encoding="utf-8"))
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# 生效条件:调用 import_names 时必须提供 path,且对该 path 内容 ast.parse 抛 SyntaxError 时返回空集合,否则返回 ast.Import 的 (asname 或 name 首段) 与 ast.ImportFrom 的 (asname 或 name) 构成的集合;
|
|
31
|
+
def import_names(path):
|
|
32
|
+
"""该文件里 import 进来的名字(这些不是「状态来源」,是固定依赖,不进条件)。"""
|
|
33
|
+
names = set()
|
|
34
|
+
try:
|
|
35
|
+
tree = ast.parse(open(path, encoding="utf-8", errors="replace").read())
|
|
36
|
+
except SyntaxError:
|
|
37
|
+
return names
|
|
38
|
+
for node in ast.walk(tree):
|
|
39
|
+
if isinstance(node, ast.Import):
|
|
40
|
+
for a in node.names:
|
|
41
|
+
names.add((a.asname or a.name).split(".")[0])
|
|
42
|
+
elif isinstance(node, ast.ImportFrom):
|
|
43
|
+
for a in node.names:
|
|
44
|
+
names.add(a.asname or a.name)
|
|
45
|
+
return names
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
# 生效条件:调用 module_constants 时必须提供 path,且对该 path 内容 ast.parse 抛 SyntaxError 时返回空集合,否则返回顶层 ast.Assign 的 Name 目标与 ast.AnnAssign 的 Name 目标构成的集合减去 import_names(path) 的结果;
|
|
49
|
+
def module_constants(path):
|
|
50
|
+
"""模块级常量名(顶层赋值目标)——它们是真正的『状态来源』,函数/类定义不算。"""
|
|
51
|
+
consts = set()
|
|
52
|
+
try:
|
|
53
|
+
tree = ast.parse(open(path, encoding="utf-8", errors="replace").read())
|
|
54
|
+
except SyntaxError:
|
|
55
|
+
return consts
|
|
56
|
+
for node in tree.body:
|
|
57
|
+
if isinstance(node, ast.Assign):
|
|
58
|
+
for t in node.targets:
|
|
59
|
+
if isinstance(t, ast.Name):
|
|
60
|
+
consts.add(t.id)
|
|
61
|
+
elif isinstance(node, ast.AnnAssign) and isinstance(node.target, ast.Name):
|
|
62
|
+
consts.add(node.target.id)
|
|
63
|
+
return consts - import_names(path)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# 生效条件:调用 explained_guards 时必须提供 rec、allowed,且 rec.get("guards") 的假值经 or [] 视为空,仅保留其中 g.get("early") 为真、且 g.get("cond")(假值经 or "" 按空串)内所有标识符都在 allowed、BUILTINS 或 self/cls 中的守卫并返回列表;
|
|
67
|
+
def explained_guards(rec, allowed):
|
|
68
|
+
"""只保留「早退式」且标识符全部可解释的守卫(模板校验清单第 6/9 条)。"""
|
|
69
|
+
out = []
|
|
70
|
+
for g in rec.get("guards") or []:
|
|
71
|
+
if not g.get("early"):
|
|
72
|
+
continue
|
|
73
|
+
ids = set(__import__("re").findall(r"[A-Za-z_][A-Za-z0-9_]*", g.get("cond") or ""))
|
|
74
|
+
if any(i not in allowed and i not in BUILTINS and i not in ("self", "cls") for i in ids):
|
|
75
|
+
continue
|
|
76
|
+
out.append(g)
|
|
77
|
+
return out
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def behavior_anchors(rec, allowed):
|
|
81
|
+
"""可陈述的行为锚点:doc 摘要 / 早退式守卫 / 引用了入参或模块常量的返回表达式。
|
|
82
|
+
|
|
83
|
+
只有形参清单而没有任何行为锚点者,判 BLINDSPOT(验证单元实测:仅复述入参/局部变量表达式不算条件)。
|
|
84
|
+
"""
|
|
85
|
+
import re as _re
|
|
86
|
+
doc = (rec.get("doc_head") or "").strip()
|
|
87
|
+
guards = explained_guards(rec, allowed)
|
|
88
|
+
rets = []
|
|
89
|
+
for r in rec.get("returns") or []:
|
|
90
|
+
ids = set(_re.findall(r"[A-Za-z_][A-Za-z0-9_]*", r))
|
|
91
|
+
if ids & allowed: # 引用局部变量的返回表达式(如 d.strip()...)不作行为锚点
|
|
92
|
+
rets.append(r)
|
|
93
|
+
return doc, guards, rets
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# 生效条件:调用 compose 时必须提供 rec(且 rec 含 name 键,直接 rec["name"] 取值),imports、consts 为假值时回落为空集合,terse 控制空槽位是否省略;当 rec["required"] 为空且 rec["externals"] 中在 consts 内的项也为空时返回固定 BLINDSPOT 串,否则按 required/optional/externals/explained_guards/returns/doc_head 拼接并返回以“。”结尾的字符串;
|
|
97
|
+
def compose(rec, imports=None, terse=True, consts=None):
|
|
98
|
+
"""按模板把一条事实填成中文生效条件;terse=True 省略空槽位。"""
|
|
99
|
+
imports = imports or set()
|
|
100
|
+
consts = consts or set()
|
|
101
|
+
name = rec["name"]
|
|
102
|
+
required = list(rec.get("required") or [])
|
|
103
|
+
optional = list(rec.get("optional") or [])
|
|
104
|
+
# 外部名只保留「模块级常量」——它们才是可陈述的状态来源(函数与 import 不是条件)
|
|
105
|
+
externals = [x for x in (rec.get("externals") or []) if x in consts]
|
|
106
|
+
allowed = set(required) | {o["name"] for o in optional} | set(externals)
|
|
107
|
+
doc, guards, returns_ok = behavior_anchors(rec, allowed)
|
|
108
|
+
if not doc and not guards and not returns_ok:
|
|
109
|
+
return "BLINDSPOT:缺功能证据(仅形参清单,未描述功能前置状态或行为)"
|
|
110
|
+
returns = list(rec.get("returns") or [])
|
|
111
|
+
# 模板规则:必需与外部锚点皆无 → BLINDSPOT(不可判,不猜)
|
|
112
|
+
if not required and not externals:
|
|
113
|
+
return "BLINDSPOT:缺证据(无必需形参且无体内外部名锚点)"
|
|
114
|
+
parts = []
|
|
115
|
+
if required:
|
|
116
|
+
parts.append("必须提供实参 " + "、".join(required))
|
|
117
|
+
elif not terse:
|
|
118
|
+
parts.append("无必需实参")
|
|
119
|
+
if optional:
|
|
120
|
+
parts.append("可选 " + "、".join("%s=%s" % (o["name"], o["default"] or "None") for o in optional) + " 可省略")
|
|
121
|
+
elif not terse:
|
|
122
|
+
parts.append("无显式可选实参")
|
|
123
|
+
if externals:
|
|
124
|
+
parts.append("体内引用 " + "、".join(externals) + " 需已定义")
|
|
125
|
+
for g in guards:
|
|
126
|
+
if g["kind"] == "if":
|
|
127
|
+
parts.append("当 " + g["cond"] + " 成立")
|
|
128
|
+
elif g["kind"] == "assert":
|
|
129
|
+
parts.append("断言 " + g["cond"] + " 成立")
|
|
130
|
+
else:
|
|
131
|
+
parts.append("可能抛出 " + (g["cond"] or "异常"))
|
|
132
|
+
if returns_ok:
|
|
133
|
+
parts.append("返回 " + " 或 ".join(returns_ok[:2]))
|
|
134
|
+
head = "调用 " + name + " 时" if rec.get("kind") != "class" else "构造/使用 " + name + " 时"
|
|
135
|
+
body_text = head + "," + ";".join(parts)
|
|
136
|
+
if doc:
|
|
137
|
+
# 不截断半个词:超长时在最近的句读处收口(校验器 C9 曾抓出 'option' ≠ 'referenced')
|
|
138
|
+
short = doc if len(doc) <= 120 else doc[:120].rsplit("。", 1)[0] + "。"
|
|
139
|
+
body_text += ";功能:" + short.rstrip("。")
|
|
140
|
+
return body_text + "。"
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
# 生效条件:调用 compose_symbol 时必须提供 path、rec(terse 可选),它用 path 取得 import 名与模块常量,以 rec 和 terse 调用 compose 生成 condition,再按 rec["lineno"] 与 rec["end"] 从 path 按换行切分的行列表切片交给 condition_anchor.judge,返回含 name/kind/lineno/end/condition/gate/anchors 的字典;
|
|
144
|
+
def compose_symbol(path, rec, terse=True):
|
|
145
|
+
"""生成并**当场过门禁**(condition_anchor.judge),返回条件与裁决。"""
|
|
146
|
+
imports = import_names(path)
|
|
147
|
+
consts = module_constants(path)
|
|
148
|
+
cond = compose(rec, imports=imports, terse=terse, consts=consts)
|
|
149
|
+
lines = open(path, encoding="utf-8", errors="replace").read().split(chr(10))
|
|
150
|
+
seg = chr(10).join(lines[max(0, rec["lineno"] - 1): rec["end"]])
|
|
151
|
+
verdict = condition_anchor.judge(cond, seg)
|
|
152
|
+
return {"name": rec["name"], "kind": rec["kind"], "lineno": rec["lineno"],
|
|
153
|
+
"end": rec["end"], "condition": cond, "gate": verdict["verdict"],
|
|
154
|
+
"anchors": verdict["anchors"]}
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
# 生效条件:调用 compose_file 时必须提供 path(terse 可选),遍历 cond_facts.file_facts(path) 的每条 rec,调用 compose_symbol(path, rec, terse=terse),返回全部结果组成的列表 out;
|
|
158
|
+
def compose_file(path, terse=True):
|
|
159
|
+
out = []
|
|
160
|
+
for rec in cond_facts.file_facts(path):
|
|
161
|
+
out.append(compose_symbol(path, rec, terse=terse))
|
|
162
|
+
return out
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
# 生效条件:调用 main 时省略 argv 或 argv 为 None 则取 sys.argv[1:],否则使用传入 argv(空列表不回落),argv 含 "--terse" 则 terse=True,含 "--json" 则打印 rows 的 JSON 后返回 0,否则以非 "--" 开头的实参作为文件路径收集 rows,打印每个 gate/path/lineno/name/condition 与 STAT 统计后返回 0;
|
|
166
|
+
def main(argv=None):
|
|
167
|
+
argv = list(argv if argv is not None else sys.argv[1:])
|
|
168
|
+
terse = "--terse" in argv
|
|
169
|
+
as_json = "--json" in argv
|
|
170
|
+
files = [a for a in argv if not a.startswith("--")]
|
|
171
|
+
rows = []
|
|
172
|
+
for f in files:
|
|
173
|
+
for r in compose_file(f, terse=terse):
|
|
174
|
+
r["path"] = f
|
|
175
|
+
rows.append(r)
|
|
176
|
+
if as_json:
|
|
177
|
+
print(json.dumps(rows, ensure_ascii=False, indent=1))
|
|
178
|
+
return 0
|
|
179
|
+
stat = {}
|
|
180
|
+
for r in rows:
|
|
181
|
+
stat[r["gate"]] = stat.get(r["gate"], 0) + 1
|
|
182
|
+
for r in rows:
|
|
183
|
+
print("[" + r["gate"] + "] " + r["path"] + ":" + str(r["lineno"]) + " " + r["name"])
|
|
184
|
+
print(" " + r["condition"][:200])
|
|
185
|
+
print("STAT " + json.dumps(stat, ensure_ascii=False))
|
|
186
|
+
return 0
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
if __name__ == "__main__":
|
|
190
|
+
raise SystemExit(main())
|