# patch_file 使用指南

> 创建: 2026-06-15
> 关联设计: docs/02-架构设计/专项设计/patch_file工具设计-2026-06-15.md

## 使用场景

局部更新文件（替代 read-merge-write 多步流程），精确定位 `old_text` 并替换为 `new_text`。

适用于只改文件中某几行的场景：大文件改 3 行无需重发全部内容，避免 `write_file` 全量覆盖的事故风险（参见 FR-S7 记录：762 行文件被覆盖为 248 字节）。

## API 参数

| 参数 | 必填 | 说明 |
|------|------|------|
| `path` | 是 | 目标文件路径（workspace 内，走 `validate_path` 校验 + 黑名单） |
| `old_text` | 是 | 要替换的精确文本（必须与文件内容完全匹配，含空格/缩进；充当乐观锁） |
| `new_text` | 是 | 替换后的新文本 |
| `line` | 否 | 行号辅助定位（快速跳转 + 去歧增强；有值时优先跳到该行检查 `old_text`，不匹配则降级全文扫描） |
| `expected_hash` | 否 | 文件指纹防脏写，格式 `"{unix_timestamp}_{size}"`（如 `"1718400000_12345"`），由 `read_file` 返回的 `file_hash` 字段携带 |

补充去歧参数（可选）：`before_text` / `after_text` —— 当 `old_text` 多处匹配时，作为上下文锚精确锁定目标位置。

## 返回值

```json
{
  "success": true,
  "patches_applied": 1,
  "total_matches": 1,
  "lines_changed": 2,
  "warnings": [],
  "file_hash": "1718400000_12400"
}
```

| 字段 | 含义 |
|------|------|
| `success` | 是否成功 |
| `patches_applied` | 成功替换的 patch 数 |
| `total_matches` | 每个 patch 的总命中数（含未替换的） |
| `lines_changed` | 总行数变化（正=增加，负=减少） |
| `warnings` | 警告信息，如 `["匹配到 3 处，仅替换第 1 处"]` |
| `file_hash` | 操作后的新指纹（下次操作传入 `expected_hash` 用） |

## 安全边界

- **RiskLevel Medium**：修改已有文件，需人工审批
- **自动 `.bak` 备份**：复用 FR-S7 已有逻辑，误操作可恢复
- **old_text 不匹配 → 报错**（不修改文件，避免盲替换）
- **expected_hash 不匹配 → 报错**（防并发脏写，提示「文件已被外部修改，请重新读取」）
- **三层防御**：L1 文件级 Mutex（防时机冲突）+ L2 old_text 精确匹配（防内容错配）+ L3 expected_hash 指纹校验（防版本漂移），底层兜底 `.bak` 备份

## 最佳实践

- **old_text 取足够上下文确保唯一**：避免短串多处匹配，收到「匹配到 N 处」warning 时用 `before_text`/`after_text` 锚定或加长 `old_text` 重试
- **大段修改用多个小 patch 而非一个巨大 patch**：每个 patch 独立校验，失败可定位
- **危险操作前先 `file_info` 确认**：核对路径、大小、是否二进制（含 `\0` 的文件会被拒绝）
- **read → patch 链路带上 hash**：`read_file` 返回 `file_hash`，传入 `patch_file` 的 `expected_hash` 形成乐观锁闭环
- **多 patch 从文件末尾往前排**：避免行号偏移（工具内部已按此执行）
- **old_text 必须完全匹配**：含空格、缩进、换行，复制粘贴原文最稳妥

## 示例

### 基础替换

```json
{
  "path": "src/main.rs",
  "old_text": "fn old()",
  "new_text": "fn new()"
}
```

### 带 hash 乐观锁

```json
{
  "path": "src/main.rs",
  "expected_hash": "1718400000_12345",
  "patches": [
    { "old_text": "fn old()", "new_text": "fn new()" }
  ]
}
```

### 多处匹配用 line 锚定

```json
{
  "path": "src/main.rs",
  "old_text": "return Ok(())",
  "new_text": "return Ok(value)",
  "line": 42
}
```

## 边界情况

| 情况 | 行为 |
|------|------|
| 文件不存在 | 报错「文件不存在」 |
| 二进制文件（含 `\0`） | 报错「不支持二进制文件」 |
| `old_text` 为空串 | 报错「old_text 不能为空」 |
| `new_text` == `old_text` | 成功 + warning「无实际更改」 |
| 路径含 `..` | `validate_path` 黑名单拦截 |
| 目标是 `.bak`/`.tmp` | 拒绝（CR-03 噪声文件过滤） |
