tools(pipeline): 流水线闸对齐 doc-conventions §12 状态段累积制——+切段数不限逐段合法(废双轨恰两段上限),新增在场约束(下游段在场须上游闭合段,带日期特批豁免)与双轨设计阶段恰两段且一文档一美术;roadmap 卡片列 <br>/换行多卡逐卡校验存在性且免整格长度叙述红(§12.2 一格一行一卡),预计周期超 2 周带日期特批放行;交叉核依据收窄为同状态行已写卡段括号路径;变异验证 34 变体(6 绿 28 红)全过+还原回绿
This commit is contained in:
1 parent
4460a13eb2
commit
31bdecd6d5
1 file changed
+60
-16
+60
-16
@@ -2,9 +2,9 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
r"""pipeline_gate.py — 工作流程流水线闸(06 条目状态机+roadmap 迭代总表+一次交叉核)。
|
||||
|
||||
职责:机检 06-待办清单条目(标题元数据/属性齐备/状态词白名单/条件凭据/双轨/特批/零命中)
|
||||
与 roadmap 迭代总表(固定六列/状态四形态/卡片与报告存在性/周期≤2 周/单元格白名单/零命中);
|
||||
「已编迭代↔迭代行卡片列」交叉核只在此一处实现。
|
||||
职责:机检 06 条目(标题元数据/属性齐备/状态词白名单/条件凭据/状态段累积制在场约束/特批/
|
||||
零命中)与 roadmap 迭代总表(六列/状态四形态/卡片与报告存在性/周期≤2 周可特批豁免/
|
||||
单元格白名单/零命中);「已编迭代↔已写卡段↔迭代行卡片列」交叉核只此一处。
|
||||
约束:每红项一行「文件:行号 判据名 一句白话说明」,exit 0=全绿 / 1=有红 / 2=输入件缺失;
|
||||
卡片与报告存在性相对 --repo-root 解析(缺省=本文件上两级);现行 06 与 roadmap 尚为旧格式,
|
||||
对其红属预期(第二批数据整改后才注册上闸)。
|
||||
@@ -27,6 +27,11 @@ STATUS_WORDS = (
|
||||
)
|
||||
# 这些状态 = 设计未闭合,条目必须带「**缺口**」属性;其余状态不得带
|
||||
GAP_REQUIRED = ("待文档设计", "文档设计中", "待美术设计", "美术设计中")
|
||||
# 状态段累积制(doc-conventions §12.1):下游段在场时的上游闭合段在场约束
|
||||
DOWNSTREAM_WORDS = ("待写卡", "已写卡", "已编迭代")
|
||||
PRESENCE_FACES = ("文档轨", "美术轨", "双轨")
|
||||
DOC_DESIGN_WORDS = ("待文档设计", "文档设计中", "文档设计已闭合")
|
||||
ART_DESIGN_WORDS = ("待美术设计", "美术设计中", "美术设计已闭合")
|
||||
ROADMAP_HEADER = ("迭代代号", "状态", "需求卡片", "编入时间", "预计周期", "实现报告")
|
||||
RESERVED_RETIRED = "已退役(git 历史可考)"
|
||||
|
||||
@@ -36,6 +41,7 @@ RESEARCH_RE = re.compile(r"design/research/[^\s()(),,]+")
|
||||
ITER_RE = re.compile(r"迭代\s*(\d+)")
|
||||
APPROVAL_RE = re.compile(r"〔用户特批([^〕]*)〕")
|
||||
CRED_RE = re.compile(r"[((]([^()()]*)[))]")
|
||||
CARD_SEP_RE = re.compile(r"<br\s*/?>|\n|、") # 迭代行卡片列一格多卡的分隔(§12.2 一格一行一卡)
|
||||
ATTR_LINE_RE = re.compile(r"^\*\*(现状|缺口|归属|状态)\*\*\s*[::]\s*(.*)$")
|
||||
HEADING_RE = re.compile(r"^(#{1,4})\s+")
|
||||
SEP_ROW_RE = re.compile(r"^\|[\s\-|:]+$")
|
||||
@@ -138,13 +144,21 @@ def check_segment(word, creds, line, issues, where, root):
|
||||
|
||||
|
||||
def check_status(raw, line, face, issues, where, root):
|
||||
"""判据 3+双轨/特批/备选。返回 (状态词列表, 已写卡路径列表, 已编迭代号|None)。"""
|
||||
"""判据 3+状态段累积制(§12.1)+特批/备选。返回 (状态词列表, 已写卡路径列表, 已编迭代号|None)。
|
||||
|
||||
累积制:按+切段后段数不限,逐段各自合法;再查两条结构约束——
|
||||
下游段(待写卡/已写卡/已编迭代)在场时上游闭合段必须在场(带日期特批可豁免);
|
||||
双轨设计阶段(无下游段)必须恰两段且一文档一美术。
|
||||
"""
|
||||
content = raw.strip()
|
||||
m = re.match(r"^\*\*(.+)\*\*$", content)
|
||||
if m:
|
||||
content = m.group(1).strip()
|
||||
dated_approval = False
|
||||
for am in APPROVAL_RE.finditer(content):
|
||||
if not DATE_RE.search(am.group(1)):
|
||||
if DATE_RE.search(am.group(1)):
|
||||
dated_approval = True
|
||||
else:
|
||||
issues.append((where, line, "特批标记", "〔用户特批〕缺日期(带 YYYY-MM-DD 才放行跳轨)"))
|
||||
content = APPROVAL_RE.sub("", content)
|
||||
content = content.replace("(备选)", "").replace("(备选)", "").strip()
|
||||
@@ -152,10 +166,6 @@ def check_status(raw, line, face, issues, where, root):
|
||||
if not segs:
|
||||
issues.append((where, line, "状态词非法", "状态行内容为空"))
|
||||
return [], [], None
|
||||
if len(segs) > 2:
|
||||
issues.append((where, line, "双轨段数", "状态 %d 段(+连接时恰好两段)" % len(segs)))
|
||||
if face == "双轨" and len(segs) != 2:
|
||||
issues.append((where, line, "双轨段数", "设计面=双轨的状态须恰好两段(现 %d 段)" % len(segs)))
|
||||
words, cards, iter_no = [], [], None
|
||||
for seg in segs:
|
||||
creds = CRED_RE.findall(seg)
|
||||
@@ -167,6 +177,24 @@ def check_status(raw, line, face, issues, where, root):
|
||||
cards.append(rp)
|
||||
if it is not None:
|
||||
iter_no = it
|
||||
downstream = [w for w in words if w in DOWNSTREAM_WORDS]
|
||||
if downstream and face in PRESENCE_FACES and not dated_approval:
|
||||
need = []
|
||||
if face in ("文档轨", "双轨") and "文档设计已闭合" not in words:
|
||||
need.append("文档设计已闭合(凭据)")
|
||||
if face in ("美术轨", "双轨") and "美术设计已闭合" not in words:
|
||||
need.append("美术设计已闭合(凭据)")
|
||||
if need:
|
||||
issues.append((where, line, "在场约束",
|
||||
"下游段(%s)在场但缺上游闭合段 %s(带日期特批可豁免)"
|
||||
% ("、".join(downstream), "、".join(need))))
|
||||
if face == "双轨" and not downstream:
|
||||
n_doc = sum(1 for w in words if w in DOC_DESIGN_WORDS)
|
||||
n_art = sum(1 for w in words if w in ART_DESIGN_WORDS)
|
||||
if len(segs) != 2 or n_doc != 1 or n_art != 1:
|
||||
issues.append((where, line, "双轨段数",
|
||||
"设计面=双轨设计阶段须恰两段且一文档一美术(现 %d 段:文档轨 %d、美术轨 %d)"
|
||||
% (len(segs), n_doc, n_art)))
|
||||
return words, cards, iter_no
|
||||
|
||||
|
||||
@@ -210,12 +238,20 @@ def row_cells(row):
|
||||
return [c.strip() for c in row.split("|")[1:-1]]
|
||||
|
||||
|
||||
def cell_shape(cell):
|
||||
"""判据 7:单元格白名单外的形态说明;合法返回 None。"""
|
||||
def cell_shape(cell, card_col=False):
|
||||
"""判据 7:单元格白名单外的形态说明;合法返回 None。
|
||||
|
||||
需求卡片列按多卡分隔符逐段看叙述、总长不设限(§12.2 一格一行一卡是合法形态),
|
||||
路径存在性由列规则逐卡承担;其余列维持整格叙述检查。
|
||||
"""
|
||||
if cell == "":
|
||||
return "空白单元格"
|
||||
if re.fullmatch(r"〔[^〕]*〕", cell):
|
||||
return None if DATE_RE.search(cell) else "特批标记缺日期"
|
||||
if card_col:
|
||||
if any(mark in part for part in CARD_SEP_RE.split(cell) for mark in ("。", ",")):
|
||||
return "成段叙述(卡片列一格一行一卡,禁叙述文字)"
|
||||
return None
|
||||
if "。" in cell or "," in cell or len(cell) > 60:
|
||||
return "成段叙述(只许路径/日期/状态词/保留字/—/≈N 周/特批标记)"
|
||||
return None
|
||||
@@ -240,7 +276,7 @@ def check_iter_table(block, issues, where, root):
|
||||
continue
|
||||
code, status, cards, date, weeks, report = cells
|
||||
for ci, cell in enumerate(cells):
|
||||
bad = cell_shape(cell)
|
||||
bad = cell_shape(cell, card_col=(ci == 2))
|
||||
if bad:
|
||||
issues.append((where, ln, "单元格白名单",
|
||||
"第 %d 列「%s」:%s" % (ci + 1, cell[:24], bad)))
|
||||
@@ -252,15 +288,23 @@ def check_iter_table(block, issues, where, root):
|
||||
issues.append((where, ln, "总表状态列",
|
||||
"「%s」不在四形态(待开工/进行中/已完成(YYYY-MM-DD)/已终止(YYYY-MM-DD))" % status))
|
||||
if norm_parens(cards) != norm_parens(RESERVED_RETIRED):
|
||||
for part in (p.strip() for p in cards.split("、")):
|
||||
for part in CARD_SEP_RE.split(cards):
|
||||
part = part.strip()
|
||||
if part and not (root / part).is_file():
|
||||
issues.append((where, ln, "卡片路径不存在",
|
||||
"需求卡片列 %s 相对仓库根不存在" % part))
|
||||
if not re.fullmatch(r"\d{4}-\d{2}-\d{2}", date):
|
||||
issues.append((where, ln, "编入时间", "「%s」须为 YYYY-MM-DD" % date))
|
||||
wm = WEEKS_RE.match(weeks)
|
||||
if not wm or float(wm.group(1)) > 2:
|
||||
issues.append((where, ln, "预计周期", "「%s」须为「≈N 周」且 N≤2(允许 0.5 步进)" % weeks))
|
||||
# 预计周期:≈N 周(N≤2);超 2 周须格内带日期特批标记(§12.2/§12.4)
|
||||
week_marks = APPROVAL_RE.findall(weeks)
|
||||
week_core = APPROVAL_RE.sub("", weeks).strip()
|
||||
wm = WEEKS_RE.match(week_core)
|
||||
if not wm:
|
||||
issues.append((where, ln, "预计周期",
|
||||
"「%s」须为「≈N 周」形态(N≤2,允许 0.5 步进)" % weeks))
|
||||
elif float(wm.group(1)) > 2 and not any(DATE_RE.search(mk) for mk in week_marks):
|
||||
issues.append((where, ln, "预计周期",
|
||||
"「%s」超 2 周且无带日期特批标记〔用户特批 YYYY-MM-DD …〕" % weeks))
|
||||
done = status_n.startswith("已完成(")
|
||||
report_n = norm_parens(report)
|
||||
if report_n == "—":
|
||||
|
||||
Reference in new issue
Block a user