内容锚改版批(判据锚行号形态废止): 锚形态唯一化<路径>「节名」<行关键词>——用户裁: 锚指向内容不指向行号,文档增删行不牵动锚与测试代码,判据句内容真改动才失配(失配=红,改句文档批同批改锚)。规约落文: 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:
DKChan committed 2026-11-09 08:22:04 +08:00
1 parent c713f335a9
commit a69310b738
123 files changed
+832 -716

No files matched your search

+91
View File
@@ -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
View File
@@ -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
View File
@@ -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, "验收对照表零行",
"表内没有数据行"))