内容锚改版批(判据锚行号形态废止): 锚形态唯一化<路径>「节名」<行关键词>——用户裁: 锚指向内容不指向行号,文档增删行不牵动锚与测试代码,判据句内容真改动才失配(失配=红,改句文档批同批改锚)。规约落文: test-conventions §8.3 判据出处锚两条文(卡片锚/测试函数注记)+§8.2 golden 预览句+§8.10 审计核对句+req-card §8 锚规格/复核门实读/description+iter 开工门+iter-plan 预检第3步+process-conventions 写卡段行; 闸件: pipeline_gate C5 与 capability_audit 注记道改核「节名在档+关键词命中节内」(共用件 tools/anchor_util.py 逻辑单点: 同名节精确命中优先、围栏内#不作节界、.gd 测量面按文件内子串),行号形态出现即红; 存量迁移: 测试/探针注记 604 条全量内容化(行号形态 568+存量残形 36 重锚)+在盘五卡 md 行号指针 74 处内容化+写卡段存量半成品锚修正(反引号吞词尾 12 处、杂尾 2 处、过期词重锚 2 处); 登记条目按新裁决改写(待执行)。区内凭据: 流水线闸 PASS(36 条目/57 行/5 卡全过)+能力审计 --strict 455 注记函数 0 红+文档域闸【验收通过】无新增红; 迁移件一次性用后即删未提交
This commit is contained in:
1 parent
c713f335a9
commit
a69310b738
123 files changed
+832
-716
No files matched your search
@@ -0,0 +1,91 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
r"""anchor_util.py — 内容判据锚(节锚)核验共用件:pipeline_gate(卡片 C5)与 capability_audit(判据出处注记道)共用的核验逻辑单点。
|
||||
|
||||
锚形态(2026-11-09 判据锚内容化改版批立,用户裁行号形态废止):
|
||||
`<文档相对路径>「节名」<行关键词>`——节名与行关键词在 design/ 现行文档(或 game/sim/ 测量面实现件)
|
||||
里定位判据句,文档增删行使行号漂移与此形态无关,只有判据句内容真被改动才会使锚失配。
|
||||
核验口径:
|
||||
.md 目标——节名首段(「·」前,沿 2026-11-07 架构收窄批惯例)须命中文档某个标题行(行首 # 记号),
|
||||
行关键词须落在该标题行的节块内(标题行起至下一个标题级别不高于它的标题行止);
|
||||
.gd 目标(测量面,无标题结构)——节名首段与行关键词均须在文件内子串命中。
|
||||
本件只做纯文本核验、不读盘:目标文件文本由调用方读入传入(编码口径归调用方)。
|
||||
机检局限如实声明:判据句被改写而行关键词仍为其子串时核验不红(语义漂移),由复核门实读兜。
|
||||
"""
|
||||
import re
|
||||
|
||||
# 内容锚整体形态:路径+「节名」+行关键词(关键词止于空白与常见分隔符;反引号/+/§/冒号
|
||||
# 不入词——卡内锚常套反引号包裹、杂尾修饰会污染关键词)
|
||||
CONTENT_ANCHOR_RE = re.compile(
|
||||
r"([^\s`'\"()(),,、;;::|]+\.md)「([^「」]+)」([^\s「」|,,、;;::()()+§`]+)")
|
||||
# 行号锚(废止形态,2026-11-09 用户裁)——判据锚位置出现即红
|
||||
LINE_ANCHOR_RE = re.compile(r"([^\s`'\"()(),,、;;::|]+\.md)\s*:\s*(\d+)")
|
||||
|
||||
|
||||
def section_block(text, head):
|
||||
"""节名首段 head 命中的标题行的节块文本;无命中标题行返回 None。
|
||||
|
||||
节块=该标题行起至下一个标题级别不高于它的标题行止(不含该行);
|
||||
标题行=行首 # 记号的 ATX 标题(本仓库 design/ 文档惯例);围栏代码块(```)内的
|
||||
# 行不是标题,探测时跳过。节名命中两档:先找标题文本与 head 全等(去除首尾空白)的行,
|
||||
全等无命中再退子串包含——同名节(如「验收标准」嵌「一屏样张的验收标准」)取精确者。"""
|
||||
lines = text.splitlines()
|
||||
|
||||
def headings():
|
||||
fence = False
|
||||
for i, ln in enumerate(lines):
|
||||
s = ln.lstrip()
|
||||
if s.startswith("```"):
|
||||
fence = not fence
|
||||
continue
|
||||
if fence:
|
||||
continue
|
||||
m = re.match(r"^(#{1,6})\s*(.+?)\s*$", s)
|
||||
if m:
|
||||
yield i, len(m.group(1)), m.group(2).strip()
|
||||
|
||||
start = -1
|
||||
level = 0
|
||||
for i, lvl, txt in headings():
|
||||
if txt == head.strip():
|
||||
start = i
|
||||
level = lvl
|
||||
break
|
||||
if start < 0:
|
||||
for i, lvl, txt in headings():
|
||||
if head in txt:
|
||||
start = i
|
||||
level = lvl
|
||||
break
|
||||
if start < 0:
|
||||
return None
|
||||
fence = False
|
||||
for j in range(start + 1, len(lines)):
|
||||
s = lines[j].lstrip()
|
||||
if s.startswith("```"):
|
||||
fence = not fence
|
||||
continue
|
||||
if fence:
|
||||
continue
|
||||
m = re.match(r"^(#{1,6})\s", s)
|
||||
if m and len(m.group(1)) <= level:
|
||||
return "\n".join(lines[start:j])
|
||||
return "\n".join(lines[start:])
|
||||
|
||||
|
||||
def verify(target_path, target_text, sec, kw):
|
||||
"""核验一个内容锚。返回 None=通过;字符串=红因白话。
|
||||
|
||||
target_path 仅用于按扩展名分派核验口径(.gd=测量面文件内子串命中,其余=标题+节块口径)。"""
|
||||
if target_path.replace("\\", "/").endswith(".gd"):
|
||||
if sec.split("·")[0] not in target_text:
|
||||
return "节名首段不在文件内"
|
||||
if kw not in target_text:
|
||||
return "行关键词不在文件内"
|
||||
return None
|
||||
block = section_block(target_text, sec.split("·")[0])
|
||||
if block is None:
|
||||
return "节名首段未命中任何标题行"
|
||||
if kw not in block:
|
||||
return "行关键词不在该节块内"
|
||||
return None
|
||||
+30
-18
@@ -14,12 +14,14 @@ r"""capability_audit.py — 能力清单、覆盖域表与 GDD 纯度机检(
|
||||
tests//probe/)有 `func 方法(`,或该方法名能在 reports/需求卡片*.md 中找到(计划入口同 D 口径);两头都无=红。
|
||||
F 覆盖域表:systems 每篇一行;状态∈{已覆盖,部分覆盖,未覆盖};非未覆盖行的「能力行」非空且每项都能
|
||||
对上能力表里的行名;部分/未覆盖行的缺口非空且指待办清单。
|
||||
G 判据出处随测注记(test-conventions §8.3):game/tests/** 与 game/probe/** 内每个验证函数
|
||||
(函数体含 check(/check_quiet(/_problems.append(/push_error( 判定调用,定义行与 _check( 前缀除外)
|
||||
须带 ≥1 条 `# 判据出处: <design/相对路径>:<行号> <判据一句>` 注记(或节锚形态
|
||||
`# 判据出处: <design/相对路径>「节名」<关键词> <判据一句>`,2026-11-07 架构收窄批立,见
|
||||
test-conventions §8.3);指针指向存在的设计文档行/节且不出
|
||||
design/ 全域。扫描面=game/tests/**+game/probe/**(sim/ 不进——测量面直构惯例)。
|
||||
G 判据出处随测注记(test-conventions §8.3;2026-11-09 判据锚内容化改版批):game/tests/** 与
|
||||
game/probe/** 内每个验证函数(函数体含 check(/check_quiet(/_problems.append(/push_error(
|
||||
判定调用,定义行与 _check( 前缀除外)须带 ≥1 条内容节锚注记
|
||||
`# 判据出处: <design/相对路径>「节名」<行关键词> <判据一句>`——节名首段(「·」前)须命中
|
||||
目标文档标题行、行关键词须落在该节块内(核验口径归 tools/anchor_util.py 共用件;
|
||||
.gd 测量面目标=节名与关键词均在文件内子串命中);行号形态 `<路径>:<行号>` 已废止、
|
||||
出现即红。指针不出 design/ 全域+game/sim/ 测量面。
|
||||
扫描面=game/tests/**+game/probe/**(sim/ 不进——测量面直构惯例)。
|
||||
条件闸行豁免(代表用例含「条件闸」字样)不参与 D/E。
|
||||
|
||||
自检五道(防扫描面空转假绿):系统文档数、能力行数、manifest 套件数、覆盖表行数、判据注记扫描面文件数
|
||||
@@ -35,6 +37,9 @@ import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||
import anchor_util # 内容判据锚核验共用件(2026-11-09 判据锚内容化改版批)
|
||||
|
||||
REPO = Path(__file__).resolve().parents[1]
|
||||
GDD_DIR = REPO / "design" / "gdd"
|
||||
SYS_DIR = GDD_DIR / "systems"
|
||||
@@ -339,7 +344,9 @@ def check_coverage(cov_tables, names):
|
||||
red(README.relative_to(REPO), 1, "覆盖域表", "%s 未在覆盖域表登记" % doc)
|
||||
|
||||
|
||||
CRITERION_RE = re.compile(r"^#\s*判据出处:\s*(\S+?\.(?:md|gd))(?::(\d+)|「([^」]+)」)\s*\S")
|
||||
# 内容节锚注记(2026-11-09 判据锚内容化改版批——唯一有效形态;行号形态=CRITERION_DEAD_RE,出现即红)
|
||||
CRITERION_RE = re.compile(r"^#\s*判据出处:\s*(\S+?\.(?:md|gd))「([^「」]+)」([^\s「」|,,、;;::()()+§`]+)\s+\S")
|
||||
CRITERION_DEAD_RE = re.compile(r"^#\s*判据出处:\s*\S+?\.(?:md|gd)\s*:\s*\d+")
|
||||
VERDICT_RE = re.compile(r"(?<![_\w])check(?:_quiet)?\(|_problems\.append\(|push_error\(")
|
||||
CRITERION_FUNC_RE = re.compile(r"^(?:static\s+)?func\s+([A-Za-z_]\w*)")
|
||||
|
||||
@@ -381,16 +388,24 @@ def check_criterion_markers():
|
||||
for ln in body:
|
||||
st = ln.strip()
|
||||
if st.startswith("#"):
|
||||
if CRITERION_DEAD_RE.match(st):
|
||||
# 行号锚(废止形态,2026-11-09 用户裁)——出现即红,且不算有效注记
|
||||
red(rel, s0 + 1, "判据出处注记",
|
||||
"%s 行号形态已废止(2026-11-09 用户裁)——改写内容节锚 "
|
||||
"`<路径>「节名」<行关键词>`" % name)
|
||||
flagged += 1
|
||||
continue
|
||||
m = CRITERION_RE.match(st)
|
||||
if m:
|
||||
oks.append(m)
|
||||
if not oks:
|
||||
red(rel, s0 + 1, "判据出处注记", "验证函数 %s 缺 # 判据出处: 注记" % name)
|
||||
red(rel, s0 + 1, "判据出处注记",
|
||||
"验证函数 %s 缺 # 判据出处: 内容节锚注记(`<路径>「节名」<行关键词> <判据一句>`)" % name)
|
||||
flagged += 1
|
||||
continue
|
||||
marked += 1
|
||||
for m in oks:
|
||||
relp, no, sec = m.group(1), m.group(2), m.group(3)
|
||||
relp, sec, kw = m.group(1), m.group(2), m.group(3)
|
||||
_rp = relp.replace("\\", "/")
|
||||
if not (_rp.startswith("design/") or _rp.startswith("game/sim/")):
|
||||
red(rel, s0 + 1, "判据出处注记", "%s 指针越出 design/ 与测量面全域:%s" % (name, relp))
|
||||
@@ -399,15 +414,12 @@ def check_criterion_markers():
|
||||
if not tp.is_file():
|
||||
red(rel, s0 + 1, "判据出处注记", "%s 指针不可核:%s(文件缺失)" % (name, relp))
|
||||
continue
|
||||
if no is not None:
|
||||
if not (1 <= int(no) <= len(read(tp).splitlines())):
|
||||
red(rel, s0 + 1, "判据出处注记", "%s 指针不可核:%s:%d(行号越界)" % (name, relp, int(no)))
|
||||
elif sec is not None:
|
||||
# 节锚(2026-11-07 架构收窄批立,test-conventions §8.3):核验节名首段(「·」前)
|
||||
# 在所指文档中出现——节名首段为「·」前的标题词(如「统一随机流」),标题在档即过;关键词不核(随文档措辞漂移由人审兜)
|
||||
head = sec.split("·")[0]
|
||||
if head not in read(tp):
|
||||
red(rel, s0 + 1, "判据出处注记", "%s 节锚不可核:%s「%s」(文档内无该节名首段)" % (name, relp, head))
|
||||
why = anchor_util.verify(relp, read(tp), sec, kw)
|
||||
if why is not None:
|
||||
# 内容锚核验(2026-11-09 判据锚内容化改版批,口径归 anchor_util 共用件):
|
||||
# 节名首段须命中标题行、行关键词须落在节块内(.gd 测量面=文件内子串命中)
|
||||
red(rel, s0 + 1, "判据出处注记",
|
||||
"%s 内容锚不可核:%s「%s」%s(%s)" % (name, relp, sec, kw, why))
|
||||
return marked, flagged
|
||||
|
||||
|
||||
|
||||
+30
-16
@@ -6,7 +6,7 @@ r"""pipeline_gate.py — 工作流程流水线闸(待办清单条目状态机
|
||||
改动面四档白名单/条件凭据/无设计线段条目的证据指认/状态段累积制/特批/正文限长双判据/零命中)
|
||||
与 roadmap 迭代总表(六列/状态五形态/卡片与报告存在性/编入时间与预计周期的
|
||||
分档放行)/需求卡片口径(C1 文档义务/C2 设计事实源/C3 改动—验收对照表/C4 验收档申报/
|
||||
C5 判据出处行锚/C6 复核记录)+一次交叉核。
|
||||
C5 判据出处锚/C6 复核记录)+一次交叉核。
|
||||
约束:每红项一行「文件:行号 判据名 一句白话说明」,exit 0=全绿 / 1=有红 / 2=输入件缺失;
|
||||
卡片与报告存在性相对 --repo-root 解析(缺省=本文件上两级);待办清单与 roadmap 均已为新格式
|
||||
(2026-10-18 数据整改批),本闸已注册进 tools/run_all_gates.sh 文档域(流水线闸)。
|
||||
@@ -17,6 +17,9 @@ import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||
import anchor_util # 内容判据锚核验共用件(2026-11-09 判据锚内容化改版批)
|
||||
|
||||
DEFAULT_TODO = Path("待办清单.md")
|
||||
DEFAULT_ROADMAP = Path("roadmap.md")
|
||||
|
||||
@@ -64,9 +67,9 @@ CAP_README = Path("game/tests/README.md")
|
||||
# C4 验收档申报(2026-11-04 验收强度分级批,生效 2026-11-05;test-conventions §8.3)
|
||||
GRADE_RULE_DATE = (2026, 11, 5)
|
||||
GRADE_RE = re.compile(r"验收档\s*[::]\s*[高中低]")
|
||||
# C5 判据出处行锚(2026-11-05 立;test-conventions §8.3:需求点格须带现行文档判据句锚)
|
||||
# C5 判据出处锚(2026-11-05 立;2026-11-09 判据锚内容化改版批:内容节锚唯一形态、行号形态废止——
|
||||
# test-conventions §8.3:需求点格须带现行文档内容锚 `<路径>「节名」<行关键词>`,核验归 anchor_util 共用件)
|
||||
ANCHOR_RULE_DATE = (2026, 11, 5)
|
||||
ANCHOR_RE = re.compile(r"([^\s`'\"()(),,、::|]+\.md)\s*:\s*(\d+)") # 路径段放行中文名(系统文档全是中文名,test-conventions §8.3)
|
||||
# C6 复核记录(2026-11-05 立,生效 2026-11-06;req-card 必备结构第 17 节+纪律「复核发现四类分流」)
|
||||
REVIEW_RULE_DATE = (2026, 11, 6)
|
||||
REVIEW_HEADING_RE = re.compile(r"^#{2,4}\s*\d*\.?\s*复核记录")
|
||||
@@ -507,19 +510,22 @@ def capability_names(root):
|
||||
return names
|
||||
|
||||
|
||||
def anchor_valid(root, rel, lineno):
|
||||
"""判据出处行锚有效性:文档存在且行号在行数范围内(相对仓库根解析)。"""
|
||||
def anchor_verify(root, rel, sec, kw):
|
||||
"""判据出处锚核验:文档存在且内容锚可核(节名在档+关键词命中节内,口径归 anchor_util)。
|
||||
|
||||
返回 None=过;字符串=红因。"""
|
||||
p = root / rel.replace("\\", "/")
|
||||
if not p.is_file():
|
||||
return False
|
||||
return 1 <= lineno <= len(p.read_text(encoding="utf-8-sig").splitlines())
|
||||
return "文档不存在"
|
||||
return anchor_util.verify(rel, p.read_text(encoding="utf-8-sig"), sec, kw)
|
||||
|
||||
|
||||
def check_ledger(text, path, when, issues, root):
|
||||
"""C3 改动—验收对照表结构检查(生效 2026-10-21);C5 判据出处行锚(生效 2026-11-05)。
|
||||
"""C3 改动—验收对照表结构检查(生效 2026-10-21);C5 判据出处锚(2026-11-05 立,2026-11-09 内容化改版)。
|
||||
|
||||
只查:对照表节在不在、表头有没有五个必需列、有没有数据行、必填格空不空;
|
||||
C5=生效日后需求点格须带现行文档行锚且指向存在(test-conventions §8.3)。
|
||||
C5=需求点格须带现行文档内容锚 `<路径>「节名」<行关键词>` 且可核(节名在档+关键词命中节内),
|
||||
行号形态出现即红(2026-11-09 用户裁废止,test-conventions §8.3)。
|
||||
不判断测试是否真的覆盖改动——那是独立验收审计的职责,机器闸不猜语义。
|
||||
"""
|
||||
lines = text.splitlines()
|
||||
@@ -634,14 +640,22 @@ def check_ledger(text, path, when, issues, root):
|
||||
% cap_name[:24]))
|
||||
if when >= ANCHOR_RULE_DATE:
|
||||
point_cell = cells[idx["需求点"]] if idx.get("需求点", 99) < len(cells) else ""
|
||||
anchors = ANCHOR_RE.findall(point_cell)
|
||||
dead = anchor_util.LINE_ANCHOR_RE.findall(point_cell)
|
||||
if dead:
|
||||
issues.append((str(path), i + 1, "判据锚行号形态废止",
|
||||
"「需求点」格出现行号锚 `%s:%s`——2026-11-09 用户裁行号形态废止,"
|
||||
"改写内容锚 `<路径>「节名」<行关键词>`" % (dead[0][0], dead[0][1])))
|
||||
anchors = anchor_util.CONTENT_ANCHOR_RE.findall(point_cell)
|
||||
if not anchors:
|
||||
issues.append((str(path), i + 1, "判据出处行锚缺失",
|
||||
"「需求点」格须带判据出处行锚 `<现行文档相对路径>:<行号>`"
|
||||
"(2026-11-05 起,test-conventions §8.3)"))
|
||||
elif not any(anchor_valid(root, rel, int(no)) for rel, no in anchors):
|
||||
issues.append((str(path), i + 1, "判据出处行锚不可核",
|
||||
"锚指向的文档或行号不存在:%s" % point_cell[:40]))
|
||||
issues.append((str(path), i + 1, "判据出处锚缺失",
|
||||
"「需求点」格须带判据出处内容锚 `<现行文档相对路径>「节名」<行关键词>`"
|
||||
"(test-conventions §8.3,2026-11-09 内容化改版)"))
|
||||
else:
|
||||
for rel, sec, kw in anchors:
|
||||
why = anchor_verify(root, rel, sec, kw)
|
||||
if why is not None:
|
||||
issues.append((str(path), i + 1, "判据出处锚不可核",
|
||||
"%s「%s」%s:%s" % (rel, sec, kw, why)))
|
||||
if rows == 0:
|
||||
issues.append((str(path), header_line, "验收对照表零行",
|
||||
"表内没有数据行"))
|
||||
|
||||
Reference in new issue
Block a user