Вайбкодинг без страховки превращает код в долг. Я обвязал Claude CPG-конвейером

В одной из сессий Claude написал у меня 47 файлов и почти 1900 строк за 38 минут. Там были два новых сценария, рефакторинг шести обработчиков и миграция конфига.

После такого быстро перестаёшь спорить о терминах. Вайбкодинг уже не экзотика. Это обычный рабочий режим.

Меня в этой истории пугает не модель сама по себе. Меня пугает другое: скорость генерации давно обогнала скорость человеческой проверки. Один техлид не может спокойно прочитать почти две тысячи строк за полчаса и ещё удержать в голове, что эти изменения ломают в соседних модулях.

Поэтому я пошёл не в сторону запретов, а в сторону страховки. Обвязал ИИ-ассистента CPG-конвейером. Каждый коммит проходит автоматический анализ. Каждый промпт получает контекст из графа кода. Каждый пул-реквест получает оценку риска и радиус изменений.

Я не доверяю генератору кода без автоматической проверки. И себе, если честно, тоже.

Что ломается при вайбкодинге

Если смотреть на проблему трезво, провалы почти всегда одни и те же.

Первое. Модель охотно дублирует логику вместо того, чтобы вынести её в общее место. Код растёт, ветвлений становится больше, а замечают это обычно уже после релиза.

Второе. Она не видит систему целиком. Видит файл, иногда пару соседних. Но не граф вызовов. В итоге меняешь один обработчик, а цепляешь ещё десяток мест.

Третье. Начинается дрейф в документации. Модель добавила новый сценарий, а в тексте осталось старое число. На одном из прошлых аудитов я нашёл 14 таких расхождений за вечер.

Без верификации вайбкодинг быстро копит долг. С верификацией он становится нормальным конвейером.

Как устроен конвейер

В моём случае Claude Code умеет вызывать хуки на разных этапах сессии. Я использую пять.

На старте сессии ассистент получает срез по проекту: сколько методов в кодовой базе, сколько файлов, какая средняя сложность.

Когда я отправляю промпт вроде «рефакторинг CommitAnalyzer», хук вытаскивает из CPG конкретный контекст по этому классу: где он лежит, какая у методов цикломатическая сложность, сколько у них входящих и исходящих вызовов.

Перед правкой можно вставить быстрые предупреждения. Например: у метода уже высокая сложность, в файле висят TODO, изменение затронет публичный путь.

После git commit запускается главный анализатор. Он проверяет, не устарел ли CPG, при необходимости обновляет его, считает метрики качества и радиус поражения. Отчёт возвращается прямо в диалог с моделью.

На выходе из сессии можно сделать пост-анализ: какие файлы вообще трогали, где выросла сложность, что начало подозрительно распухать.

Снаружи это выглядит просто. Я пишу промпт. Модель пишет код. Конвейер тут же проверяет, во что этот код уткнулся.

Самый важный хук: анализ коммита

Главный кусок здесь commit_analysis.py. Он запускается после каждого git commit внутри ассистента.

У него четыре шага.

  1. Проверка свежести CPG. Сравнивается хеш коммита из состояния графа с текущим HEAD.

  2. Обновление графа. Если CPG отстал, запускается инкрементальный gocpg update.

  3. Анализ качества. Считаются сложность, разветвлённость, TODO/FIXME, отладочный мусор, устаревшие методы.

  4. Анализ влияния. Смотрим, кто вызывает изменённые методы и через какие интерфейсы до них вообще добираются.

После коммита модель получает не абстрактное «всё ок», а нормальный отчёт. Например: изменено 4 файла, затронуто 12 методов, у одного метода сложность выросла с 8 до 12, ещё восемь вызывающих теперь под риском.

Это важный момент. Модель не помнит архитектуру сама по себе. Ей нужно подложить факты. Я их подсовываю автоматически.

Контекст до генерации полезнее, чем разбор после

Пост-анализ хорош тем, что ловит ошибки. Но ещё лучше не доводить до них.

Если я пишу «рефакторинг CommitAnalyzer» без CPG-контекста, модель просто ищет файл и предлагает правки. Она не знает, насколько там всё уже запутано, кто это вызывает и есть ли в коде старые хвосты.

С CPG-контекстом она получает это заранее. Например: у analyze_commit сложность 8, у него 8 прямых вызывающих, в файле есть TODO, наружу модуль торчит только через CLI, а через REST не торчит вообще.

И это меняет качество следующего ответа. Модель начинает осторожнее обходиться с совместимостью и меньше стреляет в темноте.

Git-хуки нужны, чтобы граф не протухал

CPG устаревает после любого коммита. Добавили файл, поменяли сигнатуру, вынесли логику в новый модуль, и вчерашний граф уже врёт.

Поэтому я поставил три git-хука: post-commit, post-merge и post-checkout. Их задача простая: после изменения репозитория запустить gocpg update в фоне.

Полный парсинг моей кодовой базы занимает около 85 секунд. Инкрементальный апдейт для пары файлов обычно укладывается в 2–5 секунд. Для 10–20 файлов получается 5–12 секунд.

Если сделать это асинхронно, разработчик почти не ждёт. Пока он идёт к следующему действию, граф уже успевает подтянуться.

В CI тот же подход, только без ручной магии

Локально всё работает быстро, но мне важно было дотащить ту же схему до пул-реквестов.

В CI я собираю GoCPG, восстанавливаю кешированный граф, обновляю его от базовой ветки и публикую результат в комментарий к PR. Отдельно грузятся SARIF-аннотации.

В итоге рецензент видит не просто большой дифф, а конкретику:

  • сколько файлов поменяли;

  • сколько узлов добавилось в граф;

  • попали ли в кеш;

  • какие места выглядят как риск;

  • где всплыли security-находки.

Это всё равно не отменяет код-ревью. Но убирает слепую зону. Особенно когда ИИ за один проход выплёвывает 40+ файлов.

Отдельная боль: числовые утверждения в статьях и доках

Я быстро заметил другую проблему. Модель любит писать числа уверенно. «95 обработчиков», «21 сценарий», «42 тысячи методов». Один раз это верно. Через неделю уже нет.

Поэтому я сделал claims_validator.py. Он проходит по markdown-файлам, вытаскивает конструкции вроде «95 обработчиков» или «90+ scenarios», находит ключевое слово и сопоставляет его с SQL-запросом к CPG.

Если число в тексте не совпало с фактом в графе, валидатор это показывает.

На моих материалах он проверяет несколько десятков утверждений меньше чем за секунду. И да, расхождения ловит. Причём ровно те, которые глазом легко пропустить, особенно в длинной серии статей.

Мне нравится эта часть не потому, что она «умная», а потому что она приземляет пафос. Написал число — будь добр подтвердить.

Самый показательный эпизод: валидация пользовательских историй

Здесь у меня был хороший отрезвляющий кейс.

Я поставил задачу: взять 83 выполненные пользовательские истории и для каждой проверить, доступна ли её функциональность хотя бы через один из пяти интерфейсов: CLI, REST API, TUI, MCP или ACP.

Ассистент сгенерировал StoryValidationRunner примерно на 450 строк. Написал 39 юнит-тестов. Все зелёные.

Первый запуск на реальном CPG дал такой результат: 83 истории, полное покрытие ноль, частичное ноль, без покрытия 83.

То есть вообще всё сломано.

Дальше пошла нормальная инженерная работа. Не магия, не вайб, а разбор.

Первый баг: пути. В CPG они лежали в Windows-формате, а запросы искали Unix-разделители. Ни один шаблон не совпадал.

Второй баг: оркестратор пытался вызывать интерактивный сценарий как пакетный.

Исправили это, добавили поддержку Go CPG, докинули тесты. Результат стал уже человеческим: 46 историй с полным покрытием, 32 с частичным, только 2 не найдены, ещё 3 неприменимы.

Потом вылезла следующая волна проблем. Ложные срабатывания. Один и тот же REST-маршрут начал притягиваться к историям, к которым не имел отношения. Причина была в урезанном регулярном выражении: оно хватало только первый сегмент пути. Плюс не хватало стоп-слов, и общие слова матчились там, где не должны.

После этого «полное покрытие» даже немного уменьшилось: с 49 до 46. И это хороший пример того, как люди неправильно читают метрики. Число снизилось, но система стала точнее. Три «успеха» оказались мусором.

Все эти баги нашлись только на реальном графе. Юнит-тесты были зелёными на каждом этапе. Они честно проверяли моки и при этом ничего не говорили о реальной структуре проекта.

Вот за это я и ценю CPG. Он не даёт притвориться, что абстракция и реальность совпадают.

Что я добавил в код-ревью после этого

После этой истории конвейер разросся. Было пять хуков. Стало тринадцать проверок.

Среди них самые полезные для меня такие:

  • предупреждение, если изменённый метод торчит наружу через CLI, REST или MCP;

  • проверка, зарегистрирован ли новый обработчик там, где должен;

  • поиск межмодульных зависимостей, когда правка в одном слое цепляет другой;

  • анализ реального git diff, а не догадок по файлам;

  • учёт Go-кода отдельным CPG;

  • проверка тестов и регистрации для новых файлов;

  • транзитивный анализ вызывающих, не только прямых;

  • отслеживание дельты покрытия историй интерфейсами.

На одном из реальных коммитов это выглядело скучно, и в этом как раз плюс: три файла, несколько строк диффа, ноль ложных тревог, ноль проблем с интерфейсами, мягкая деградация при отсутствии бинарника GoCPG, все тесты зелёные. Хороший конвейер часто выглядит именно так. Тихо. Без сюрпризов.

Зачем хранить историю качества

Разовый отчёт после коммита полезен. Но на дистанции важнее другое: куда проект вообще ползёт.

Я записываю снимки качества в DuckDB после каждого успешного анализа. Сколько методов в кодовой базе, какая средняя сложность, сколько тяжёлых методов, сколько TODO, есть ли мёртвый код.

Например, за неделю у меня прибавилось 46 методов. Средняя цикломатическая сложность не поползла вверх. Количество тяжёлых методов даже снизилось. Значит, код растёт, но не расползается бесконтрольно.

Мне это нравится намного больше, чем разговоры в духе «кажется, проект стал сложнее». Кажется или не кажется, можно посчитать.

Что я из этого вынес

  1. Хук не должен бесить. Если он тормозит сильнее, чем сама генерация, его выключат.

  2. Контекст до генерации выгоднее, чем долгий разбор после. Ошибку лучше не допустить, чем красиво её описать.

  3. Линтер видит файл. Граф видит систему. Для некоторых классов проблем разница принципиальная.

  4. Всё должно ставиться одной командой и дальше жить само. Иначе этим будут пользоваться только на демо.

Я не пытаюсь остановить вайбкодинг. Поздно. Он уже въехал в разработку и обжился.

Вопрос теперь другой: вы продолжаете делать вид, что ручного просмотра хватит, или строите вокруг генерации нормальный контур проверки.

У меня ответ получился довольно приземлённый. Модель генерирует. CPG проверяет. Git-хуки обновляют граф. CI добивает то, что не видно локально. Валидатор чисел ловит враньё в текстах. А реальные данные регулярно портят красивую картину из моков и зелёных тестов.

Автор: msvn

Источник

Оставить комментарий