From acb8e83acaf86c6cdc4f56e6a1cf506e44e97f11 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Wed, 28 Oct 2026 07:08:03 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B7=A5=E7=A8=8B(undo=5Fproof.py=20=E9=80=80?= =?UTF-8?q?=E5=BD=B9):=20=E6=92=A4=E9=94=80=E8=AF=81=E6=98=8E=E7=A6=81?= =?UTF-8?q?=E4=BB=A4=E7=94=9F=E6=95=88=E5=90=8E=E7=9A=84=E6=AD=BB=E5=B7=A5?= =?UTF-8?q?=E5=85=B7=E5=88=A0=E9=99=A4=E2=80=94=E2=80=94tools/undo=5Fproof?= =?UTF-8?q?.py(159=E8=A1=8C,=E6=8C=82=E6=AD=BB=E9=98=B2=E6=8A=A4=E6=89=B9?= =?UTF-8?q?=E4=BA=A7=E7=89=A9)=E5=88=A0=E9=99=A4=E5=89=8D=E5=85=A8?= =?UTF-8?q?=E4=BB=93=E5=BC=95=E7=94=A8=E9=9D=A2=E5=AE=9E=E6=9F=A5=E4=BB=85?= =?UTF-8?q?=E8=87=AA=E8=BA=AB=E4=B8=8E=E5=BE=85=E5=8A=9E=E6=9D=A1=E7=9B=AE?= =?UTF-8?q?=E3=80=81=E9=9B=B6=E9=97=B8=E4=BB=B6/=E8=84=9A=E6=9C=AC?= =?UTF-8?q?=E8=B0=83=E7=94=A8,git=20rm=20=E6=97=A0=E7=A0=B4=E5=9D=8F;?= =?UTF-8?q?=E5=90=8C=E6=97=8F=E6=AE=8B=E7=95=99=E9=A1=BA=E5=B8=A6=E6=B8=85?= =?UTF-8?q?=E2=80=94=E2=80=94.zcode|.qoder=20=E4=B8=A4=E4=BB=BD=20skills/i?= =?UTF-8?q?ter/evals=20mock-card-iter40-1=20=E7=9A=84=E3=80=8C=E6=94=B9?= =?UTF-8?q?=E5=8A=A8=E2=80=94=E9=AA=8C=E6=94=B6=E5=AF=B9=E7=85=A7=E8=A1=A8?= =?UTF-8?q?=E3=80=8D=E4=BB=8E=E5=9B=9B=E5=88=97(=E5=90=AB=E3=80=8C?= =?UTF-8?q?=E6=92=A4=E9=94=80/=E5=8F=98=E5=BC=82=E8=AF=81=E6=98=8E?= =?UTF-8?q?=E3=80=8D=E5=88=97,=E6=A0=B7=E4=BE=8B=E5=8D=95=E5=85=83?= =?UTF-8?q?=E6=A0=BC=E5=8D=B3=E6=92=A4=E9=94=80/=E5=8F=98=E5=BC=82?= =?UTF-8?q?=E5=BC=8F=E5=8F=96=E8=AF=81)=E6=94=B9=E5=86=99=E4=B8=BA?= =?UTF-8?q?=E7=8E=B0=E8=A1=8C=20req-card=20=E5=85=AD=E5=88=97=E5=8F=A3?= =?UTF-8?q?=E5=BE=84(=E9=9C=80=E6=B1=82=E7=82=B9|=E6=94=B9=E5=8A=A8|?= =?UTF-8?q?=E9=AA=8C=E6=94=B6=E6=B5=8B=E8=AF=95|=E5=B1=82=E7=BA=A7|?= =?UTF-8?q?=E7=9C=9F=E5=AE=9E=E5=85=A5=E5=8F=A3|=E5=88=A4=E6=8D=AE?= =?UTF-8?q?=E7=BB=91=E5=AE=9A=E9=9D=A2,=E5=88=A4=E6=8D=AE=E6=94=B9?= =?UTF-8?q?=E7=9C=9F=E5=AE=9E=E5=85=A5=E5=8F=A3=E6=AD=A3=E5=90=91=E6=96=AD?= =?UTF-8?q?=E8=A8=80);=E5=BE=85=E5=8A=9E=E6=9D=A1=E7=9B=AE=E7=98=A6?= =?UTF-8?q?=E8=BA=AB=E6=94=B9=E9=A2=98=E3=80=8C=E6=92=A4=E9=94=80/?= =?UTF-8?q?=E5=8F=98=E5=BC=82=E7=A6=81=E4=BB=A4=E9=98=B2=E5=A4=8D=E5=8F=91?= =?UTF-8?q?=E6=89=AB=E6=8F=8F=E2=80=94=E2=80=94pipeline=5Fgate=20=E7=9F=AD?= =?UTF-8?q?=E8=AF=AD=E7=BA=A7=E6=89=AB=E6=8F=8F=E5=BE=85=E8=A3=81=E3=80=8D?= =?UTF-8?q?=E4=BF=9D=E7=95=99=E5=94=AF=E4=B8=80=E4=BD=99=E7=82=B9(pipeline?= =?UTF-8?q?=5Fgate.py=20=E6=98=AF=E5=90=A6=E5=8A=A0=E7=9F=AD=E8=AF=AD?= =?UTF-8?q?=E7=BA=A7=E6=89=AB=E6=8F=8F,=E6=8C=89=E3=80=8C=E5=8F=98?= =?UTF-8?q?=E5=BC=82=E3=80=8D=E5=8D=95=E8=AF=8D=E6=89=AB=E4=BC=9A=E5=91=BD?= =?UTF-8?q?=E4=B8=AD=E5=9C=B0=E5=BD=A2=E5=8F=98=E5=BC=82=E7=AD=89=E7=8E=A9?= =?UTF-8?q?=E6=B3=95=E8=AF=8D)=E5=BE=85=E7=94=A8=E6=88=B7=E8=A3=81,?= =?UTF-8?q?=E8=A3=81=E5=90=8E=E6=95=B4=E6=9D=A1=E5=88=A0;=E8=8C=83?= =?UTF-8?q?=E5=9B=B4=E5=A4=96=E5=8F=91=E7=8E=B0=E9=9A=8F=E6=9D=A1=E7=9B=AE?= =?UTF-8?q?=E5=91=88=E6=8A=A5:=20req-card=20SKILL=20=E7=AC=AC88/106?= =?UTF-8?q?=E8=A1=8C=E4=B8=A4=E5=8F=A5=E3=80=8C=E6=92=A4=E9=94=80=E8=AF=81?= =?UTF-8?q?=E6=98=8E=E6=96=B9=E5=BC=8F=E3=80=8D=E6=AE=8B=E7=95=99=E4=B8=8E?= =?UTF-8?q?=E5=90=8C=E6=96=87=E4=BB=B6=E7=AC=AC44=E8=A1=8C=E7=A6=81?= =?UTF-8?q?=E4=BB=A4=E8=87=AA=E7=9B=B8=E7=9F=9B=E7=9B=BE,=E5=B1=9E=202026-?= =?UTF-8?q?10-24/28=20=E4=B8=A4=E8=A3=81=E6=9C=AA=E6=89=AB=E5=87=80?= =?UTF-8?q?=E7=9A=84=E6=8A=80=E8=83=BD=E9=9D=A2=E6=AE=8B=E7=95=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tools/undo_proof.py | 159 -------------------------------------------- 待办清单.md | 4 +- 2 files changed, 2 insertions(+), 161 deletions(-) delete mode 100644 tools/undo_proof.py diff --git a/tools/undo_proof.py b/tools/undo_proof.py deleted file mode 100644 index 071fd430..00000000 --- a/tools/undo_proof.py +++ /dev/null @@ -1,159 +0,0 @@ -#!/usr/bin/env python3 -"""撤销证明自动化工具:一条命令完成「改前绿 → 真撤销复红 → 还原复绿」三连并出回执。 - -撤销证明的判据(test-conventions §8.4):只撤销该改动、跑能覆盖它的最窄测试、对应断言 -必须变红、还原后复绿;撤销后仍绿=断言没钉在改动上,判缺漏。本工具把这三连机械化—— -合法性属于「真撤销」(回退本批真实改动),全项目禁做变异类测试(改断言极性、置空状态、 -改名键、删调用行等人为制造红=伪造证据)。 - -用法: - python tools/undo_proof.py --cmd "<最窄测试命令>" --revert-commit <该批提交> - python tools/undo_proof.py --cmd "<最窄测试命令>" --revert-files --base - 可选 --out 落回执;--timeout <秒> 限制单次测试命令时长(缺省 3600)。 - - --revert-commit:按该笔提交触过的文件集回退到其父提交版本(改动须已提交在分支上)。 - --revert-files+--base:把指定文件回退到 (默认 HEAD)版本——适合改动尚未单独 - 成笔、混在当前尖里的场景。 - 测试命令在仓库根以 shell 执行;单套件直跑形态见 game/tests/README.md(测试工具手册)。 - -流程与退出码: - ① 改前基线:测试命令必须绿(红=改前就红,无证明意义,中止)。 - ② 真撤销:指定文件回退到基准版本 → 重跑命令,必须红(红=断言钉在该改动上)。 - ③ 还原:文件恢复到 HEAD → 重跑命令,必须绿。 - ④ 任何一步异常都先还原再退出;三步全过=退出码 0 并打印回执,否则退出码 1。 -""" - -from __future__ import annotations - -import argparse -import json -import subprocess -import sys -from pathlib import Path - -REPO_DEFAULT = Path(__file__).resolve().parent.parent -REPO = REPO_DEFAULT - - -def run(args: list[str], check: bool = True) -> str: - r = subprocess.run(["git", "-C", str(REPO)] + args, - capture_output=True, text=True, encoding="utf-8", errors="replace", - timeout=60) - if check and r.returncode != 0: - sys.stderr.write("git %s 失败:%s\n" % (" ".join(args), (r.stderr or "").strip())) - sys.exit(1) - return (r.stdout or "").strip() - - -def run_cmd(cmd: str, timeout: int) -> int: - r = subprocess.run(cmd, shell=True, cwd=str(REPO), capture_output=True, - text=True, encoding="utf-8", errors="replace", timeout=timeout) - if r.returncode != 0: - tail = "\n".join((r.stdout or "").splitlines()[-15:] or ["(无输出)"]) - sys.stderr.write(" | 测试命令失败(exit=%d),输出尾部:\n%s\n" % (r.returncode, tail)) - return r.returncode - - -def _in_rev(rev: str, f: str) -> bool: - r = subprocess.run(["git", "-C", str(REPO), "cat-file", "-e", "%s:%s" % (rev, f)], - capture_output=True, timeout=30) - return r.returncode == 0 - - -def apply_rev(rev: str, files: list[str]) -> None: - # 逐文件:基准版本存在 → 恢复到该版本;基准没有(该批新增的文件)→ 从工作区删除。 - for f in files: - if _in_rev(rev, f): - run(["checkout", rev, "--", f]) - else: - subprocess.run(["git", "-C", str(REPO), "rm", "-q", "-f", "--", f], - capture_output=True, text=True, timeout=30) - - -def main() -> None: - ap = argparse.ArgumentParser(description="自动三连出撤销证明红绿回执") - ap.add_argument("--cmd", required=True, help="最窄测试命令(shell 形,仓库根执行)") - ap.add_argument("--revert-commit", default=None, help="该批提交:按其触过的文件集回退到父版本") - ap.add_argument("--revert-files", default=None, help="逗号分隔的文件清单(与 --base 配用)") - ap.add_argument("--base", default="HEAD", help="回退基准版本(默认 HEAD)") - ap.add_argument("--timeout", type=int, default=3600, help="单次测试命令时长上限(秒)") - ap.add_argument("--repo", default=None, help="仓库/worktree 路径(缺省=本脚本所在仓库)") - ap.add_argument("--out", default=None, help="回执 JSON 落盘路径") - args = ap.parse_args() - - global REPO - REPO = Path(args.repo).resolve() if args.repo else REPO_DEFAULT - - try: - sys.stdout.reconfigure(encoding="utf-8", errors="replace") - sys.stderr.reconfigure(encoding="utf-8", errors="replace") - except Exception: - pass - - if bool(args.revert_commit) == bool(args.revert_files): - ap.error("--revert-commit 与 --revert-files 二选一") - - if args.revert_commit: - files = [x.strip() for x in run( - ["-c", "core.quotepath=false", "show", "--name-only", "--format=", - args.revert_commit]).splitlines() if x.strip()] - base = "%s^" % args.revert_commit - if not files: - sys.stderr.write("[撤销证明] 提交 %s 未触碰任何文件,无可撤销\n" % args.revert_commit) - sys.exit(2) - else: - files = [x.strip() for x in args.revert_files.split(",") if x.strip()] - base = args.base - - dirty = run(["-c", "core.quotepath=false", "status", "--porcelain", "--"] + files) - if dirty: - sys.stderr.write("[撤销证明] 指定文件带未提交改动,撤销与还原的基准不清,先提交:\n%s\n" % dirty) - sys.exit(2) - - steps: list[dict[str, object]] = [] - result = "pass" - - def step(name: str, expect: str, code: int) -> bool: - ok = (code == 0) if expect == "绿" else (code != 0) - steps.append({"step": name, "expect": expect, "exit": code, "ok": ok}) - print("[撤销证明] %s:期望%s,实测 exit=%d → %s" - % (name, expect, code, "符合" if ok else "不符合")) - return ok - - print("[撤销证明] 对象文件 %d 个;基准 %s;命令:%s" % (len(files), base, args.cmd)) - try: - if not step("① 改前基线", "绿", run_cmd(args.cmd, args.timeout)): - result = "fail(改前就红——先修绿再做撤销证明)" - else: - apply_rev(base, files) - try: - if not step("② 真撤销复红", "红", run_cmd(args.cmd, args.timeout)): - result = "fail(撤销后仍绿=断言没钉在该改动上,判缺漏)" - else: - apply_rev("HEAD", files) - if not step("③ 还原复绿", "绿", run_cmd(args.cmd, args.timeout)): - result = "fail(还原后未复绿——还原面有残留,查工作区)" - finally: - apply_rev("HEAD", files) - except subprocess.TimeoutExpired: - result = "fail(测试命令超时)" - finally: - # 兜底还原:无论走到哪一步,退出前把对象文件恢复到 HEAD 态 - apply_rev("HEAD", files) - - residue = run(["-c", "core.quotepath=false", "status", "--porcelain", "--"] + files) - if residue: - result = "fail(退出前工作区仍有对象文件残留改动,手工核对)" - - print("─" * 56) - print("[撤销证明] 结论:%s" % result) - if args.out: - with open(args.out, "w", encoding="utf-8") as f: - json.dump({"cmd": args.cmd, "base": base, "files": files, - "steps": steps, "result": result}, f, ensure_ascii=False, indent=1) - print("[撤销证明] 回执已落 %s" % args.out) - sys.exit(0 if result == "pass" else 1) - - -if __name__ == "__main__": - main() diff --git a/待办清单.md b/待办清单.md index 71d3026b..da16a1a4 100644 --- a/待办清单.md +++ b/待办清单.md @@ -407,9 +407,9 @@ **状态**:待执行 -#### undo_proof.py 退役——撤销证明禁令生效后的死工具(2026-10-28 登记,来源=批次移交) +#### 撤销/变异禁令防复发扫描——pipeline_gate 短语级扫描待裁(2026-10-28 登记,来源=批次移交) -**实况**:证据=test-conventions §8.4 现行口径(2026-10-24/2026-10-28 用户两裁:禁变异类测试+禁撤销类取证,验收唯一主判据=真实入口断言当轮执行并通过;AGENTS.md 红线「验收闭合」与 req-card SKILL 第 8 条同批换代)。`tools/undo_proof.py`(159 行,挂死防护批产物)为撤销证明的自动化载体(三连红绿回执并自还原),服务对象已被禁令整体废止——工具本体退役删除。同族残留顺带裁:`.zcode|.qoder/skills/iter/evals/files/mock-card-iter40-1.md` 表头「撤销/变异证明」字样(两份,反向残留);`tools/pipeline_gate.py` 是否加「变异测试/撤销证明」短语级扫描防复发(**短语级**——按「变异」单词扫会命中地形变异等玩法词,实测假红面大)。 +**实况**:证据=test-conventions §8.4 现行口径(2026-10-24/2026-10-28 用户两裁:禁变异类测试+禁撤销类取证)。原「undo_proof.py 退役」条目主件已完成(2026-10-28:`tools/undo_proof.py` 已删,git 历史可考;`.zcode|.qoder` 两份 mock-card-iter40-1 的「撤销/变异证明」列改写为现行 req-card 六列对照表口径,判据改真实入口正向断言)。剩唯一待裁点:`tools/pipeline_gate.py` 是否加「变异测试/撤销证明」短语级扫描防复发——**短语级**(按「变异」单词扫会命中地形变异等玩法词,实测假红面大)。用户裁加=执行后整条删除;裁不加=本条即删。另呈报一处范围外发现:req-card SKILL 第 88 行「验收步骤…+撤销证明方式」与第 106 行复核门「撤销证明方式写死」两句残留,与同文件第 44 行「卡内不得再出现撤销证明/变异证明列或步骤」自相矛盾,属 2026-10-24/28 两裁未扫净的技能面残留,待用户发令直写。 **改动面**:工具代码