- tools/audit_active_state.py:判四条可机械测的膨胀签名(状态记录条目数>30、 快照行数>10、条目编号重复含并行撞号、指向 design/qa|research 中间文档的死指针), 另加快照整行自我重复一项(换代不彻底只往顶部 prepend 的签名)。 - 变异验证:对换代前的 34 条版本报 5 项红(A1/A2/A3/A4/A5 全见红), 对退役后的 3 条版本报绿;两态实测 RC=1/0。 - 入 run_all_gates.sh 为 08b(脚本即清单,不另立第二份闸清单),baseline.json 随批补 08b 绿项(本分支内实测绿,非臆造)。 - 规约挂指针两处:doc-conventions §10 机械闸句、process-conventions 批次清单 12。 - active.md 按 §10 退役:状态记录 34→3 条,快照 21 行自我重复压回 10 行, 删「项目当前位置」整节(滞后且与 roadmap/快照重复),未决面按迭代 27/28 重列。 - 不动 Stop 钩子:回合末判收口时机错配(一轮答完≠任务完成),机械层交合回闸, 语义层交收口技能。
121 lines
4.7 KiB
Python
121 lines
4.7 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
"""audit_active_state.py — 根目录 active.md 体量闸(doc-conventions §10 的机械面)
|
||
|
||
状态文件只记当下:条目只增不减就是腐烂。本脚本查五条机械可判的膨胀签名——
|
||
状态记录条目数超上限、快照节行数超上限、条目编号重复、快照整行自我重复、
|
||
指向中间文档(design/qa、design/research)的死指针。
|
||
|
||
退出码:0=全绿;1=有红(逐条打印差额,供收口批据此删条目)。
|
||
用法:python tools/audit_active_state.py [--file PATH] [--quiet]
|
||
"""
|
||
import argparse
|
||
import os
|
||
import re
|
||
import sys
|
||
|
||
ENTRY_LIMIT = 30 # §10:状态记录超过 30 条必须清
|
||
SNAPSHOT_LIMIT = 10 # §10:当前状态快照 ≤10 行
|
||
INTERMEDIATE_PREFIX = ("design/qa/", "design/research/")
|
||
ENTRY_RE = re.compile(r"^- \*\*(\d+)( 续)?")
|
||
BULLET_RE = re.compile(r"^- .{20,}")
|
||
PATH_RE = re.compile(r"design/(?:qa|research)/[^\s)`,,。)]+?\.md")
|
||
|
||
|
||
def split_sections(text):
|
||
"""按二级标题切段;标题前的内容归入 '' 键(文件头元信息,本闸不判)。"""
|
||
out, key = {}, ""
|
||
for line in text.splitlines():
|
||
if line.startswith("## "):
|
||
key = line[3:].strip()
|
||
out[key] = []
|
||
continue
|
||
out.setdefault(key, []).append(line)
|
||
return out
|
||
|
||
|
||
def find_key(sections, prefix):
|
||
for name in sections:
|
||
if name.startswith(prefix):
|
||
return name
|
||
return None
|
||
|
||
|
||
def audit(path, repo):
|
||
with open(path, encoding="utf-8") as f:
|
||
sections = split_sections(f.read())
|
||
reds = []
|
||
|
||
snap_key = find_key(sections, "当前状态快照")
|
||
rec_key = find_key(sections, "状态记录")
|
||
if snap_key is None:
|
||
reds.append("A0 快照节缺失:状态文件必须含「当前状态快照」节")
|
||
if rec_key is None:
|
||
reds.append("A0 状态记录节缺失:状态文件必须含「状态记录」节")
|
||
|
||
snap = [ln for ln in sections.get(snap_key, []) if BULLET_RE.match(ln)]
|
||
if len(snap) > SNAPSHOT_LIMIT:
|
||
reds.append("A1 快照节 %d 条,超上限 %d——先删被新一轮取代的旧行,别只往顶部加"
|
||
% (len(snap), SNAPSHOT_LIMIT))
|
||
|
||
seen = {}
|
||
for ln in snap:
|
||
seen[ln] = seen.get(ln, 0) + 1
|
||
dups = [ln for ln, n in seen.items() if n > 1]
|
||
if dups:
|
||
reds.append("A2 快照节有 %d 行整行自我重复(换代不彻底的签名):%s"
|
||
% (len(dups), dups[0][:60] + " …"))
|
||
|
||
nums, conts = [], []
|
||
for ln in sections.get(rec_key, []):
|
||
m = ENTRY_RE.match(ln)
|
||
if m:
|
||
(conts if m.group(2) else nums).append(int(m.group(1)))
|
||
if len(nums) > ENTRY_LIMIT:
|
||
reds.append("A3 状态记录 %d 条,超硬上限 %d——按 §10 判据删除已完结批次条目"
|
||
% (len(nums), ENTRY_LIMIT))
|
||
repeated = sorted({n for n in nums if nums.count(n) > 1})
|
||
if repeated:
|
||
reds.append("A4 条目编号重复(并行侧写撞号):%s"
|
||
% "、".join(str(n) for n in repeated))
|
||
|
||
body = "\n".join(sections.get(snap_key, []) + sections.get(rec_key, [])
|
||
+ sections.get(find_key(sections, "未决面"), []))
|
||
dead = sorted({p for p in PATH_RE.findall(body)
|
||
if not os.path.exists(os.path.join(repo, p.replace("`", "")))})
|
||
if dead:
|
||
reds.append("A5 指向中间文档的死指针 %d 处(文件已按 §11 退役,条目却还在引):%s"
|
||
% (len(dead), "、".join(dead)))
|
||
return reds, len(nums), len(snap)
|
||
|
||
|
||
def main():
|
||
ap = argparse.ArgumentParser(description="active.md 体量闸")
|
||
ap.add_argument("--file", default=None, help="待审文件(默认为本仓库根目录 active.md)")
|
||
ap.add_argument("--quiet", action="store_true", help="只输出结论行")
|
||
args = ap.parse_args()
|
||
|
||
repo = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||
path = args.file or os.path.join(repo, "active.md")
|
||
if not os.path.exists(path):
|
||
print("[active.md 体量闸] 找不到文件:%s" % path)
|
||
return 1
|
||
reds, entries, snap = audit(path, repo)
|
||
if reds:
|
||
print("[active.md 体量闸] FAIL:%d 项——状态记录 %d 条 / 快照 %d 行" % (len(reds), entries, snap))
|
||
for r in reds:
|
||
print(" " + r)
|
||
return 1
|
||
if not args.quiet:
|
||
print("[active.md 体量闸] PASS:状态记录 %d 条 / 快照 %d 行,编号唯一、指针全活" % (entries, snap))
|
||
return 0
|
||
|
||
|
||
if __name__ == "__main__":
|
||
try:
|
||
sys.stderr.reconfigure(encoding="utf-8", errors="replace")
|
||
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
||
except Exception:
|
||
pass
|
||
sys.exit(main())
|