第 6 章:文件编辑与补丁应用

为什么编辑比写新代码更难

生成新代码比安全修改现有代码容易。一次 Agent 编辑必须保留周围上下文,避免覆盖用户改动,处理编码,尊重权限,并留下人类可以 review 的 diff。

有两种主流编辑风格:

  • Patch-based editing:描述一个 diff 并应用它。
  • String-replacement editing:用新字符串替换精确的旧字符串。

Codex 主要使用 patch application。Claw 暴露 file write 和 string replacement 风格的编辑。

安全 Agent 中的编辑

任何编辑前,运行时都应该回答:

def prepare_edit(path, workspace, permission_policy):
    real_path = canonicalize(path)
    if not real_path.is_relative_to(workspace.root):
        raise PermissionError("edit escapes workspace")

    if is_binary(real_path):
        raise ValueError("refuse text edit on binary file")

    permission_policy.require_write_access(real_path)
    original = read_text(real_path)
    return real_path, original

编辑后,运行时应该保留足够信息供 review:

def finish_edit(path, before, after):
    if before == after:
        return {"changed": False, "message": "No changes applied"}

    return {
        "changed": True,
        "path": str(path),
        "diff_preview": unified_diff(before, after),
    }

Codex:Patch-Based Editing

Codex 有自定义 patch tool,而不是依赖外部 patchgit apply。模型发出 patch-like operation,运行时解析它,计算受影响文件,检查权限,并通过 tool orchestration path 应用变更。

Patch 编辑形状

def apply_patch_document(patch_text, workspace):
    operations = parse_patch(patch_text)

    for op in operations:
        path = resolve_workspace_path(op.path, workspace)
        before = read_text(path) if path.exists() else ""

        if op.kind == "add":
            after = op.new_text
        elif op.kind == "delete":
            after = None
        elif op.kind == "update":
            after = apply_hunks(before, op.hunks)
        elif op.kind == "move":
            after = apply_hunks(before, op.hunks)
            schedule_move(op.old_path, op.new_path)

        validate_result(op, before, after)

    commit_all_file_changes(operations)

重要的运行时行为不只是应用文本。它还要验证 patch 引用的是预期旧内容,并且每个 touched path 在当前策略下被允许。

为什么 Patches 很适合 Agents

Patch-based editing 有有用属性:

  • 多文件变更可以放进一个工具调用。
  • Review 输出天然是 diff-shaped。
  • 模型可以表达插入、删除、移动和替换。
  • 运行时可以在应用前从 patch 计算写权限。
  • 失败的 context matches 可以防止在错误位置意外编辑。

缺点是 patch syntax 是模型必须正确输出的另一门语言。Codex 通过严格 parser 和清晰工具 grammar 缓解这一点。

Claw:字符串替换与文件写入

Claw 的文件操作包括 read、write、edit、glob 和 grep 风格工具。edit 操作概念上是字符串替换:在文件中找到旧字符串,用新字符串替换。write 操作可以在权限检查后创建或覆盖文件。

字符串替换形状

def edit_file(path, old_string, new_string, replace_all=False):
    real_path, before = prepare_edit(path, workspace, permission_policy)

    count = before.count(old_string)
    if count == 0:
        raise ValueError("old_string was not found")

    if count > 1 and not replace_all:
        raise ValueError("old_string is not unique")

    if replace_all:
        after = before.replace(old_string, new_string)
    else:
        after = before.replace(old_string, new_string, 1)

    write_text(real_path, after)
    return finish_edit(real_path, before, after)

这种编辑风格简单且可预测。它推动模型先读取文件,选择一个精确 span,并只替换那个 span。

为什么字符串替换有效

字符串替换有有用属性:

  • 一次只改一个文件,因此 blast radius 小。
  • 旧文本作为 stale context 的 guard。
  • 失败容易理解:not found 或 not unique。
  • 容易执行文件大小限制和二进制检测。

缺点是较大 refactor 需要多个工具调用。如果模型没有包含足够精确上下文,替换可能失败。

整文件写入

整文件写入对新文件和生成资产是必要的,但对已有文件风险更高。安全运行时应该区别对待 overwrite 和 create。

def write_file(path, content, overwrite=False):
    real_path = resolve_workspace_path(path)
    permission_policy.require_write_access(real_path)

    if real_path.exists() and not overwrite:
        raise ValueError("file exists; use edit instead of write")

    if len(content.encode("utf-8")) > MAX_WRITE_BYTES:
        raise ValueError("content too large")

    atomic_write_text(real_path, content)
    return {"path": str(real_path), "bytes": len(content)}

Claw 的文件层包含显式 size checks 和 binary safeguards。Codex 的 patch flow 同样会在应用变更前计算写入目标。

编辑与用户改动

Agent 永远不应该假设它读过的文件仍未改变。在 read 和 write 之间,用户或另一个工具可能修改文件。稳健的编辑流程可以与预期内容比较:

def guarded_edit(path, expected_before, transform):
    current = read_text(path)
    if current != expected_before:
        raise RuntimeError("file changed since it was read")

    after = transform(current)
    write_text(path, after)

真实系统在严格程度上不同,但原则很重要:编辑应该锚定在已观察到的文件内容上,而不是只锚定在模型记忆上。

对比

方面 Codex Patch Tool Claw File Edit
主要抽象 带 hunks 的 patch document 精确字符串替换
多文件编辑 自然 需要多次调用
防 stale context Hunk context old_string match
Review 形状 Diff-first Result 加可选 diff preview
Parser 复杂度 更高 更低
模型负担 必须输出有效 patch grammar 必须选择唯一旧字符串
权限规划 从 patch 计算 touched paths 每次调用检查目标 path

Agent 的实用规则

最佳编辑策略通常是:

def choose_edit_strategy(change):
    if change.creates_new_file:
        return "write_file"
    if change.touches_many_files or needs_reordering(change):
        return "apply_patch"
    if change.is_small_local_replacement:
        return "string_replace"
    return "read_more_context_first"

Codex 优化 patch fluency。Claw 优化 guarded local edits 和 structured file operations。

源码锚点

对 Codex,有用的文件名是 apply_patch.rs 和 patch parser files。对 Claw,有用的文件名是 file_ops.rspermission_enforcer.rstools/lib.rs