--- name: 代码替换请求的现状审计 description: 处理用户"代码替换/新增"指令时,先审计磁盘真实状态再用 ask_user_question 确认——避免覆盖已落盘的高版本代码 source: auto-skill extracted_at: '2026-06-03T05:36:58.746Z' --- # 代码替换请求的现状审计 ## 适用场景 用户给出"代码替换"或"按某版本代码新增"指令,但**没有提供与磁盘当前状态对比信息**时。典型触发: - 用户贴了一段代码说"请帮我写/替换这个" - 用户引用某个文档/旧版本/旧 chat 说"按这个来" - 之前的 `state_snapshot` / `memory` / `git log` 描述可能与磁盘现状不一致 ## 核心原则 **永远不要盲信"用户给的代码是最新版本"**——磁盘上的代码可能已经是更完善的版本(用户或其他 agent 已迭代过)。覆盖 = 丢功能。 直接覆盖的代价不一定是显式 bug,也可能是"丢失用户已批准的设计决策"(如 duck-type 探测 / ctx 抽象 / 信号协议 / 二次确认窗 / 错误定位)。 ## 5 步标准操作 ### 1. 确认文件存在 `glob` 或 `list_directory` 看目标文件是否已存在: - 不存在 → 新建 - 存在 → 进入第 2 步审计 ### 2. grep 关键符号 + 读关键段 - 找"用户贴的代码"里的 3-5 个关键符号(函数名 / 类名 / 关键常量 / import) - 在磁盘文件里 grep 同样的符号 - `read_file` 关键段(行号从 grep 结果直接拿) ### 3. 构造差异对照表 列出: ``` | 目标文件 | 用户贴的版本 | 磁盘现有版本 | 直接覆盖会丢失 | ``` **关键列**:"直接覆盖会丢失什么"——让用户判断成本。具体粒度到"功能模块 / 设计决策 / 防御层 / 入口协议",不要写"代码差异"这种空话。 ### 4. ask_user_question 让用户拍板 3 个标准选项(措辞可调,但**必须给出现状 + 三选一**): - **A. 保留现状**(推荐,磁盘已是更新版)—— 直接进 Smoke Test - **B. 强制覆盖到旧版** —— 写明丢什么 + 备份建议(git stash / 复制到 `_old.py`) - **C. 混合:只取某段增量** —— 见第 5 步 **不要在第 1 次 ask 时就列具体的"哪段增量"**——先让用户在 A/B/C 之间选。如果选 C,再做第 5 步。 ### 5. 若用户选 C,识别"真正增量" 对比 1.0 vs 2.0,识别 1.0 真正独有的部分(2.0 没有的): - ❌ 排除 1.0 比 2.0 简单的(2.0 是超集 / 工厂分层 / 多了 CLI) - ❌ 排除 1.0 整体被 2.0 工厂分层超越的(_make_objective vs _build_model + _get_search_space) - ✅ 关注 1.0 独有的功能层(即使 2.0 不"明显"需要) 对每个候选增量,再问一次"采纳哪段",让用户具体选(multiSelect=false,一次只选 1 段最稳)。 ## 落地原则 执行"采纳 1.0 某段增量到 2.0"时: - **最小化外科手术式编辑**:只动需要动的文件,只改需要改的段 - **保留 2.0 的设计决策**(duck-type 探测 / ctx 抽象 / 信号协议 / 二次确认窗 / 错误定位) - **顶部 import 增量用 `replace_all=False` 单点插入**,避免破坏其他 import 顺序 - **同名变量全链路替换**(如 `self.config` → `clean_config`)要贯穿 ctx 构造 / v2 调用 / v1 fallback,避免双源差异 - **单步模式不一定要清洗**(不走 panel 完整 config,与清洗器无关) - **清洗器这种"防患于未然"的代码要给日志**(`self.log_message.emit(f"[清洗器] 已删除 N 个未知 key")`)让运行时可见 ## 验证三件套 落地后必跑: 1. **AST 语法检查**:`ast.parse(open(p, encoding='utf-8-sig').read())` 对 5 个核心文件 - 必加 `utf-8-sig`:WQ_GUI 的 water_quality_gui.py line 1 是 BOM,plain `utf-8` 必挂 2. **关键符号 grep**:确认新代码的关键符号(import / 关键函数调用)都命中,hit 数符合预期 3. **顶层导入测试**:用 mock PyQt5 + `sys.path.insert(0, 'src/gui/core')`,验证模块整体可加载 - PyQt5 mock 模板见下方"参考代码" - Windows 环境调 Python:用 conda env 的 `python.exe` 全路径,不要靠 PATH ## 反例(不要做) - ❌ "按用户贴的代码原封不动写入"——1.0 简化版的覆盖陷阱 - ❌ "保留 state_snapshot 描述"——state snapshot 可能不准确(写的是意图,磁盘才是事实) - ❌ "用 git log 反推当前状态"——git log 不能反映工作区未提交改动 - ❌ "靠 memory 推断当前状态"——memory 可能是 22 天前的(已确认过期) - ❌ "磁盘和用户给的代码看起来一样就不审计"——一行之差可能就是"防弹层"丢失 ## 参考代码 ### PyQt5 mock 模板(worker_thread.py 顶层导入测试) ```python import os, sys os.environ['GDAL_FILENAME_IS_UTF8'] = 'YES' os.environ['SHAPE_ENCODING'] = 'UTF-8' sys.path.insert(0, 'src/gui/core') import types pyqt5 = types.ModuleType("PyQt5") qtc = types.ModuleType("PyQt5.QtCore") class _QThread: def __init__(self, *a, **kw): pass class _Signal: def __init__(self, *a, **kw): pass qtc.QThread = _QThread qtc.pyqtSignal = _Signal qtc.Qt = type("Qt", (), {"QueuedConnection": 1, "UserRole": 0})() sys.modules["PyQt5"] = pyqt5 sys.modules["PyQt5.QtCore"] = qtc import worker_thread # 副作用: check_pipeline_dependencies() 会打印依赖检查日志(可忽略) ``` ### Windows 上跑 conda env python ```bat cmd /c "D:\xxx\anconda\envs\XXX\python.exe D:\path\to\script.py" ``` PowerShell 单行 `python -c "..."` 在中文路径 / 双引号 / 单引号嵌套时易翻车,**写临时 .py 文件再用 `cmd /c` 调**最稳。 ## 案例来源(2026-06-03 WQ_GUI 路线 B MVP) - 用户贴 1.0 简化版:300 行 automl_trainer / 简化 worker_thread.run() / 简化 on_run_all_clicked - 磁盘上 2.0 落盘版:545 行 automl_trainer(_build_model + _get_search_space 工厂 / argparse CLI)/ duck-type 探测 v2 + PipelineContext 抽象 / 完整二次确认窗 / 失败步骤 _focus_step 定位 / [DEPRECATED] stop 保留 - 1.0 唯一真增量 = **"防弹级参数清洗器"**(method_map 14 项 + inspect.signature 过滤未知 key + has_kwargs 豁免 + 未知 key 数量日志) - 落地:worker_thread.py:run() 内 set_callback 之后插入 53 行清洗器,self.config 6 处替换为 clean_config - 验证:5 文件 AST 全通过 + 关键符号 7 项命中 + PyQt5 mock 下 import 成功 - 净增行数:407 → 457(+50 行)