Агент-инженер по репозиторию · Модуль 4 · Урок 4.2

Инструменты записи: edit_file/apply_patch и минимальный дифф

Зачем (какую проблему чиним)

Изоляция готова — теперь даём агенту писать внутри неё. Но «перезапиши файл целиком» — плохой инструмент записи: модель перегенерирует файл, теряя куски и раздувая дифф. Нам нужны точечные, структурированные правки с минимальным диффом — это и безопаснее (легче ревьюить), и дешевле.

Решение и альтернативы

Решение: инструмент edit_file принимает точную замену: старый фрагмент (anchor) → новый. Замена применяется, только если anchor найден ровно один раз (иначе ошибка — модель уточняет контекст). Для многофайловых правок — apply_patch в формате унифицированного диффа, применяемый строго в worktree. Обе операции вызывают assertWritable перед записью.

Альтернативы: write_file(path, content) целиком — модель теряет части файла, дифф огромен, ревью невозможно; построчные правки по номерам строк — хрупки (номера плывут после первой же правки). Замена по уникальному anchor — устойчива и даёт минимальный сфокусированный дифф.

DIFF

Добавляем edit_file (уникальная замена) и apply_patch; обе — только в worktree, через assertWritable, с возвратом получившегося диффа для самопроверки.

⚠ Безопасность

Каждая запись: (1) проходит assertWritable (не main, в worktree); (2) ограничена корнем worktree (safeJoin); (3) минимальна — anchor должен совпасть однозначно, иначе отказ. Минимальный дифф — это не только аккуратность: чем меньше и точнее изменение, тем реальнее человеческое ревью на версии v4.

Проверка

«Добавь проверку nil в функцию X» → edit_file меняет ровно нужные строки; git -C <wt> diff показывает минимальный сфокусированный дифф. Неуникальный anchor → отказ с просьбой уточнить контекст. Запись вне worktree/в main невозможна.

Глубже

Структурированные правки и инструменты записи кодинг-агентов — курс «Продакшн-разработка», Модуль 4 (специализированные агенты, гл. 8).

edit.go: точечная замена по уникальному anchor (новый файл, фрагмент)
+// editFile заменяет ровно одно вхождение oldText на newText в файле внутри
+// worktree. Уникальность anchor гарантирует минимальный предсказуемый дифф.
+func (w *Workspace) editFile(ctx context.Context, rel, oldText, newText string) (string, error) {
+	if err := w.assertWritable(ctx); err != nil { // ИНВАРИАНТ перед записью
+		return "", err
+	}
+	abs, err := safeJoin(w.Dir, rel) // запись строго внутри worktree
+	if err != nil {
+		return "", err
+	}
+	src, err := os.ReadFile(abs)
+	if err != nil {
+		return "", err
+	}
+	if n := strings.Count(string(src), oldText); n != 1 {
+		return "", fmt.Errorf("anchor встречается %d раз (нужно ровно 1): уточните контекст", n)
+	}
+	out := strings.Replace(string(src), oldText, newText, 1)
+	if err := os.WriteFile(abs, []byte(out), 0o644); err != nil {
+		return "", err
+	}
+	return runGitWrite(ctx, w.Dir, "diff", "--", rel) // вернуть дифф для самопроверки
+}

Anti-patterns

Грабли инструментов записи
ГрабляПочему плохоКак правильно
write_file(path, content) — перезапись файла целикомМодель теряет куски при регенерации; гигантский дифф невозможно ревьюитьedit_file точечной заменой по уникальному anchor — минимальный дифф
Правки по номерам строкНомера плывут после первой правки; последующие замены бьют не тудаЗамена по содержимому (anchor), а не по позиции
Применять anchor, встречающийся несколько разНеоднозначность: правка уходит не в то местоТребовать ровно одно совпадение; иначе отказ с просьбой уточнить контекст
Запись без assertWritable/safeJoinМожно случайно записать в main или вне worktreeКаждая запись: инвариант «не main, в worktree» + ограничение корнем worktree

Практическое задание (RA-v3)

  • Реализовать edit_file (уникальная замена по anchor) и apply_patch (унифицированный дифф) строго в worktree.
  • Вызывать assertWritable и safeJoin перед каждой записью; возвращать получившийся дифф.
  • Закоммитить: git commit -m "v3: edit_file/apply_patch with minimal diffs".

Проверка знаний

Почему edit_file с заменой по уникальному anchor предпочтительнее write_file, перезаписывающего весь файл?

  • A write_file не поддерживается в Go
  • B Точечная замена даёт минимальный сфокусированный дифф (легко ревьюить, дешевле) и не теряет части файла при регенерации моделью
  • C Анкоры быстрее пишутся на диск
  • D Разницы нет