工程(undo_proof.py 退役): 撤销证明禁令生效后的死工具删除——tools/undo_proof.py(159行,挂死防护批产物)删除前全仓引用面实查仅自身与待办条目、零闸件/脚本调用,git rm 无破坏;同族残留顺带清——.zcode|.qoder 两份 skills/iter/evals mock-card-iter40-1 的「改动—验收对照表」从四列(含「撤销/变异证明」列,样例单元格即撤销/变异式取证)改写为现行 req-card 六列口径(需求点|改动|验收测试|层级|真实入口|判据绑定面,判据改真实入口正向断言);待办条目瘦身改题「撤销/变异禁令防复发扫描——pipeline_gate 短语级扫描待裁」保留唯一余点(pipeline_gate.py 是否加短语级扫描,按「变异」单词扫会命中地形变异等玩法词)待用户裁,裁后整条删;范围外发现随条目呈报: req-card SKILL 第88/106行两句「撤销证明方式」残留与同文件第44行禁令自相矛盾,属 2026-10-24/28 两裁未扫净的技能面残留
This commit is contained in:
1 parent
7f49a8c27b
commit
acb8e83aca
2 files changed
+2
-161
No files matched your search
@@ -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 <f1,f2> --base <rev>
|
||||
可选 --out <json 路径> 落回执;--timeout <秒> 限制单次测试命令时长(缺省 3600)。
|
||||
|
||||
--revert-commit:按该笔提交触过的文件集回退到其父提交版本(改动须已提交在分支上)。
|
||||
--revert-files+--base:把指定文件回退到 <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()
|
||||
@@ -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 两裁未扫净的技能面残留,待用户发令直写。
|
||||
|
||||
**改动面**:工具代码
|
||||
|
||||
|
||||
Reference in new issue
Block a user