238 lines
8.3 KiB
Plaintext
238 lines
8.3 KiB
Plaintext
使用统一 diff(`@@` 块、`-`/`+`/空格行)对单个文件做精确编辑:追加、插入、替换、删除都可以在一次调用里完成。
|
||
硬性规则:
|
||
|
||
1) 补丁必须被 `*** Begin Patch` 与 `*** End Patch` 包裹。
|
||
2) 每个修改块必须以 `@@ [id:数字]` 开头。
|
||
3) 块内每一行只能是三类之一:
|
||
- 上下文行:以空格开头(` ␠`),表示“文件里必须原样存在”的锚点;
|
||
- 删除行:以 `-` 开头,表示要从文件中移除的原文;
|
||
- 新增行:以 `+` 开头,表示要写入的新内容。
|
||
4) 任何“想新增/想删除/想替换”的内容都必须逐行写 `+` 或 `-`;如果你把多行新内容直接贴上去却不加 `+`,它会被当成上下文锚点去匹配原文件,极易导致“未找到匹配的原文”。
|
||
5) 重要语义:一个块里如果完全没有上下文行(空格开头)也没有删除行(`-`),那么它会被视为“仅追加(append-only)”,也就是把所有 `+` 行追加到文件末尾——这对“给空文件写正文”很合适,但对“插入到中间”是错误的。
|
||
|
||
正面案例(至少 5 个,且都包含多行原文/多处修改)
|
||
|
||
1) 给空文件写完整正文(追加到末尾;空文件=正确)
|
||
目标:新建 README.md 后一次性写入标题、安装、用法、FAQ(多段落、多行)。
|
||
要点:没有上下文/删除行 → 追加模式;空文件时最常用。
|
||
|
||
*** Begin Patch
|
||
@@ [id:1]
|
||
+# 项目名称
|
||
+
|
||
+一个简短说明:这个项目用于……
|
||
+
|
||
+## 安装
|
||
+
|
||
+```bash
|
||
+pip install -r requirements.txt
|
||
+```
|
||
+
|
||
+## 快速开始
|
||
+
|
||
+```bash
|
||
+python main.py
|
||
+```
|
||
+
|
||
+## 常见问题
|
||
+
|
||
+- Q: 为什么会报 xxx?
|
||
+ A: 先检查 yyy,再确认 zzz。
|
||
+
|
||
*** End Patch
|
||
|
||
2) “不删除,直接插入内容”到函数内部(必须用上下文锚定插入位置)
|
||
目标:在 def build_prompt(...): 里插入日志与参数归一化,但不改动其它行。
|
||
要点:插入发生在“两个上下文行之间”,上下文必须精确(包含缩进)。
|
||
|
||
*** Begin Patch
|
||
@@ [id:1]
|
||
def build_prompt(user_text: str, system_text: str, tags: list):
|
||
prompt_parts = []
|
||
+ # 参数归一化:去掉首尾空白,避免模型误判
|
||
+ user_text = (user_text or "").strip()
|
||
+ system_text = (system_text or "").strip()
|
||
+
|
||
+ logger.debug("build_prompt: tags=%s, user_len=%d", tags, len(user_text))
|
||
prompt_parts.append(system_text)
|
||
prompt_parts.append(user_text)
|
||
if tags:
|
||
prompt_parts.append("TAGS: " + ",".join(tags))
|
||
*** End Patch
|
||
|
||
3) 复杂替换:整段函数重构(多行 old/new + 保留稳定上下文)
|
||
目标:把旧的 apply_patch()(弱校验)替换成新实现(多分支、异常信息更清晰)。
|
||
要点:替换不是“改一行”,而是“删一段、加一段”,并用函数签名/相邻代码作锚点。
|
||
|
||
*** Begin Patch
|
||
@@ [id:1]
|
||
class FilePatcher:
|
||
def __init__(self, root: Path):
|
||
self.root = root
|
||
|
||
def apply_patch(self, path: str, patch_text: str) -> dict:
|
||
- # old: naive replace
|
||
- content = (self.root / path).read_text(encoding="utf-8")
|
||
- content = content.replace("foo", "bar")
|
||
- (self.root / path).write_text(content, encoding="utf-8")
|
||
- return {"success": True}
|
||
+ full_path = (self.root / path).resolve()
|
||
+ if self.root not in full_path.parents and full_path != self.root:
|
||
+ return {"success": False, "error": "非法路径:越界访问"}
|
||
+
|
||
+ if "*** Begin Patch" not in patch_text or "*** End Patch" not in patch_text:
|
||
+ return {"success": False, "error": "补丁格式错误:缺少 Begin/End 标记"}
|
||
+
|
||
+ try:
|
||
+ original = full_path.read_text(encoding="utf-8")
|
||
+ except Exception as e:
|
||
+ return {"success": False, "error": f"读取失败: {e}"}
|
||
+
|
||
+ # 这里省略:解析 blocks、逐块应用、失败回滚等
|
||
+ updated = original
|
||
+ try:
|
||
+ full_path.write_text(updated, encoding="utf-8")
|
||
+ except Exception as e:
|
||
+ return {"success": False, "error": f"写入失败: {e}"}
|
||
+
|
||
+ return {"success": True, "message": "已应用补丁"}
|
||
*** End Patch
|
||
|
||
4) 复杂多块:同一文件里同时“加 import + 替换逻辑 + 插入新 helper + 删除旧函数”
|
||
目标:一次调用完成 4 种操作,且每块都有足够上下文,避免误匹配。
|
||
要点:不同区域用不同 @@ [id:n] 分块,互不干扰。
|
||
|
||
*** Begin Patch
|
||
@@ [id:1]
|
||
-import json
|
||
+import json
|
||
+import re
|
||
from pathlib import Path
|
||
|
||
@@ [id:2]
|
||
def normalize_user_input(text: str) -> str:
|
||
- return text
|
||
+ text = (text or "").strip()
|
||
+ # 压缩多余空白,减少提示词抖动
|
||
+ text = re.sub(r"\\s+", " ", text)
|
||
+ return text
|
||
|
||
@@ [id:3]
|
||
def load_config(path: str) -> dict:
|
||
cfg_path = Path(path)
|
||
if not cfg_path.exists():
|
||
return {}
|
||
data = cfg_path.read_text(encoding="utf-8")
|
||
return json.loads(data)
|
||
+
|
||
+def safe_get(cfg: dict, key: str, default=None):
|
||
+ if not isinstance(cfg, dict):
|
||
+ return default
|
||
+ return cfg.get(key, default)
|
||
|
||
@@ [id:4]
|
||
-def legacy_parse_flags(argv):
|
||
- # deprecated, kept for compatibility
|
||
- flags = {}
|
||
- for item in argv:
|
||
- if item.startswith("--"):
|
||
- k, _, v = item[2:].partition("=")
|
||
- flags[k] = v or True
|
||
- return flags
|
||
-
|
||
def main():
|
||
cfg = load_config("config.json")
|
||
# ...
|
||
*** End Patch
|
||
|
||
5) 删除示例:删除一整段“废弃配置块”,并顺手修正周围空行(多行删除 + 上下文)
|
||
目标:删掉 DEPRECATED_* 配置和旧注释,确保删除位置精确。
|
||
要点:删除行必须逐行 `-`;保留上下文行确保定位。
|
||
|
||
*** Begin Patch
|
||
@@ [id:1]
|
||
# ==============================
|
||
# Runtime Config
|
||
# ==============================
|
||
-DEPRECATED_TIMEOUT = 5
|
||
-DEPRECATED_RETRIES = 1
|
||
-# 注意:这些字段将在下个版本移除
|
||
-# 请迁移到 NEW_TIMEOUT / NEW_RETRIES
|
||
NEW_TIMEOUT = 30
|
||
NEW_RETRIES = 3
|
||
*** End Patch
|
||
|
||
如何写“带上下文”的正确姿势(要点)
|
||
|
||
- 上下文要选“稳定锚点”:函数签名、类名、关键注释、紧邻的两三行缩进代码。
|
||
- 不要用“容易变的行”当唯一锚点:时间戳、日志序号、随机 id、生成内容片段。
|
||
- 上下文必须字节级一致(空格/Tab/大小写/标点都算),否则会匹配失败。
|
||
|
||
反面案例(至少 3 个,且都是“真实会踩坑”的类型)
|
||
|
||
反例 A(来自一次常见错误):空文件时只有第一行加了 `+`,后面直接贴正文
|
||
这会让后面的正文变成“上下文锚点”,工具会去空文件里找这些原文,必然失败(常见报错:未找到匹配的原文)。
|
||
|
||
*** Begin Patch
|
||
@@ [id:1]
|
||
+
|
||
仰望U9X·电驭苍穹
|
||
银箭破空电光闪
|
||
三千马力云中藏
|
||
*** End Patch
|
||
|
||
正确做法:正文每一行都要写 `+`(包括空行也写 `+`)。
|
||
|
||
(对应的正确 patch 示例:向空文件追加多行)
|
||
|
||
*** Begin Patch
|
||
@@ [id:1]
|
||
+
|
||
+仰望U9X·电驭苍穹
|
||
+银箭破空电光闪
|
||
+三千马力云中藏
|
||
*** End Patch
|
||
|
||
反例 B:想“插入到中间”,却只写 `+`(没有任何上下文/删除行)
|
||
这种块会被当成“追加到文件末尾”,结果内容跑到文件最后,不会插入到你以为的位置。
|
||
|
||
*** Begin Patch
|
||
@@ [id:1]
|
||
+# 我以为会插到某个函数上面
|
||
+print("hello")
|
||
*** End Patch
|
||
|
||
正确做法:用上下文锚定插入点(见正面案例 2)。
|
||
|
||
(对应的正确 patch 示例:用上下文把内容插入到函数内部,而不是追加到文件末尾)
|
||
|
||
*** Begin Patch
|
||
@@ [id:1]
|
||
def main():
|
||
config = load_config("config.json")
|
||
+ # 这里插入:启动提示(不会移动到文件末尾)
|
||
+ print("hello")
|
||
run(config)
|
||
*** End Patch
|
||
|
||
反例 C:补丁在第一个 `@@` 之前出现内容 / 或漏掉 Begin/End 标记
|
||
解析会直接报格式错误(例如:“在检测到第一个 @@ 块之前出现内容”、“缺少 Begin/End 标记”)。
|
||
|
||
(错误形态示意)
|
||
这里先写了一段说明文字(没有 @@)
|
||
@@ [id:1]
|
||
+...
|
||
|
||
正确做法:确保第一段非空内容必须从 `@@ [id:n]` 开始,并且整体有 Begin/End。
|
||
|
||
(对应的正确 patch 示例:完整结构、第一段内容从 @@ 块开始)
|
||
|
||
*** Begin Patch
|
||
@@ [id:1]
|
||
# ==============================
|
||
# Runtime Config
|
||
# ==============================
|
||
+# 说明:此处新增一行注释作为示例
|
||
NEW_TIMEOUT = 30
|
||
*** End Patch
|