Как стоимость разработки смещается от генерации к проверке, рефакторингу и контролю изменений
Я всё чаще вижу один и тот же сценарий. Агент получает задачу, за несколько минут собирает патч, запускает тесты и сообщает об успехе. Первый просмотр выглядит обнадёживающе. Потом начинается ревью.
Один обработчик исключений не совпадает с принятым в проекте. Новый сервис дублирует уже существующий слой. Тест прошёл с другим профилем. Метод из зависимости существует в документации, но отсутствует в версии, подключённой к модулю. Изменение рабочее локально, однако никто пока не доказал, что оно безопасно для остального графа вызовов.
Чем быстрее появляется код, тем больше времени уходит на восстановление контекста: что агент поменял, на какие факты опирался, какой конфигурацией запускал тесты и сколько попыток сделал до финального ответа.
В этой точке полезно отделить генерацию патча от подготовки изменения к отправке в основную ветку. В этой статье я называю инструменты для второй задачи Code Clean-up Agents. Это предлагаемая инженерная рамка, а не устоявшаяся рыночная категория.
Почему успешный патч ещё не результат
Цель генератора кода можно описать коротко:
требование -> правдоподобный патч
Для изменения в корпоративном проекте цепочка длиннее:
требование -> патч -> проверка связей между символами -> статические проверки IDE -> сборка и тесты в нужной конфигурации -> проверка поведения во время выполнения -> ревью изменений -> решение разработчика
У этих процессов разные функции качества. Генератор стремится создать код, который выглядит подходящим и решает сформулированную задачу. Агент очистки должен получить минимальное проверенное изменение: патч с понятными границами, воспроизводимыми проверками и достаточными доказательствами для ревью.
Разница особенно заметна после нескольких агентных задач. Первый патч вводит один способ преобразования DTO. Второй добавляет альтернативный mapper. Третий оборачивает ошибки в новый тип. Каждый фрагмент по отдельности может компилироваться. Вместе они создают несколько конкурирующих соглашений, которые затем приходится сводить вручную.
Code Clean-up Agent работает с накопленным результатом: находит расхождения, применяет существующие рефакторинги, проверяет уязвимые пути данных, приводит имена и конструкции к правилам проекта, затем показывает разработчику итоговый diff и результаты проверок.
Агентный цикл и нелинейная стоимость
Одиночный запрос к языковой модели обычно состоит из одного входа и одного ответа. У агента другая архитектура:
Goal -> Read context -> Plan -> Edit -> Run tools -> Read result -> Retry or finish
Если тест падает, цикл повторяется. Следующая итерация часто содержит исходную задачу, план, предыдущий патч, вывод сборки, трассировку ошибки и рассуждение о неудаче. Поэтому цена третьей попытки может быть выше цены первой.
Упрощённо стоимость сессии можно записать так:
C_total = Σ[i=1..n] ( C_context(i) + C_reasoning(i) + C_generation(i) + C_tools(i))
Здесь C_context(i) зависит от того, сколько истории перешло в итерацию i. Если агент при каждой ошибке добавляет полный лог и весь предыдущий диалог, объём контекста растёт:
Context(i + 1) = Context(i) + Patch(i) + ToolOutput(i) + FailureAnalysis(i)
Даже при одинаковом числе выходных токенов запросы постепенно дорожают. Инструментальные вызовы тоже имеют цену: тест занимает вычислительное время, индексирование и анализ требуют ресурсов IDE, внешняя среда может тарифицировать контейнер или облачный запуск.
Отсюда следует важная метрика:
стоимость успешной сессии != стоимость проверенного результата
Агент может завершить задачу и выдать патч, но если тест запускался с неверным профилем или затронутые вызовы не исполнялись, доказательства неполны. Затраты уже понесены, а инженерная работа продолжается на ревью.
Когда расходы растут вместе с числом попыток
Экономику агентного цикла можно оценивать без громких корпоративных примеров. Каждый повтор добавляет к расходам новый запрос модели, увеличившийся контекст и очередной запуск инструментов. Открытая цель при этом не задаёт верхнюю границу числу действий.
Точный расход зависит от модели, длины контекста, набора инструментов и характера задачи. Поэтому число подключённых разработчиков или завершённых сессий мало говорит об эффективности. Нужны показатели, связанные с принятым результатом:
-
стоимость принятого изменения, а не одной сессии;
-
доля патчей, прошедших детерминированные проверки;
-
число повторных запусков до передачи человеку;
-
объём контекста на каждой попытке;
-
количество замечаний после агентной проверки;
-
время от первого патча до принятого diff.
Ограничиваем агентный цикл до запуска
До начала работы агент должен знать доступный бюджет, число попыток, разрешённые файлы, допустимые инструменты и условие передачи задачи разработчику.
В Veai область разрешённых изменений задаётся через Edit Scope: разработчик указывает файлы и директории, которые агент может редактировать в конкретной задаче. Это удобнее постоянного изменения .agentignore, когда границы доступа зависят от задачи. Такой механизм превращает условие changedFiles ⊆ allowedEditScope из рекомендации в проверяемое ограничение.
Маршрутизация моделей
Не каждой операции нужна старшая модель. Классификация ошибки, нормализация формата или извлечение причины из лога могут выполняться дешёвой моделью. Архитектурное решение или анализ нескольких модулей передаются модели с более сильным рассуждением.
Task -> Router -> deterministic tool, если ответ уже доступен IDE -> small model, если требуется краткое преобразование -> flagship model, если требуется сложное решение
Первый маршрут здесь принципиален. Если IDE уже знает точный тип символа, список реализаций интерфейса или ошибку инспекции, обращаться к LLM за предположением дороже и менее надёжно, чем запросить структурированный факт.
Ограничение повторов
Лимит maxAttempts = 3 лучше бесконечного цикла, но одного счётчика мало. Попытки различаются по стоимости. Третья может включать два предыдущих патча и большие логи.
Ограничение должно учитывать сразу несколько величин:
attempts <= maxAttemptsspentTokens + estimatedNextTokens <= tokenBudgetelapsedTime + estimatedNextTime <= timeBudgetchangedFiles ⊆ allowedEditScope
Проверка остатка бюджета
Перед каждым дорогим действием контроллер оценивает следующую операцию.
Технически связать маршрутизацию, сокращение контекста и передачу человеку можно так:
data class Limits( val maxAttempts: Int, val tokenBudget: Long, val allowedFiles: Set<Path>)data class SessionState( val attempt: Int, val spentTokens: Long, val evidence: List<Evidence>, val failures: List<Failure>, val changedFiles: Set<Path>)fun cleanUp(task: Task, limits: Limits): Outcome { var state = SessionState(0, 0, emptyList(), emptyList(), emptySet()) while (state.attempt < limits.maxAttempts) { val context = contextPruner.compact(task, state) val route = router.select(task, context) val estimate = budgetEstimator.estimate(route, context) if (state.spentTokens + estimate.tokens > limits.tokenBudget) { return Outcome.Handoff(state, "token budget reached") } val proposal = route.execute(task, context) val touched = proposal.changedFiles if (!limits.allowedFiles.containsAll(touched)) { return Outcome.Handoff(state, "edit scope exceeded") } val staticEvidence = ideInspections.verify(proposal) val runtimeEvidence = runConfigurations.verify(proposal) val verification = verifier.combine(staticEvidence, runtimeEvidence) state = state.record(proposal, estimate, verification) if (verification.accepted) { return Outcome.NeedsHumanApproval(proposal, state.evidence) } } return Outcome.Handoff(state, "retry limit reached")}
Этот фрагмент не привязан к конкретному SDK агента, но контролирующая логика исполнима: оценка выполняется до дорогого вызова, область редактирования проверяется до применения патча, а успешная машинная проверка заканчивается запросом подтверждения, а не автоматическим слиянием.
Сокращение контекста
Передавать модели весь вывод Gradle или Maven на каждой попытке не требуется. Нужна структурированная выжимка, в которой сохранены:
-
завершившаяся с ошибкой задача;
-
класс и сообщение исключения;
-
первый релевантный фрейм стека;
-
модуль и конфигурация запуска;
-
изменившиеся относительно прошлой попытки симптомы;
-
ссылки на полный артефакт, если разработчику потребуется аудит.
Сокращение нельзя поручать только LLM. Парсер тестового отчёта, модель проекта IDE и структурированный результат запуска сохраняют факты точнее. Модель может сжать пояснение, но исходный артефакт должен оставаться доступным.
Контрольная точка разработчика
После превышения лимита полезен не текст «не удалось решить», а пакет передачи:
- текущий diff;- затронутые файлы и символы;- выполненные конфигурации запуска;- прошедшие и упавшие тесты;- результаты инспекций;- краткая история попыток;- расход токенов и времени;- причина остановки.
Разработчик получает состояние, с которого можно продолжить, изменить направление или отклонить патч. Потраченные попытки не растворяются в истории чата.
Четыре этапа подготовки изменения
Самооценка модели не заменяет проверку. Модель, создавшая патч, может повторить исходную ошибку при анализе результата. Устойчивее разделить роли:
предложение модели -> детерминированные проверки IDE -> проверка во время выполнения -> подтверждение разработчика
В Veai эта цепочка собирается из инструментов самой JetBrains IDE. Агент может проверить ссылки и инспекции по индексам проекта, запустить сохранённую Run Configuration, а при ошибке перейти к отладчику или анализу покрытия. Разработчик получает данные тех же механизмов, которыми проверяет код вручную. После основного агента в том же чате автоматически запускается отдельный Auto Review: он получает задачу и diff, читает код и использует инспекции IDE без права записи. Это вероятностная проверка, поэтому финальное решение остаётся за разработчиком.
1. Предложение модели
Модель формирует гипотезу и патч. На этом этапе результат считается кандидатом, а не решением.
2. Детерминированные проверки IDE
JetBrains IDE хранит структурированную модель проекта. Она знает объявления и использования символов, типы, наследование, области видимости, доступные рефакторинги и результаты инспекций.
Это позволяет проверить то, что поиск по строкам не доказывает:
-
какой именно перегруженный метод вызывается;
-
какие реализации интерфейса затронет изменение;
-
не сломается ли доступность после переноса символа;
-
можно ли безопасно переименовать метод во всех местах использования;
-
появились ли риск
NullPointerException, утечка ресурса или нарушение контракта типов.
Рефакторинг через IDE также отличается от текстовой замены. Rename refactoring меняет ссылки согласно семантической модели языка. Замена строки может затронуть комментарий, одноимённый локальный идентификатор или не обновить косвенное использование.
3. Проверка во время выполнения
Статический анализ не отвечает на все вопросы. Нужен запуск с тем же SDK, модулем, профилем и переменными среды, которые использует проект.
Run Configurations дают агенту готовую конфигурацию вместо самостоятельно собранной команды. Отладчик показывает значения переменных и фактическую ветку выполнения. Анализ покрытия отвечает, исполнялся ли изменённый участок. Инструменты наблюдения во время выполнения могут записать последовательность вызовов и аргументы без добавления временных println в код.
Отдельная проблема возникает с зависимостями. Документация описывает опубликованный API, но проект может использовать другую версию или внутреннюю сборку. Decompile позволяет читать фактически подключённый байткод и проверять доступную сигнатуру.
4. Подтверждение разработчика
Даже полный набор автоматических проверок не знает всех бизнес-ограничений. Разработчик решает, соответствует ли патч намерению задачи, допустим ли архитектурный компромисс и достаточно ли собранных доказательств.
Что именно очищает Clean-up Agent
Работу удобно разделить на три класса.
Архитектурная нормализация и соглашения проекта
Агент ищет дублирующиеся способы решить одну задачу: несколько mapper-классов, разные схемы обработки ошибок, обход существующего слоя доступа к данным, новые абстракции поверх уже имеющихся. После анализа он предлагает минимальный рефакторинг с учётом всех использований.
Сюда же относятся принятые типы ошибок, правила именования, расположение тестов, способ создания компонентов и границы модулей. Такие соглашения извлекаются из существующего кода и конфигурации, а спорные изменения показываются разработчику.
Проверка безопасности
Сюда входят пути, где внешние данные доходят до SQL-запроса, файловой системы, десериализации или проверки прав доступа. Нужна трассировка пути данных: откуда значение пришло и где стало опасным. Простого совпадения со словом execute или readObject недостаточно.
Исследование 2026 года о приложениях, созданных с помощью генеративного программирования, описывает измеримый долг безопасности (arXiv:2606.23130). Другая работа отмечает отклонение от безопасных практик при LLM-assisted разработке постквантовой криптографии (arXiv:2606.19474). Эти результаты не доказывают уязвимость любого AI-патча, но поддерживают необходимость отдельной проверки после генерации.
Подготовка проверяемого набора изменений
Финальный результат должен отвечать на вопросы ревьюера:
-
какие файлы и символы изменены;
-
зачем требовалось каждое изменение;
-
какие инспекции запускались;
-
какой конфигурацией выполнены тесты;
-
какие ветки покрыты;
-
сколько попыток потребовалось;
-
где осталась неопределённость.
Именно здесь Code Clean-up Agent отличается от очередного режима генерации. Его единица результата: минимальный diff с проверяемым происхождением и результатами выполненных проверок.
Почему средой становится IDE
Терминальный агент хорошо вызывает команды и читает текстовые артефакты. Для очистки крупного Java- или Kotlin-проекта этого мало. Требуются сведения, которые уже вычисляет IDE:
|
Задача |
Текстовый подход |
Механизм JetBrains IDE |
|---|---|---|
|
Найти использования |
поиск совпадений |
символьные ссылки и полиморфные вызовы |
|
Переименовать API |
замена текста |
language-aware refactoring |
|
Проверить ошибку |
чтение лога |
inspections, debugger, structured test result |
|
Запустить проект |
восстановить команду |
Run Configuration с SDK и окружением |
|
Проверить зависимость |
документация или поиск в сети |
decompile подключённой версии |
|
Оценить тест |
код теста и stdout |
результат запуска и coverage |
Veai реализует этот подход внутри JetBrains IDE. Например, при безопасном переименовании агент использует семантический рефакторинг и получает все ссылки на символ из индекса. Если исправление связано с поведением приложения, он запускает готовую Run Configuration с настройками проекта и может проверить фактические значения через отладчик. Для кода из зависимости Veai читает подключённую версию библиотеки, включая декомпилированные классы, поэтому проверяет доступную проекту сигнатуру, а не API из другой версии документации.
Edit Scope дополняет эти проверки контролем изменений: разработчик заранее ограничивает файлы и директории, доступные для редактирования. В итоге Veai связывает предложение модели, факты IDE, запуск приложения и подтверждение человека в одной сессии.
Где автоматизацию надо остановить
Есть задачи, для которых следующий агентный цикл добавляет мало информации.
Нестабильный тест
Если один тест падает с разными симптомами без изменений кода, повторная генерация патча может маскировать проблему. Нужна диагностика среды, временных зависимостей или конкуренции потоков.
Неявное бизнес-правило
Код может допускать несколько корректных поведений, а нужное известно только владельцу продукта. Агент обязан сформулировать развилку и запросить решение.
Изменение публичного контракта
Новая сигнатура API, формат события или схема данных затрагивают внешних потребителей. Локально прошедших тестов недостаточно.
Миграция данных
Откат, блокировки, объём таблиц и совместимость версий требуют отдельного плана. Автоматический рефакторинг приложения не доказывает безопасность миграции.
Ошибка распределённой системы без наблюдаемости
Если нет трассировки между сервисами, идентификаторов запросов и воспроизводимого стенда, модель будет строить гипотезы по неполному логу. Сначала нужно получить наблюдаемые данные.
Высокая цена следующей попытки
Когда бюджет почти исчерпан, а новая итерация повторяет уже проверенную гипотезу, корректный результат агента: остановиться и передать накопленные доказательства человеку.
Ограничения подхода
Code Clean-up Agent не доказывает правильность требований и не знает скрытых договорённостей команды. Инспекции могут иметь ложные срабатывания. Прошедшие тесты подтверждают только покрытые сценарии. Coverage показывает факт исполнения строк и веток, но не качество утверждений в тесте. Отладчик фиксирует один запуск, а не все возможные состояния конкурентной системы.
Экономия тоже не гарантирована. Для маленькой очевидной правки полный цикл проверок может стоить дороже ручного изменения. Для архитектурной задачи плохо настроенные лимиты остановят агента слишком рано. Слишком щедрые лимиты вернут проблему дорогих повторов.
Поэтому оценивать такую систему стоит по стоимости принятого изменения, числу предотвращённых замечаний и воспроизводимости доказательств. Количество сгенерированных строк и число запущенных сессий для этого недостаточны.
Что меняется в процессе разработки
После массового внедрения генерации узким местом становится не набор кода. Команда тратит время на проверку согласованности, анализ влияния, воспроизведение запусков и контроль стоимости агентных циклов.
Code Clean-up Agents оформляют эту работу в отдельный этап:
план -> AI-генерация -> очистка и нормализация -> детерминированная проверка -> проверка во время выполнения -> подтверждение разработчика -> отправка изменения
Для команды это означает новый контракт с агентом. Он не получает бесконечную задачу «исправь всё». Он получает ограниченную область файлов, бюджет, условия остановки и перечень обязательных проверок. В ответ возвращает патч вместе с доказательствами и историей затрат.
Veai уже строит такой процесс внутри JetBrains IDE: Edit Scope ограничивает область изменений, индексы и инспекции дают агенту факты о коде, Run Configurations и отладчик подтверждают поведение приложения. Auto Review автоматически запускает отдельного агента-ревьюера в том же чате: он проверяет задачу и diff с помощью чтения кода и инспекций IDE, но не редактирует файлы. Финальное решение остаётся за разработчиком.
ссылка на оригинал статьи https://habr.com/ru/articles/1061498/