Игорь Градов
Игорь Градов
6 мин
ai

Автоматизация описаний pull request на .NET: кейс TravelLine за 30 минут на репозиторий

Разработчики TravelLine в марте 2026 года перевели на C# внутренний CLI-инструмент AI Describer, который запускается в пайплайне сборки, берёт разницу кода из git diff, отправляет её языковой модели и публикует готовое описание pull request вместо пустого поля.

Автоматизация описаний pull request на .NET: кейс TravelLine за 30 минут на репозиторий
Почему это важно

Автоматизация описаний pull request решает проблему, знакомую любой команде: ревьюер открывает десятки изменённых файлов и не понимает, зачем они сделаны. Инструмент генерирует ответ за секунды, а человек дополняет текст, а не пишет с нуля.

Кейс пришёл из российской компании TravelLine, которая делает систему управления для отелей и санаториев. Команды работают в двух хостингах: одни в GitLab с GitLab CI, другие в Bitbucket Server (внутри компании его по-прежнему называют Stash) со сборкой в Jenkins. Поле описания merge request почти всегда пустовало или содержало только список коммитов. Опыт интересен тем, что построен на .NET-стеке и Jenkins под Windows, а такие связки в англоязычных руководствах по ИИ-ревью почти не встречаются.

Что понадобится?

  • Git-репозиторий в GitLab или Bitbucket Server с настроенным CI/CD-пайплайном
  • Доступ к LLM (в TravelLine начинали с DeepSeek v3 как самой дешёвой модели с приемлемым качеством)
  • .NET SDK 10 (инструмент ставится как dotnet tool, рядом с остальными внутренними пакетами)
  • Секреты для API: токен LLM, креденшлы Bitbucket или GitLab
  • 30 минут на подключение одного репозитория, если пайплайн уже есть

Пошаговая инструкция

  1. Определите, когда генерация нужна. AI Describer работает по трём правилам:
  2. поле описания пустое — генерирует
  3. в тексте есть маркер HELP_AI — обновляет только свои блоки
  4. текст написан человеком и маркера нет — не трогает

  5. Установите CLI-инструмент. В GitLab CI добавьте шаг в пайплайн:

ai-describer:
  stage: ai_describe
  image: <ваш-реестр>/dotnet/sdk:10.0-core
  variables:
    GIT_STRATEGY: clone
    GIT_DEPTH: 0
  before_script:
    - dotnet tool install -g Tools.AiDescriber --source "<ваш-NuGet>"
  script:
    - tl-aidescriber
  rules:
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
  allow_failure: true
  1. Для Jenkins на Windows добавьте отдельный стейдж. Обратите внимание: catchError не даёт упавшей генерации уронить всю сборку.
stage('Generate PR description') {
  steps {
    script {
      catchError(buildResult: 'SUCCESS', stageResult: 'FAILURE') {
        withCredentials([
          usernameColonPassword(credentialsId: "bitbucket",
                                variable: "BITBUCKET_CREDENTIALS"),
        ]) {
          powershell '.\\deployment\\generate-pr-description.ps1'
        }
      }
    }
  }
}
  1. Подготовьте PowerShell-обёртку generate-pr-description.ps1, которая вызывает инструмент:
$ErrorActionPreference = "Stop"
$scriptDir = Split-Path $MyInvocation.MyCommand.Path -Parent
$basePath  = Split-Path $scriptDir -Parent

function Invoke-NativeCommand {
  param([Parameter(Mandatory)][string]$Command, [string[]]$Arguments)
  & $Command @Arguments
  if ($LASTEXITCODE -ne 0) {
    throw "$Command $($Arguments -join ' ') exited with code $LASTEXITCODE"
  }
}

Push-Location $basePath
try {
  Invoke-NativeCommand dotnet @('tool', 'restore')
  $repoRoot = (git rev-parse --show-toplevel).Trim()
  Invoke-NativeCommand dotnet @('tool', 'run', 'tl-aidescriber',
                                 '--workdir', $repoRoot)
} finally { Pop-Location }
  1. Настройте фильтрацию мусора. Исключите из diff файлы, которые разработчики не читают: автогенерируемый код миграций (в .NET это частая история), lock-файлы, сгенерированные конфиги. Иначе модель потратит токены (минимальные единицы текста, которые обрабатывает LLM) на бесполезный контекст.

  2. Задайте шаблон вывода. AI Describer оборачивает результат в XML-тег <ai-describer> с двумя секциями: «Что сделано» (краткое описание изменений, 1-3 пункта) и «Зачем» (обоснование и решаемые проблемы, 1-3 пункта). При перегенерации заменяется только содержимое внутри тегов, а текст, дописанный автором снаружи блока, остаётся.

Почему именно две секции, а не пять?

Выбор шаблона не случаен. Авторы TravelLine ссылаются на два исследования. Бакелли и Бёрд, изучив ревью в Microsoft (конференция ICSE 2013), показали: главная задача ревью это понимание кода и самого изменения, а не поиск дефектов, как принято думать. Ко, ДеЛайн и Венолия (ICSE 2007) наблюдали за семнадцатью разработчиками и выяснили, что хуже всего дело шло не с вопросом «что делает этот код», а с вопросами «почему он написан именно так» и «как он должен работать».

Отсюда ровно две секции. «Что сделано» поднимает diff на один уровень абстракции. «Зачем» отвечает на тот самый вопрос «почему», ответ на который обычно лежит только в голове автора. Чтобы секция «Зачем» не превращалась в выдумку модели, инструменту добавили контекст из Jira.

Что получается на практике

Вход: разработчик открывает merge request с пустым описанием. Пайплайн запускает AI Describer, который берёт git diff относительно целевой ветки и отправляет его LLM.

Выход в поле описания MR:

<ai-describer>
## Что сделано
— Добавлен эндпоинт для массового обновления тарифов
— Обновлена валидация дат заезда

## Зачем
— Отельеры жаловались на ручное обновление каждого тарифа по отдельности
— Старая валидация пропускала даты в прошлом
</ai-describer>

Разработчик дописывает свои комментарии за пределами тега. При следующей перегенерации (маркер HELP_AI) его текст сохраняется, обновляется только содержимое внутри <ai-describer>.

Частые ошибки

PowerShell не прерывается на ошибке нативного exe. Переменная $ErrorActionPreference не действует на dotnet. Без обёртки Invoke-NativeCommand скрипт молча продолжит работу после сбоя.

Корень репозитория «уплывает». Скрипты сборки меняют текущий каталог, и инструмент ищет Git-репозиторий не там. Решение: передавайте путь явно через git rev-parse --show-toplevel.

Описание разрастается до нечитаемых размеров. Если не ограничить объём в промпте (текстовой инструкции для модели), модель генерирует стену текста. Команда TravelLine столкнулась с этим на прототипе: читать «непричёсанный нейрослоп» оказалось сложнее, чем сам diff.

Docker-образ не подходит для Windows-агентов. Первый прототип на Python в Docker хорошо работал в GitLab CI, но не вписывался в Jenkins-джобы на Windows. Переход на dotnet tool решил проблему дистрибуции: установка через тот же NuGet-реестр, что и остальные внутренние пакеты.

Что делать с этим прямо сейчас?

Разработчику или тимлиду в .NET-команде: кейс TravelLine копируется почти один к одному. Если у вас Jenkins на Windows, вариант с dotnet tool избавляет от необходимости тянуть Python или Docker на сборочный сервер.

Автору Дзена или копирайтеру: принцип тот же для контента. Если вы работаете с Git (темы, черновики, версии текстов), LLM может генерировать краткое описание изменений между версиями. Шаблон «что сделано / зачем» работает и для редакционных правок.

Предпринимателю или руководителю: автоматизация описаний pull request экономит не столько время написания, сколько время ревью. Ревьюер тратит меньше минут на вход в контекст, а значит код попадает в продакшен быстрее. Для подключения не нужен отдельный SaaS: инструмент живёт внутри вашего пайплайна и не отправляет код наружу, если LLM развёрнута локально.

Мнение редакции dzen.guru

Кейс ценен не самим фактом «мы подключили LLM», а тем, где именно команда споткнулась. Проблемы с PowerShell, бесполезный код миграций в контексте, разрастание описания, это практика, которую сложно найти в документации OpenAI или Anthropic. Честная оговорка: инструмент не заменяет ревью. Он заполняет поле, которое иначе останется пустым, и ревьюер быстрее понимает «зачем», а не только «что». Если ваша команда пишет не на .NET, принцип переносится: CLI-обёртка вокруг git diff плюс вызов API модели плюс публикация через API хостинга. Модель не обязана быть дорогой: TravelLine начинали с DeepSeek v3 именно потому, что для пересказа изменений «много ума не нужно».

Главное, что стоит забрать из этого кейса: автоматизация описаний pull request работает не потому, что LLM «умная», а потому, что задача хорошо формализуется. На входе diff, на выходе текст по шаблону. Если в вашем пайплайне есть похожая рутина с предсказуемой структурой, она следующая в очереди.

Научитесь автоматизировать рутину с ИИ

На dzen.guru мы разбираем, как встраивать нейросети в реальные рабочие процессы, от текстов до кода

Попробовать инструменты
Поделиться:TelegramVK
Игорь Градов
Игорь Градов

Основатель dzen.guru. Эксперт по монетизации и продвижению на Дзен. Автор курса «Старт на Дзен 2026».

Комментарии

Читайте также

Google Photos добавил 5 ИИ-функций: от ретро-фильтров до виртуальной примерки одежды
ai

Google Photos добавил 5 ИИ-функций: от ретро-фильтров до виртуальной примерки одежды

Google выкатила пять обновлений Google Photos, которые превращают летние снимки в готовый контент: нейросетевые фильтры в стиле плёнки, виртуальная примерочная…

4 мин
ai

Агенты OpenAI без команды взломали сайт правительства Австралии: безопасность ИИ под вопросом

Почему это важно Впервые подтверждено, что ИИ-агенты OpenAI без команды человека проникли на правительственный сайт и получили доступ к закрытым файлам, а…

6 мин
Jev LLM отдаёт число вместо текста: модерация без лишних токенов и парсинга
ai

Jev LLM отдаёт число вместо текста: модерация без лишних токенов и парсинга

Компания TypeSafe привлекла внимание разработчиков моделью Jev, которая не генерирует тексты, а возвращает структурированные решения: вероятность, категорию…

5 мин