工程(能力清单改版——验证账本改实现指导地图): 能力清单定位改实现指导+阅读导航+义务声明,只宣布「要验证什么」不记验证状态(落地账归改动—验收对照表+合回前独立审计)——game/tests/README.md 八张能力表瘦身四列(能力|系统|真实入口|代表用例,删「最后验证批次」「缺口」两列,存量行内容保留不回溯验证)、引区块改版(义务清单定位+写卡当场入表+实现照单落名+收尾核对,废「滞后一个迭代」),覆盖域表保留现状不动;test-conventions §8.3 维护锚整段改写、§8.9 三层模型中层「覆盖索引」改「义务清单」并同步法则与机器闸句;tools/capability_audit.py 判据随改(CAP_COLS 收窄四列含系统列非空、删「最后验证批次=真实 commit」检查、代表用例与真实入口改双通道=代码在或卡在 reports/需求卡片*.md——两头都无=红、条件闸行豁免保留代表用例口径);tools/pipeline_gate.py 对照表能力行收紧(「新增:<能力名>」同样要求该能力行已在清单在场——写卡与入表同 commit)。新判据经造红探针孤立验证(两头无=红/卡在=绿/代码在=绿;新增不在表=红)
This commit is contained in:
1 parent
fafb4fee0a
commit
2a087c9ead
4 files changed
+136
-139
No files matched your search
+34
-46
@@ -1,18 +1,19 @@
|
||||
#!/usr/bin/env python3
|
||||
r"""capability_audit.py — 能力清单、覆盖域表与 GDD 纯度机检(文档域闸件)
|
||||
|
||||
职责(test-conventions §8.9 三层模型的机检半边;规则正文=test-conventions §8.9+doc-conventions §5):
|
||||
职责(test-conventions §8.9 三层模型的机检半边;规则正文=test-conventions §8.3+§8.9+doc-conventions §5):
|
||||
A 文档纯度:design/gdd 全树现行文档禁写实现细节——代码路径(.gd/.py/.sh)、测试文件名、断言名;
|
||||
唯一例外=配置文件索引(.tres 表路径与表名)。围栏代码块内不扫。
|
||||
B 验收标准:design/gdd/systems/** 每篇须有非空的「## 验收标准」节(豁免件具名在册,附理由)。
|
||||
C 能力行结构:game/tests/README.md 能力表六列齐、逐行六格非空。
|
||||
D 代表用例可核:能力行「代表用例」的每条 `文件:函数`——文件存在、在 run_all.gd 套件 manifest 内、
|
||||
该文件有 `func 函数名(`。
|
||||
E 真实入口可核:能力行「真实入口」的每个 `类.方法()`,方法名须在产品码(game/ 除 tests//probe//sim/)
|
||||
有 `func 方法(`。
|
||||
F 最后验证批次:真实 commit(git cat-file 可解析);条件闸行豁免。
|
||||
G 覆盖域表:systems 每篇一行;状态∈{已覆盖,部分覆盖,未覆盖};非未覆盖行的「能力行」非空且每项都能
|
||||
C 能力行结构:game/tests/README.md 能力表四列齐、逐行四格非空。
|
||||
D 代表用例可核(双通道):能力行「代表用例」的每条 `文件:函数`——代码在(文件在 run_all.gd 套件
|
||||
manifest 内且该文件有 `func 函数名(`)或卡在(该文件名或函数名能在 reports/需求卡片*.md 中找到,
|
||||
供写卡时当场入表的计划用例锚定);两头都无=红(清单挂了无凭证义务)。
|
||||
E 真实入口可核(双通道):能力行「真实入口」的每个 `类.方法()`——产品码(game/ 除 tests//probe//sim/)
|
||||
有 `func 方法(`,或该方法名能在 reports/需求卡片*.md 中找到(计划入口同 D 口径);两头都无=红。
|
||||
F 覆盖域表:systems 每篇一行;状态∈{已覆盖,部分覆盖,未覆盖};非未覆盖行的「能力行」非空且每项都能
|
||||
对上能力表里的行名;部分/未覆盖行的缺口非空且指待办清单。
|
||||
条件闸行豁免(代表用例含「条件闸」字样)不参与 D/E。
|
||||
|
||||
自检四道(防扫描面空转假绿):系统文档数、能力行数、manifest 套件数、覆盖表行数任一为零 ⇒ exit 2。
|
||||
|
||||
@@ -23,7 +24,6 @@ exit:0=绿/1=有红项(--strict)/2=扫描面异常
|
||||
"""
|
||||
import argparse
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
@@ -32,12 +32,13 @@ GDD_DIR = REPO / "design" / "gdd"
|
||||
SYS_DIR = GDD_DIR / "systems"
|
||||
README = REPO / "game" / "tests" / "README.md"
|
||||
RUN_ALL = REPO / "game" / "tests" / "run_all.gd"
|
||||
CARDS_DIR = REPO / "reports"
|
||||
|
||||
PURITY_RE = re.compile(r"\.(gd|py|sh)\b")
|
||||
ASSERT_RE = re.compile(r"check_quiet\s*\(|\bcheck\s*\(")
|
||||
FENCE_RE = re.compile(r"^\s*```")
|
||||
|
||||
CAP_COLS = ("能力", "真实入口", "代表用例", "最后验证批次", "缺口")
|
||||
CAP_COLS = ("能力", "系统", "真实入口", "代表用例")
|
||||
COV_COLS = ("系统文档", "能力行", "状态", "缺口")
|
||||
STATUS_VALUES = ("已覆盖", "部分覆盖", "未覆盖")
|
||||
|
||||
@@ -49,7 +50,6 @@ ACCEPT_EXEMPT = {
|
||||
|
||||
CASE_RE = re.compile(r"([A-Za-z0-9_\-]+\.gd):([A-Za-z_][A-Za-z0-9_]*)")
|
||||
ENTRY_RE = re.compile(r"([A-Za-z_][A-Za-z0-9_]*)\.([A-Za-z_][A-Za-z0-9_]*)\s*\(\)")
|
||||
HASH_RE = re.compile(r"^`?([0-9a-f]{7,40})`?$")
|
||||
|
||||
reds = []
|
||||
selfcheck = []
|
||||
@@ -129,12 +129,11 @@ def product_funcs():
|
||||
return names
|
||||
|
||||
|
||||
def commit_ok(short, cache):
|
||||
if short not in cache:
|
||||
r = subprocess.run(["git", "-C", str(REPO), "cat-file", "-e", short + "^{commit}"],
|
||||
capture_output=True)
|
||||
cache[short] = r.returncode == 0
|
||||
return cache[short]
|
||||
def cards_corpus():
|
||||
"""reports/需求卡片*.md 全文拼接(双通道的「卡在」锚面);无卡返回空串。"""
|
||||
if not CARDS_DIR.is_dir():
|
||||
return ""
|
||||
return "\n".join(read(p) for p in sorted(CARDS_DIR.glob("需求卡片-*.md")))
|
||||
|
||||
|
||||
def check_purity():
|
||||
@@ -168,7 +167,7 @@ def capability_rows():
|
||||
selfcheck.append("game/tests/README.md 缺失")
|
||||
return [], []
|
||||
lines = read(README).splitlines()
|
||||
cap_tables = list(tables(lines, ("真实入口", "代表用例", "最后验证批次")))
|
||||
cap_tables = list(tables(lines, ("真实入口", "代表用例")))
|
||||
cov_tables = list(tables(lines, ("系统文档", "能力行", "状态")))
|
||||
if not cap_tables:
|
||||
selfcheck.append("README 能力表未找到")
|
||||
@@ -177,7 +176,7 @@ def capability_rows():
|
||||
return cap_tables, cov_tables
|
||||
|
||||
|
||||
def check_capability_rows(cap_tables, suites, funcs):
|
||||
def check_capability_rows(cap_tables, suites, funcs, cards):
|
||||
total = 0
|
||||
names = []
|
||||
suite_by_base = {}
|
||||
@@ -202,17 +201,16 @@ def check_capability_rows(cap_tables, suites, funcs):
|
||||
red(README.relative_to(REPO), no, "能力行结构",
|
||||
"「%s」行格数不足或空列(%s)" % (name[:16], "/".join(empty) or "列数"))
|
||||
continue
|
||||
if "条件闸" in short("代表用例") or "条件闸" in short("缺口"):
|
||||
if "条件闸" in short("代表用例"):
|
||||
continue
|
||||
check_cases(no, name, short("代表用例"), suite_by_base, funcs)
|
||||
check_entry(no, name, short("真实入口"), funcs)
|
||||
check_hash(no, name, short("最后验证批次"))
|
||||
check_cases(no, name, short("代表用例"), suite_by_base, funcs, cards)
|
||||
check_entry(no, name, short("真实入口"), funcs, cards)
|
||||
if total == 0:
|
||||
selfcheck.append("能力表零数据行")
|
||||
return names
|
||||
|
||||
|
||||
def check_cases(no, name, cell, suite_by_base, funcs):
|
||||
def check_cases(no, name, cell, suite_by_base, funcs, cards):
|
||||
chunks = [c.strip().strip("`") for c in re.split(r"[;;]", cell) if c.strip()]
|
||||
if not chunks:
|
||||
red(README.relative_to(REPO), no, "代表用例", "「%s」无代表用例" % name[:16])
|
||||
@@ -224,37 +222,27 @@ def check_cases(no, name, cell, suite_by_base, funcs):
|
||||
"「%s」格格式应为 `文件.gd:函数`:%s" % (name[:16], chunk[:40]))
|
||||
continue
|
||||
fname, fn = m.groups()
|
||||
# 双通道(test-conventions §8.9):代码在(套件 manifest 内且 func 在文件里)
|
||||
# 或卡在(文件名/函数名见于 reports/需求卡片*.md——写卡时当场入表的计划用例)。
|
||||
hits = suite_by_base.get(fname, [])
|
||||
if not hits:
|
||||
code_ok = bool(hits) and any(
|
||||
re.search(r"^(?:static\s+)?func\s+%s\s*\(" % re.escape(fn), read(RUN_ALL.parent / rel), re.M)
|
||||
for rel in hits)
|
||||
card_ok = bool(cards) and (fname in cards or fn in cards)
|
||||
if not code_ok and not card_ok:
|
||||
red(README.relative_to(REPO), no, "代表用例",
|
||||
"「%s」%s 不在 run_all.gd 套件 manifest" % (name[:16], fname))
|
||||
continue
|
||||
if not any(re.search(r"^(?:static\s+)?func\s+%s\s*\(" % re.escape(fn), read(RUN_ALL.parent / rel), re.M)
|
||||
for rel in hits):
|
||||
red(README.relative_to(REPO), no, "代表用例",
|
||||
"「%s」%s 无 `func %s(`" % (name[:16], fname, fn))
|
||||
"「%s」%s 无凭证——代码面(manifest+func)与需求卡片均未锚定" % (name[:16], chunk[:40]))
|
||||
|
||||
|
||||
def check_entry(no, name, cell, funcs):
|
||||
def check_entry(no, name, cell, funcs, cards):
|
||||
tokens = ENTRY_RE.findall(cell)
|
||||
if not tokens:
|
||||
red(README.relative_to(REPO), no, "真实入口", "「%s」无可核的 `类.方法()`" % name[:16])
|
||||
return
|
||||
for _, method in tokens:
|
||||
if method not in funcs:
|
||||
if method not in funcs and not (cards and method in cards):
|
||||
red(README.relative_to(REPO), no, "真实入口",
|
||||
"「%s」方法 %s() 在产品码无定义" % (name[:16], method))
|
||||
|
||||
|
||||
def check_hash(no, name, cell):
|
||||
m = HASH_RE.match(cell.strip())
|
||||
if not m:
|
||||
red(README.relative_to(REPO), no, "最后验证批次",
|
||||
"「%s」须为 commit 短哈希:%s" % (name[:16], cell[:24]))
|
||||
return
|
||||
if not commit_ok(m.group(1), {}):
|
||||
red(README.relative_to(REPO), no, "最后验证批次",
|
||||
"「%s」哈希 %s 不解析为 commit" % (name[:16], m.group(1)))
|
||||
"「%s」方法 %s() 无凭证——产品码无定义且需求卡片未载" % (name[:16], method))
|
||||
|
||||
|
||||
def check_coverage(cov_tables, names):
|
||||
@@ -324,7 +312,7 @@ def main():
|
||||
funcs = product_funcs()
|
||||
if not funcs:
|
||||
selfcheck.append("产品码零 `func`")
|
||||
names = check_capability_rows(cap_tables, suites, funcs)
|
||||
names = check_capability_rows(cap_tables, suites, funcs, cards_corpus())
|
||||
check_coverage(cov_tables, names)
|
||||
|
||||
if selfcheck:
|
||||
|
||||
Reference in new issue
Block a user