Агент-инженер по репозиторию · Модуль 5 · Урок 5.3
Структурное описание MR и идемпотентность
Зачем (какую проблему чиним)
Чтобы человек быстро и ответственно одобрил MR, ему нужно описание, на которое можно опереться: что сделано, зачем, как протестировано, какие риски. И вторая проблема: агента могут перезапустить (ретрай, повторный вебхук) — нельзя плодить дубли MR и оставлять мусорные ветки после неудач.
Решение и альтернативы
Решение: описание MR генерируется по шаблону из состояния задачи: Что (план/дифф), Зачем (issue/цель), Как протестировано (вердикт verify: какие проверки и их итог), Риски (что мог задеть, чего не покрыли тесты). Идемпотентность: перед созданием — FindPR(branch); если MR на эту ветку уже есть, обновляем, а не создаём второй. Неудачные попытки убираем: снять worktree, удалить брошенную ветку без MR.
Альтернативы: описание свободным текстом от модели — расплывчато, ревьюер не получает структуры рисков; ключ идемпотентности по времени/случайности — плодит дубли. Ключ по ветке задачи стабилен и естественен.
DIFF
Шаблон описания MR из состояния задачи и идемпотентное создание (find-or-update по ветке) + уборка неудачных попыток.
⚠ Безопасность
Раздел Риски в описании — часть human-in-the-loop: агент обязан честно перечислить, что мог задеть и чего тесты не покрывают, помогая ревьюеру, а не усыпляя его. Идемпотентность предотвращает шум и путаницу из дублей; уборка брошенных веток не оставляет «висячих» предложений изменений.
Проверка
MR содержит все четыре раздела, заполненные из реального состояния задачи (вердикт verify совпадает с прогоном). Повторный запуск на той же ветке обновляет существующий MR, а не создаёт второй; брошенная ветка без MR удаляется.
Глубже
Идемпотентность и устойчивость к ретраям — курс «Продакшн-разработка», Модуль 1 (durable execution, гл. 1); структурированный вывод и контракты — Модуль 4 (гл. 7).
+// renderMRBody собирает структурное описание из состояния задачи.
+func renderMRBody(task Task, plan Plan, v Verdict, diffStat string) string {
+ return "## Что\n" + plan.summary() +
+ "\n\n## Зачем\n" + task.Issue +
+ "\n\n## Как протестировано\n" + verdictReport(v) + // build/tests/lint + итоги
+ "\n\n## Риски\n" + riskNotes(plan, diffStat) // что задето, чего не покрыли тесты
+}
+
+// openOrUpdateMR идемпотентен по ветке: не плодит дубли при ретраях/вебхуках.
+func openOrUpdateMR(ctx context.Context, f Forge, pr PullRequest) (string, error) {
+ if url, found, err := f.FindPR(ctx, pr.Branch); err != nil {
+ return "", err
+ } else if found {
+ return url, updatePR(ctx, f, pr) // обновляем существующий, не создаём второй
+ }
+ return f.CreatePR(ctx, pr)
+}Anti-patterns
| Грабля | Почему плохо | Как правильно |
|---|---|---|
| Описание MR свободным текстом без структуры рисков | Ревьюер не видит, что задето и чего не покрыли тесты; ответственное одобрение затруднено | Шаблон: Что / Зачем / Как протестировано / Риски — из реального состояния задачи |
| Создавать MR без проверки существующего | Ретрай/повторный вебхук плодят дубли MR | Идемпотентность: find-or-update по ветке задачи |
| Оставлять брошенные ветки/worktree после неудач | Мусор, висячие предложения изменений, путаница | Уборка: снять worktree, удалить ветку без MR |
| Преувеличивать готовность в описании | Усыпляет ревьюера; ложное чувство безопасности | Честный раздел Риски; вердикт verify приводится как есть |
Практическое задание (RA-v4)
- Сгенерировать описание MR по шаблону Что/Зачем/Как протестировано/Риски из состояния задачи и вердикта
verify. - Сделать создание MR идемпотентным (find-or-update по ветке); убирать брошенные ветки/worktree.
- Закоммитить:
git commit -m "v4: structured MR body + idempotency".
Проверка знаний
Агента перезапустили по повторному вебхуку на той же ветке. Как не создать дубль MR?
Верный ответ: B
B. Ключ идемпотентности — стабильная ветка задачи. Find-or-update гарантирует ровно один MR на задачу при любых ретраях. Случайные суффиксы (A) плодят дубли; «лишние закроет человек» (C) — перекладывание шума на ревьюера.