From 76868d7228dc9605bb859e3b452d5740122da887 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Sun, 18 Oct 2026 19:56:11 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E9=97=B8):=20pipeline=5Fgate=20=E8=A1=A5?= =?UTF-8?q?=E4=B8=A4=E6=9D=A1=E5=8D=A1=E7=89=87=E6=9C=BA=E6=A3=80=20C1/C2?= =?UTF-8?q?=E2=80=94=E2=80=94=E5=8D=A1=E7=89=87=E5=86=B2=E7=AA=81=E4=BF=AE?= =?UTF-8?q?=E8=AE=A2=E6=89=B9=E7=9A=84=E9=98=B2=E5=9B=9E=E5=BD=92=E5=85=9C?= =?UTF-8?q?=E7=BD=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - C1 文档义务口径(生效=文件名日期 ≥2026-10-17):关联文档同步面表内目标格指向 design/(含 ../gdd/ 等相对写法)或根规则文件、义务格仍写「随批」者红——req-card §6 改口径后旧措辞「机械面随批」不得再入卡 - C2 设计事实源声明(生效 ≥2026-10-18):全文既无「设计事实源」也无「唯一事实源」者红;两种合格写法都认(独立小节/头部「来源:…(唯一事实源:…)」),避免按小节名字面判死全库卡 - 只追新规生效日之后的卡,更早的卡属已实现批次历史留档,不回头改(同「换代清零+定期跑批」先例,不设豁免清单、不加编辑期钩子) - 负向验证已做:临时卡注入两处违例→两红(design 目标红、game/README 行不误红)→删除复跑绿;文档域闸全绿无新增红(回执 commit=ad48d2a2) - 同批另修两份技能文件的「设计事实源」字面对齐(.zcode/skills 与 .qoder/skills 双写逐字一致)——两目录均被 gitignore,该改动只在本机盘上、不入 git --- tools/pipeline_gate.py | 75 +++++++++++++++++++++++++++++++++++++++--- 1 file changed, 71 insertions(+), 4 deletions(-) diff --git a/tools/pipeline_gate.py b/tools/pipeline_gate.py index 9388e2a6..559d8c1f 100644 --- a/tools/pipeline_gate.py +++ b/tools/pipeline_gate.py @@ -1,11 +1,13 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- -r"""pipeline_gate.py — 工作流程流水线闸(06 条目状态机+roadmap 迭代总表+一次交叉核)。 +r"""pipeline_gate.py — 工作流程流水线闸(06 条目状态机+roadmap 迭代总表+需求卡片口径+一次交叉核)。 职责:机检 06 条目(标题元数据/属性齐备/状态词白名单/条件凭据/状态段累积制在场约束/特批/ 零命中)与 roadmap 迭代总表(六列/状态四形态/卡片与报告存在性/编入时间与预计周期的 历史行「—」豁免/周期≤2 周可特批豁免/单元格白名单/零命中);「已编迭代↔已写卡段↔迭代行 - 卡片列」交叉核只此一处。 + 卡片列」交叉核只此一处。2026-10-18 卡片冲突修订批补两条卡片机检:C1 关联文档同步面的 + 文档义务口径(生效 2026-10-17)、C2 设计事实源声明(生效 2026-10-18)——两条都按文件名 + 日期只追新规生效日之后的卡,旧卡属 git 历史留档不回头改。 约束:每红项一行「文件:行号 判据名 一句白话说明」,exit 0=全绿 / 1=有红 / 2=输入件缺失; 卡片与报告存在性相对 --repo-root 解析(缺省=本文件上两级);06 与 roadmap 均已为新格式 (2026-10-18 数据整改批),本闸已注册进 tools/run_all_gates.sh 文档域(08c 流水线闸)。 @@ -36,6 +38,16 @@ ART_DESIGN_WORDS = ("待美术设计", "美术设计中", "美术设计已闭合 ROADMAP_HEADER = ("迭代代号", "状态", "需求卡片", "编入时间", "预计周期", "实现报告") RESERVED_RETIRED = "已退役(git 历史可考)" +# ---- 卡片机检(2026-10-18 卡片冲突修订批补)---- +CARD_GLOB = "需求卡片-*.md" +CARD_DIR = "design/research" +SYNC_RULE_DATE = (2026, 10, 17) # req-card §6:实现批禁写 design/ 与根规则文件,义务一律列反哺点 +SOURCE_RULE_DATE = (2026, 10, 18) # req-card §2:设计事实源必填 +SOURCE_MARKS = ("设计事实源", "唯一事实源") # 两种合格写法,与库内既有卡片实际形态对齐 +SYNC_HEADING_RE = re.compile(r"^#{2,3}\s*\d*\.?\s*关联文档同步面") +BANNED_DOC_MARKS = ("design/", "../gdd/", "../registry/", "../research/", "../qa/", + "../dev/", "../ux/", "../art/", "roadmap.md", "CLAUDE.md") + DATE_RE = re.compile(r"\d{4}-\d{2}-\d{2}") VER_RE = re.compile(r"[vV]\d+(?:\.\d+)*") RESEARCH_RE = re.compile(r"design/research/[^\s()(),,]+") @@ -361,6 +373,60 @@ def cross_check(links, index, issues, where): "迭代 %d 行卡片列不含本条目已写卡路径 %s" % (no, card))) +def card_date(path): + """文件名尾日期 → (y, m, d) 元组;取不到返回 None(该卡跳过两条新规机检)。""" + found = DATE_RE.findall(path.stem) + if not found: + return None + return tuple(int(x) for x in found[-1].split("-")) + + +def check_cards(root, issues): + """C1 文档义务口径(生效 2026-10-17)+C2 设计事实源声明(生效 2026-10-18)。 + + 只检文件名日期达生效日的卡——更早的卡属已实现批次的历史留档,不回头改。 + C1 判据:「关联文档同步面」表内,目标格指向 design/ 或根规则文件、而义务格仍写 + 「随批」者红(2026-10-17 req-card §6 改口径后,这类点只能列 reports/ 反哺点清单)。 + C2 判据:全文既无「设计事实源」也无「唯一事实源」者红(两种写法都与库内实态对齐)。 + """ + dirn = root / CARD_DIR + if not dirn.is_dir(): + issues.append((str(dirn), 1, "零命中自检", "卡片目录不存在,C1/C2 无卡可检")) + return 0 + n = 0 + for path in sorted(dirn.glob(CARD_GLOB)): + when = card_date(path) + if when is None: + continue + n += 1 + text = path.read_text(encoding="utf-8-sig") + if when >= SOURCE_RULE_DATE and not any(mark in text for mark in SOURCE_MARKS): + issues.append((str(path), 1, "设计事实源缺失", + "须含「设计事实源」或「唯一事实源」声明句(req-card §2 三选一," + "GDD 节+版本号/样张+art-bible 版本/工程直入证据指认)")) + if when < SYNC_RULE_DATE: + continue + in_sec = False + for lineno, raw in enumerate(text.splitlines(), start=1): + stripped = raw.strip() + if SYNC_HEADING_RE.match(stripped): + in_sec = True + continue + if in_sec and stripped.startswith("#"): + in_sec = False + continue + if not in_sec or not stripped.startswith("|") or SEP_ROW_RE.match(stripped): + continue + cells = row_cells(stripped) + if len(cells) < 3 or cells[0] in ("文档", ""): + continue + if any(mark in cells[0] for mark in BANNED_DOC_MARKS) and "随批" in cells[2]: + issues.append((str(path), lineno, "文档义务口径", + "目标「%s」属实现批禁写面,义务却写「随批」——改标反哺点并列进 " + "reports/ 清单(req-card §6,2026-10-17 起)" % cells[0][:36])) + return n + + def main(): ap = argparse.ArgumentParser( description="工作流程流水线闸:06 条目状态机+roadmap 迭代总表+一次交叉核") @@ -382,13 +448,14 @@ def main(): links = check_todo(todo_text, issues, str(todo_path), root) index = check_roadmap(roadmap_text, issues, str(roadmap_path), root) cross_check(links, index, issues, str(todo_path)) + n_cards = check_cards(root, issues) for where, line, name, msg in issues: print("%s:%d %s %s" % (display(where, root), line, name, msg)) if issues: print("流水线闸 FAIL:红项 %d" % len(issues)) return 1 - print("流水线闸 PASS:06 条目 %d · 总表数据行 %d · 交叉核 %d 链全过" - % (len(parse_todo(todo_text)), len(index), len(links))) + print("流水线闸 PASS:06 条目 %d · 总表数据行 %d · 交叉核 %d 链 · 卡片口径 %d 张全过" + % (len(parse_todo(todo_text)), len(index), len(links), n_cards)) return 0