Files
admin 19101acd65 init: workbuddy_skills 重建,仅收录 session-mechanism
- 按用户指示清空原有 25 技能内容,只提交 session-mechanism(57 文件)
- 附 .gitignore(产物 + 本机凭据)
- 令牌明文已脱敏(历史 .neodata_token 与 pitfalls 引用均不入库)
- 本提交为孤儿提交(父提交为空),历史自此重新开始
2026-10-05 14:13:24 +08:00

263 lines
13 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""judge_audit —— **判据元体检**(2026-10-04 建)
一句话:**给判据本身做体检**,防止「判据恒绿 / 假绿」。
🔴🔴 为什么必须有这个工具(⛔ 不是又写个检查)
今天栽的坑收敛后**只有两个根因**,都不是「不小心」,都是**结构性的**:
**A. 判据与被检对象之间的「连接」没被验证**
判据**看起来**在验某功能,实际压根没连上被检对象 ⇒ 恒绿。今天至少 6 次:
· V4 判据里**自己重算**一遍算法(`lambda`),与被检的 `_norm_ws` 无关
· V6 锚在「源码里出现过 `automations` 这个**词**」⇒ 把查询改空照样绿
· 夹具只覆盖**顺带**跑到的分支(写回那步恒空转 ⇒ 判据删了它照样全绿)
· 锚点**凭记忆**写(文档写「正确处置=」我写「正解=」⇒ 基线直接红)
⚠️ 共同点:**判据绿灯与被检对象状态无关**。
**B. 验证动作本身出错,而我不检查它**
· 变异脚本自己 `IndexError`(`sys.argv` 少传参)崩掉,**照样打出 PASS 2/FAIL 0**
· 变异后**只重置一份**文件 ⇒ 下一轮累积污染 ⇒ 后面几轮结论全作废
⚠️ 共同点:**验证器自己的失败被当成了结论**。
⇒ 纪律(`references/00-动手前必过.md`)挡不住这两条 —— 今天写了一整天纪律,
该栽还是栽。**根因是「靠记得」⇒ 解法只能是「机器强制」**。
⇒ 本工具做的就是把这两条**变成读数**:⛔ 你不跑它,你自己看不见。
用法(三个都只读,⛔ 不写任何文件)
1) 元体检(扫 selftest.py 里所有判据的写法):
python judge_audit.py --audit selftest.py
2) 变异体检(给某条判据造变异,验证它真会红):
python judge_audit.py --mutate <被检文件> "<锚点>" "<替换成>" -k <用例关键字>
3) 列已知弱锚点清单(自测要断言这个表非空):
python judge_audit.py --list-rules
"""
from __future__ import annotations
import argparse
import hashlib
import io
import os
import re
import shutil
import subprocess
import sys
import tempfile
from pathlib import Path
try:
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
except Exception:
pass
HERE = Path(__file__).resolve().parent
SELFTEST = HERE / "selftest.py"
PY = sys.executable
# ── 元规则表:⛔ 每条都注明「今天栽在哪」 ────────────────────────────────
# kind: weak = 弱锚点(只证明「文本出现过」,不证明「行为对」)
# self = 自我重算(判据自己算一遍,与被检对象无关)
# weak_ev= 弱行为判据(跑了函数但只查一个弱条件)
RULES = [
# ---- A 类:判据没连上被检对象 ----
("self-recompute", "self",
# 🔴 2026-10-04 修:原式 `=\s*lambda\s+\w+\s*:` **漏抓 `(lambda s: …)`**(带括号的写法)
# ⇒ 实测探针明明写了 `(lambda s: s.lower())` 却只命中 1 类规则,
# 判据却断言「≥2 类」⇒ **假红**。⇒ 括号可选,并允许更宽的形参。
r"=\s*\(?\s*lambda\s[\s\w,*]*:",
"判据里用 `lambda` **自己重算**被检的算法 ⇒ 改被检对象它照样绿(今天 V4)"),
("anchor-text-only", "weak",
r"\b(in|not in)\s+(src|T|SK|TXT|txt|body|content)\b",
"锚在「源码/文本里出现过某词」⇒ ⛔ 证明不了行为(今天 V6;判据元规则之一)"),
("anchor-imagined", "weak",
r"正解=|正确处置=|正确处置= ",
"锚点是**凭记忆重写**的措辞 ⇒ 文档微调就假红/假绿(今天多次)"),
# ---- B 类:验证器自身的失败被当成结论 ----
("mutate-no-md5", "self",
r"def\s+mut\w*\(.*\):(?![^}]*md5)",
"变异函数**不核对 md5** ⇒ 变异没真植入时照样打 PASS(今天 `sys.argv` 少传参那次)"),
("no-restore-check", "self",
r"replace\(a,\s*b\s*,\s*1\)(?![^)]*\n[^)]*(md5|restore|还原))",
"只 `replace` 不**核对还原** ⇒ 多轮变异互相污染(今天累积污染那次)"),
# ---- 附带的经典坑 ----
("glob-existence", "weak_ev",
r"glob\([^)]*\*\*[^)]*\)[^\n]*(exists\(\)|不存在|零命中)",
"用 `glob('**/x')` 判「文件不存在」⇒ ⛔ `**` **不穿透点目录**(今天栽过 4 次)"),
("count-without-filter", "weak_ev",
r"count\(\s*[\"']✗[\"']\s*\)|\.count\(b?[\"']✗",
"数 `✗` **不排除「报告型」行** ⇒ 报告型用例一多就误判(今天栽过)"),
]
# ── 误报白名单:⛔ 元规则表本身**也会**假绿/假红(元规则也判不准) ──────
# 🔴 2026-10-04 实测踩到:`glob-existence` 规则把**教训本身**当成了错误写法 ——
# 那处 `import glob` 正是「自建夹具证明 glob 会漏」这条判据的**被测行为**,
# 不是"用 glob 判文件不存在"的错误用法。⇒ 扫写法天然分不清
# 「引用教训」与「犯错误」。⇒ 只能显式列出已知误报。
# ⛔ 代价:白名单要人维护 ⇒ 每加一条误报必须回来加一行(宁可漏报,不许把真问题压掉)。
FALSE_POSITIVE = {
# 行号 → 原因。行号随文件变动会漂 ⇒ 漂了就当没命中(宁可漏报)。
"selftest.py:2762": "`import glob` 是**被测行为**(自建夹具证明 glob 会漏)"
"⛔ 不是「用 glob 判文件不存在」的错误写法",
}
def _fp_key(path: Path, line: int) -> str:
return "%s:%d" % (Path(path).name, line)
def _md5(p) -> str:
return hashlib.md5(Path(p).read_bytes()).hexdigest()
def _lines(path):
out = []
for i, ln in enumerate(Path(path).read_text(encoding="utf-8", errors="replace").splitlines(), 1):
out.append((i, ln))
return out
# ── ① 元体检 ────────────────────────────────────────────────────────────
def audit(target: Path) -> int:
src = target.read_text(encoding="utf-8", errors="replace")
hits = []
fps = []
for name, kind, pat, why in RULES:
rx = re.compile(pat, re.M)
for i, ln in _lines(target):
if rx.search(ln):
k = _fp_key(target, i)
if k in FALSE_POSITIVE:
fps.append({"line": i, "why": FALSE_POSITIVE[k], "rule": name})
continue
hits.append({"rule": name, "kind": kind, "line": i,
"why": why, "text": ln.strip()[:96]})
by = {}
for h in hits:
by.setdefault(h["rule"], []).append(h["line"])
print("=" * 72)
print("判据元体检 · %s" % target)
print("=" * 72)
if fps:
print(" ⓘ 已知误报 %d 处(⛔ 已人工判定为「引用教训」⛔ 不是错误写法):"
% len(fps))
for f in fps[:5]:
print(" · 行 %d [%s] %s" % (f["line"], f["rule"], f["why"][:70]))
if len(fps) > 5:
print(" … 另有 %d 处" % (len(fps) - 5))
print()
if not hits:
print(" ✅ 未命中任何已知弱锚点模式")
print("\n ⓘ ⛔ **「没命中」不等于「没有」** —— 本表只覆盖已知的坑,")
print(" 新形态的弱锚点要**手写变异**才抓得到 ⇒ 仍须 `--mutate`。")
return 0
print(" ⚠️ 命中 %d 处(⛔ 这些是**可疑写法**,⛔ 不等于一定是错):\n" % len(hits))
for name, kind, _pat, why in RULES:
if name not in by:
continue
print(" 🔴 %-20s [%s] 行 %s" % (name, kind, by[name][:8]))
print(" ↳ %s\n" % why)
print(" 处置:逐条改判据(⛔ 别改规则表消警告 —— 那是把体温计砸了)")
print(" ⓘ 确认是误报就加进 `FALSE_POSITIVE`(⛔ 但要写清为什么,⛔ 不许默默加)")
return len(hits)
def do_mutate(tgt: Path, old: str, new: str, kw: str, cases: int = 1) -> int:
"""真植入变异 ⇒ 跑判据 ⇒ 必须报红 ⇒ 还原并**核对 md5**。
🔴🔴 2026-10-04 修(今天第三次栽在"验证器自己出错却输出结论"):
原实现把被检文件**拷到临时目录**再跑 selftest ⇒ 但 `selftest.py` 里
`HERE = Path(__file__).resolve().parent` 加载的是**原目录**下那份
⇒ **变异根本没进被检对象** ⇒ 判据当然不报红,而输出却写着
「⛔ 没报红 ❌」+ rc=0(看着像"验证过了,实际什么也没验证")。
⇒ 改成**原地改 → 跑 → `finally` 还原**(还原有 md5 兜底)。
🔴 本函数自己就是「B 类根因」的教训对象,故:
· 变异**没真植入**(`old` 不在)⇒ rc=2,⛔ **绝不**输出"通过"
· 还原后**核对 md5** ⇒ 对不上 rc=2(⛔ 环境被污染,后续结论全作废)
· 判据输出**同时**报 rc 与是否报红 ⇒ ⛔ 不靠"看输出像不像 PASS"
· ⛔ 跑完**一定**还原(`finally`),异常路径也不许留下污染
"""
if not tgt.exists():
print("⛔ 被检文件不存在:%s" % tgt)
return 2
text = tgt.read_bytes().decode("utf-8", errors="replace")
if old not in text:
print("⛔ 锚点不在被检文件里 ⇒ **变异未生效** ⇒ 本轮作废(⛔ 不许输出通过)")
print(" 锚点:%r" % old[:80])
return 2
orig = tgt.read_bytes()
md5_0 = hashlib.md5(orig).hexdigest()
n_red = 0
try:
for k in range(cases):
try:
mutated = text.replace(old, new, 1)
if mutated == text:
print(" ⛔ 变异 %d 植入后文本**没变** ⇒ 等价变异,本轮作废" % (k + 1))
print(" ⛔ 变异必须动**语义**,⛔ 不能只动写法(今天栽过)")
return 2
tgt.write_text(mutated, encoding="utf-8", newline="")
if _md5(tgt) == md5_0:
print(" ⛔ 变异 %d 写入后 md5 没变 ⇒ 没真改,本轮作废" % (k + 1))
return 2
r = subprocess.run([PY, str(SELFTEST), "-k", kw],
capture_output=True, text=True, encoding="utf-8",
errors="replace", cwd=str(tgt.parent.parent))
out = (r.stdout or "") + (r.stderr or "")
red = bool(re.search(r"^\s*✗", out, re.M))
n_red += 1 if red else 0
print(" 变异 %d/%d:rc=%d |判据%s"
% (k + 1, cases, r.returncode, "**报红 ✅**" if red else "⛔ 没报红 ❌"))
if not red:
for ln in out.splitlines():
if "合计" in ln or ln.strip().startswith("✗"):
print(" %s" % ln.strip()[:110])
except Exception as e:
print(" ⛔ 变异 %d 自身失败:%r ⛔ 本轮结论作废" % (k + 1, e))
return 2
finally:
# 🔴 一定还原(⛔ 异常路径也还原 ⇒ 不留污染)
tgt.write_bytes(orig)
if _md5(tgt) != md5_0:
print("⛔⛔ 还原后 md5 **不一致**(%s ≠ %s)⇒ 环境已污染,⛔ 后续结论全部作废"
% (_md5(tgt)[:8], md5_0[:8]))
return 2
print(" ✅ 还原核对:md5 一致(%s)" % md5_0[:8])
return 0 if (cases == n_red) else 1
def main() -> int:
ap = argparse.ArgumentParser(description="判据元体检 · 防恒绿/假绿",
allow_abbrev=False)
ap.add_argument("--audit", nargs="?", default=str(SELFTEST))
ap.add_argument("--mutate", metavar="被检文件")
ap.add_argument("rest", nargs="*", metavar="旧文本 新文本")
ap.add_argument("-k", "--kw", default="")
ap.add_argument("--cases", type=int, default=1)
ap.add_argument("--list-rules", action="store_true")
a = ap.parse_args()
if a.list_rules:
for name, kind, _p, why in RULES:
print("%-20s [%s] %s" % (name, kind, why))
return 0
if a.mutate:
# 🔴 2026-10-04 修:旧/新文本**走位置参数**。
# 原先用 `--old`/`--new`,而它们是**带前导空格的代码行** ⇒ shell 正常传、
# 但 `--old` 的值以空格开头时被 argparse 判成"unrecognized arguments"
# ⇒ rc=2 来自**参数解析失败**,⛔ 不是"识别出等价变异" ⇒ 结论是假的
# (今天第二次栽在"验证器自己出错却输出结论")。
# ⇒ 现在:位置参数,且**参数个数不对就直接拒绝**,⛔ 不猜。
if len(a.rest) != 2:
print("⛔ 需要**两个位置参数**:`旧文本` `新文本`(收到 %d 个)" % len(a.rest))
print(" 例:judge_audit.py --mutate <文件> ' return s.lower()' ' return s' -k <关键字>")
return 2
return do_mutate(Path(a.mutate), a.rest[0], a.rest[1], a.kw or " ", a.cases)
if a.rest:
ap.print_help()
return 2
return 0 if audit(Path(a.audit)) == 0 else 1
if __name__ == "__main__":
sys.exit(main())