diff --git a/tools/pipeline_gate.py b/tools/pipeline_gate.py index 2ea41b98..0fd6c8c1 100644 --- a/tools/pipeline_gate.py +++ b/tools/pipeline_gate.py @@ -5,7 +5,8 @@ r"""pipeline_gate.py — 工作流程流水线闸(待办清单条目状态机 职责:机检待办清单条目(标题元数据与可选优先级档名/四属性齐备与旧属性名防回潮/状态词白名单/ 改动面四档白名单/条件凭据/无设计线段条目的证据指认/状态段累积制/特批/正文限长双判据/零命中) 与 roadmap 迭代总表(六列/状态五形态/卡片与报告存在性/编入时间与预计周期的 - 分档放行)/需求卡片口径(C1 文档义务/C2 设计事实源/C3 改动—验收对照表/C4 验收档申报)+一次交叉核。 + 分档放行)/需求卡片口径(C1 文档义务/C2 设计事实源/C3 改动—验收对照表/C4 验收档申报/ + C5 判据出处行锚/C6 复核记录)+一次交叉核。 约束:每红项一行「文件:行号 判据名 一句白话说明」,exit 0=全绿 / 1=有红 / 2=输入件缺失; 卡片与报告存在性相对 --repo-root 解析(缺省=本文件上两级);待办清单与 roadmap 均已为新格式 (2026-10-18 数据整改批),本闸已注册进 tools/run_all_gates.sh 文档域(流水线闸)。 @@ -66,6 +67,12 @@ GRADE_RE = re.compile(r"验收档\s*[::]\s*[高中低]") # C5 判据出处行锚(2026-11-05 立;test-conventions §8.3:需求点格须带现行文档判据句锚) ANCHOR_RULE_DATE = (2026, 11, 5) ANCHOR_RE = re.compile(r"([A-Za-z0-9_\-.\\/]+\.(?:md))\s*:\s*(\d+)") +# 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*复核记录") +REVIEW_TARGET_WORDS = ("自判", "补写", "待裁", "退回上游") +REVIEW_HEADER_WORDS = ("轮次", "去向") +REVIEW_REPORT_RE = re.compile(r"reports/复核-[^\s()(),,、|]+\.md") # 待办条目正文限长双判据(2026-11-04 立,doc-conventions §12.1:行数抓清单蔓延、字数抓单行堆砌) ENTRY_MAX_LINES = 12 ENTRY_MAX_CHARS = 700 @@ -455,7 +462,7 @@ def cross_check(links, index, issues, where): 一条目可有多张已写卡分编多个迭代(状态段累积「已编迭代 40+已编迭代 41」): 对每个已编迭代号核两件——总表该行存在;该行卡片列含本条目至少一张已写卡路径。 - 哪张卡编哪个迭代的卡级映射由编排四查与人工核验,闸不猜卡级映射。 + 哪张卡编哪个迭代的卡级映射由编排五查与人工核验,闸不猜卡级映射。 """ for line, no, cards in links: row = index.get(no) @@ -641,6 +648,55 @@ def check_ledger(text, path, when, issues, root): "表内没有数据行")) +def check_review(lines, start, path, issues, root): + """C6 复核记录的单节校验(req-card 必备结构第 17 节,生效 2026-11-06)。 + + 只查结构与可核性:节内每条记录行都要带四类去向之一(自判/补写/待裁/退回上游), + 表格行的处置结果格(末格)非空;节内须申报 reports/复核-*.md 路径且该文件在盘上存在。 + 分流分得对不对属语义判断,本闸不判。返回节末行下标(不含)。 + """ + end = len(lines) + for i in range(start + 1, len(lines)): + if lines[i].strip().startswith("#"): + end = i + break + records = 0 + for i in range(start + 1, end): + s = lines[i].strip() + if not s or s.startswith("|") and SEP_ROW_RE.match(s): + continue + is_row = s.startswith("|") + is_bullet = bool(re.match(r"^[-*]\s+", s)) + if not (is_row or is_bullet): + continue + if any(mark in s for mark in REVIEW_HEADER_WORDS): + continue + if not any(word in s for word in REVIEW_TARGET_WORDS): + issues.append((str(path), i + 1, "复核去向未标", + "该条复核记录缺四类去向之一(自判/补写/待裁/退回上游)——" + "先按 req-card「复核发现四类分流」判归属,前两类禁转成问句呈报用户")) + continue + if is_row: + cells = row_cells(s) + if not cells or not cells[-1].strip(): + issues.append((str(path), i + 1, "复核处置未填", + "处置结果格为空——须写改了卡内哪一节、退给了哪一段、或用户裁决的结论")) + records += 1 + if records == 0: + issues.append((str(path), start + 1, "复核记录空节", + "「复核记录」节内没有一条发现记录——卡片必经对抗复核并逐轮登记")) + sec = "\n".join(lines[start:end]) + reports = REVIEW_REPORT_RE.findall(sec) + if not reports: + issues.append((str(path), start + 1, "复核报告未申报", + "「复核记录」节末须申报复核报告落盘路径 reports/复核-<批名>-<日期>.md")) + for rel in reports: + if not (root / rel).is_file(): + issues.append((str(path), start + 1, "复核报告不可核", + "申报的复核报告不在盘上:%s" % rel)) + return end + + def check_cards(root, issues): """C1 文档义务口径(生效 2026-10-17)+C2 设计事实源声明(生效 2026-10-18) +C3 改动—验收对照表结构(生效 2026-10-21)。 @@ -654,6 +710,8 @@ def check_cards(root, issues): 能力行列(2026-10-29 起)既有与新增的名称都须在 game/tests/README.md 能力表在场 (新增=写卡同批入表,test-conventions §8.3);至少一行;必填格非空;全文含「验收审计」声明。 语义覆盖不在此判。 + C6 判据(生效 2026-11-06):有「复核记录」节;节内每条发现记录带四类去向之一 + (自判/补写/待裁/退回上游)且表格行处置格非空;节内申报 reports/复核-*.md 且文件在盘。 """ dirn = root / CARD_DIR if not dirn.is_dir(): @@ -680,6 +738,16 @@ def check_cards(root, issues): issues.append((str(path), 1, "验收档未申报", "须在卡片头部信息区申报「验收档:高/中/低」(2026-11-05 起," "test-conventions §8.3)")) + if when >= REVIEW_RULE_DATE: + lines = text.splitlines() + head = next((i for i, raw in enumerate(lines) + if REVIEW_HEADING_RE.match(raw.strip())), None) + if head is None: + issues.append((str(path), 1, "复核记录缺失", + "缺「复核记录」节——卡片必经对抗复核,逐轮发现与处置须登记在案" + "(req-card 必备结构第 17 节,2026-11-06 起生效)")) + else: + check_review(lines, head, path, issues, root) if when < SYNC_RULE_DATE: continue in_sec = False