Repository navigation
v0.1.10
0.1.10
这一版用户能感知到的变化
- 写回验证默认只跑一遍脚本,等待时间约减半(行为变更):
apply --write的 happy path 由"跑 2 遍"(1 遍原样重跑 + 1 遍套壳取状态)改为 1 遍。这一遍同时回答两件事:脚本还跑得通吗、你要调的字段真落到目标图上了吗。取不到目标图状态时(脚本没画这张图 / 从没savefig/ 子进程没留下状态)才回退 2 遍,并把回退原因打印出来;需要归因"是不是脚本本来就跑不通"时最多 3 遍。--timeout语义不变(每一次重跑各自受约束)。 - 验证过程不再改坏用户目录里的图片(修缺陷):此前验证的那一遍为了在脚本跑完后还能读到图状态,会把
plt.close拦成空操作;脚本里写成savefig(...)→plt.close(fig)→ 再裸plot(...)(不新建 figure)时,后续绘制会画到上一张图上,而验证那一遍最后写盘 ⇒ 你目录里的 PNG 被静默覆盖成错的(此时工具可能仍报"验证通过")。现在取数改成Figure.savefig快照 + 按创建顺序持住图对象,两者都不改脚本行为,plt.close不再被拦。 - 关窗后直接给一条可复制的写回命令:拖过东西再关窗,终端会打印
mpltweak apply <脚本> --write(带过--params/ 透传参数就一并写全)。只是把命令打出来,写回仍要你自己确认——工具绝不自动改你的源码;没拖过时不打任何一行。 - 成功时人读输出收敛成一行:
[OK] 已原位写回 3 处(fig1.py);图已更新(重跑 + 语义验证通过);逐处清单、备份路径、各层结论移进-v / --verbose。失败、回滚、降级(--verify-fast/--no-verify)与"未判定"(verified: null)不加-v也照旧全部打出来,--json的字段集一个不少。 - Tk 回退提示只说一次:开窗落到 Tk(没装 Qt)时那句"交互体验不如 Qt"不再每次开窗都念——每个用户只提醒一次;
doctor与文档里始终可查。 - 失败时给四类归因 + 一句可照抄的下一步,写回前还能先预检:验证重跑失败时把「脚本跑不通(缺数据 / 依赖)」/「脚本需要参数」/「无头环境跑不通(要开窗 / 交互)」/「工具自身异常」分开报,每类各给一句下一步(记不住的参数会直接告诉你加
--透传的写法;工具自身异常会写明"没改动你的源码、已回滚")。--json多出机器可读的attribution/next_step,agent 可据此自动分支。此外写回与开窗之前会先做一次不执行脚本的静态预检(缺依赖 / 需要参数 / 会联网 / 等人输入 / 要弹窗),把"撞上之后才归因"改成"事前就说清楚"。 - 脚本自己带参数时,验证重跑也拿得到参数(bug #6):
mpltweak apply fig1.py --write -- --data x.npz——--之后的参数沿apply → writeback → verify_run / verify_semantic → 子进程全链原样到达脚本的sys.argv;"脚本本来就跑不通"的基线判别同样带上这些参数。不带--时行为与以前完全一致。 - 验证期间你在编辑器里保存的改动不会被覆盖:落盘前再确认一次"当前文件 == 最初读到的那份",检测到外部改动就拒绝写盘、明确告诉你、不覆盖你的编辑(原位与块两条路径都加)。
修复
每条格式:现象 → 根因 → 修法 → 证据。
写回验证会静默改坏用户图片(本轮最该记住的一条)
- 现象:脚本里"
savefig→plt.close(fig)→ 再裸plot()"这种写法下,apply --write会把你目录里的图片覆盖成错的 —— 追因样本的目标图由 185,701 B /6a7acb15d52f变成 140,667 B /252597fc6170(内容不同像素 15.2%),而工具这一路仍报成功,旧口径下verified=True。 - 根因:验证的语义层用「把
plt.close拦成空操作」来保证"脚本跑完还能读到图"。图因此不销毁、仍是 current,后续绘制画回上一张图;而真实链路里原样跑那一遍先写出正确的图、套壳这一遍后写盘 ⇒ 覆盖。 - 修法:不再拦
close;取数改成两件只读的东西 ——Figure.savefig钩子在写盘那一刻抓快照 + 按创建顺序持强引用的 figure 注册表(脚本自己close过也读得到)。判据只严不松:主口径仍是"脚本跑完时图对象的状态"(与旧verify_semantic逐项一致),快照是追加判据——磁盘上那张图也必须体现这次改动。注册表只认 pyplot 托管的图(用Figure() + FigureCanvasAgg的 OO 风格脚本结论仍是"取不到状态")。 - 证据:探针 5 臂双向定位(
B′==A、B″==B:去掉拦 close 差异全消失、只加拦 close 差异完整复现)→ 产出文件一致率 143/144 → 144/144(diff_artifacts1 → 0);同一脚本"旧代码 apply / 新代码 apply / 写回后脚本原样跑"三方对账:旧代码写出的图 ≠ 原样跑产物、新代码 ==;独立目录 A/B 对账 41/41 张 PNG 逐字节相同(含pgl_sasa.png、S14_pca_plot.png)。素材见tools/probe_close_vs_wrapper.py、_review/cmp_plain_vs_instrumented/。
写回验证单遍化(2 遍 → 1 遍)
- 现象:
apply --write每次要跑两遍脚本 —— 第一遍原样跑只为看退出码,第二遍套壳跑为拿图状态,而第二遍必须改脚本行为(即上一条的拦 close)。 - 根因:取数方式与"跑不跑得通"两个问题被放在两个进程里问。
- 修法:一次子进程同时回报"脚本自身成败"与"图状态"(
verify.py信封里多带script_ok/script_exit/error),状态比对仍走同一个_compare_state,判据不可能分叉;取不到状态一律 fail-closed 回退(绝不静默判通过)。 - 证据:探针实测 2 遍 38.1 s → 1 遍 19.6 s(−48.7 %,24/24 可比样本单遍命中);
--json新增verify_passes(1 = 单遍 / 2 = 已回退 / 0 = 没为验证重跑)与semantic_basis,口径见 docs/verify.md 的「写回会跑几遍脚本」。
失败归因四类 + 一句下一步(新增)
- 现象:带参数或需要 GUI 的脚本在验证重跑里跑不通时,用户只看到一句类似"缺数据 / 缺依赖"的失败,很容易归因成"这工具不行"。
- 根因:验证子进程原样继承命令行,脚本自己的参数没有透传(bug #6 同源);失败也只有一个笼统原因。
- 修法:把「脚本跑不通」与「工具改坏了」彻底分开,四类各给一句可照抄的下一步;
--json里给机器可读的attribution/next_step(agent 可自动分支);另加"写回 / 开窗前"的静态预检(不执行脚本:只做ast+find_spec,自身异常即放行)。 - 证据:
messages.py的四类文案与下一步(script_cannot_run/script_needs_args/script_needs_gui_or_headless_impossible/tool_bug)、apply.py的attribution输出;新增src/mpltweak/preflight.py,在apply.py与launch.py两条入口接入。
验证期间的"外部编辑保护"落到实处
- 现象:验证要真跑一遍脚本,可能等上几分钟;这段时间你在编辑器里保存的改动,过去可能被随后的落盘覆盖。
- 根因:写回读入源码后到落盘之间没有二次确认。
- 修法:落盘前比对"当前文件 == 最初读到的
src",不一致即拒绝写盘、明确报出、不覆盖;inplace/ 块两条路径都加。 - 证据:
_guard_external_edit与对应回归(tests/test_writeback.py,另有docs/verify.md/docs/exec-boundaries.md的对外口径)。
新增 -- 参数透传(bug #6)
- 现象:依赖
sys.argv的脚本(python fig1.py --data x.npz)在验证重跑里拿不到自己的参数 → 行为与基线不一致 → 默认回滚 + 误归因"缺数据 / 依赖"。 - 根因:验证子进程的命令行是
[python, -m, mpltweak.verify, 脚本, out, idx],不带用户脚本的参数;而且套壳自己的位置参数还会残留在脚本的sys.argv[1:]里(表现为unrecognized arguments)。 - 修法:
--之后的参数沿apply → writeback → verify_run / verify_semantic → 子进程全链原样透传;sys.argv无条件摆成[脚本] + 脚本参数(不再只在"有参数"时才摆)。 - 证据:
tests/test_verify_onepass.py;同一份需要位置参数的脚本,"原样跑"与"带--的验证重跑"同 rc、同 stdout(不带--时不再出现工具自身参数的unrecognized arguments)。
测试与验收
- 两种编码环境(UTF-8 / GBK)下套件均 ALL PASS;
pytest -q= 61 passed / 5 skipped(含 hygiene 棘轮:宽兜底基线未涨、子进程后端 pin 与真实 GUI 对象两道新棘轮)。 - 防污染回归网
tests/test_verify_onepass.py(新增,24 条 check:产物对账 / 单遍 + 快照追加 / 跑不通 / 改动没生效 / fail-closed 回退可见);反向验证(tools/teeth_onepass.py):把 close 拦截还原 ⇒ "用户目录里的每张图都与原样跑逐字节一致"必红,把单遍验证改成永远通过 / 把状态比对改成永不报错 ⇒ 对应用例分别变红,共 3/3 按预期变红,未变异代码上全绿。 tests/test_docs.py补锚点 / 相对链接 / 图片 / 外链格式的冻结基线与 slug 自证(基线只允许降),并修掉一处"冻结基线引用未入库手册导致干净检出必红"的缺陷。
数字
- 写回验证 2 遍 → 1 遍:探针实测 38.1 s → 19.6 s(−48.7 %),24/24 可比样本单遍命中;回退口径见上(只有取不到目标图状态才回退 2 遍,失败归因最多 3 遍)。
- 验证产物一致率 143/144 → 144/144:探针在 144 份可比脚本上判"原样跑 vs 套壳跑"的产出文件是否一致,
diff_artifacts由 1 降到 0(剩下的不一致份里,退出码、stdout/stderr 差异已逐条归因)。 - 静态层:语料 4,433 份过筛,判定口径为主表
3,531 / 3,610 = 97.8 %(五类完全划分 3,531 / 10 / 37 / 32 = 3,610);口径与完整材料见 docs/reliability.md。 - 运行时真跑:池 1,837 份(含 28 份"真会开窗" + 29 份"联网 / 子进程"的静态排除)⇒ 真跑候选 1,780 份,其中 928 份写回并验证通过、四条铁律逐律 928 / 928、"说没写就一个字节都不能变"违约 0。
- 0.1.10 收尾时另做了一次语料试点(167 份):判定差异 0、违约 0、语料未被修改 —— 它验证的是"判源码字节"这条口径与每份脚本各自的隔离工作目录,不涉及图片产物,因此上面的已发布数字不受验证重跑影响。