AI Фактор

Чекліст, щоб AI-агент не зламав legacy-код

Прийом 27.10.2026
Чекліст, щоб AI-агент не зламав legacy-код

Автор розбирає чотири типові провали AI-агентів на legacy-коді: втрата контексту у великих файлах, зламані відступи при масовому рерайті, цикл нескінченних виправлень і тихе видалення логіки (порядок обробки, логування, запис у БД) — тести зелені, а продакшн ламається. Рішення: передавати агенту AST-скелет (лише сигнатури функцій і docstring, без тіла) і працювати в три фази — зафіксувати поведінку тестами, обмежити правку до функції ≤50 рядків, після кожної зміни перевіряти код і автоматично відкочувати через Git у разі провалу.

Чотири провали, яких ґардрейл має зупинити

Без обмежень агент на legacy-коді провалюється за одним із чотирьох сценаріїв з джерела.

  • Втрата контексту. Файл не влазить у вікно одночасно, точність уваги моделі падає, і вона пропускає умовні розгалуження чи обробку винятків — результат: runtime-помилки з посиланням на неоголошені змінні.
  • Зламані відступи. При масовому переписуванні формат diff ламається: з'являються помилки відступів або пропущені закривні скоби, і програма просто не стартує.
  • Нескінченний цикл правок. Тест падає — агент різко переписує існуючу логіку, щоб його полагодити, і кожна така правка ламає щось інше.
  • Тихе видалення логіки. Заради «прибирання коду» агент видаляє порядок обробки, вивід логів чи запис у БД; юніт-тести лишаються зеленими, а продакшн ламається через неузгодженість даних.

AST-скелет: що бачить агент замість повного файлу

Замість усього файлу агенту передають скелет — лишаються тільки сигнатури функцій (назва, аргументи, type hints) і docstring, а тіло функції замінене на pass. У джерелі це клас SkeletonExtractor(ast.NodeTransformer): метод visit_FunctionDef бере docstring, якщо є, додає ast.Pass() і повертає вузол без оригінального тіла. Команда python legacy_guardrail.py extract ШЛЯХ_ДО_ФАЙЛУ парсить код через ast.parse, проганяє через трансформер і віддає текст через ast.unparse.

Перевірка — окрема команда python legacy_guardrail.py validate ШЛЯХ_ДО_ФАЙЛУ: спершу ast.parse ловить синтаксичні помилки з номером рядка й колонки, потім ruff check --select E,F прогонить статичний аналіз. Якщо провалюється хоч один крок, скрипт завершується з кодом 1 і друкує, на чому саме зупинився.

У прикладі прогону з матеріалу на файлі 1480 рядків підхід дав 38/38 пройдених тестів, покриття 91.4% і зайняв 18.4 секунди на 8940 токенів (близько $0.027).

Три фази замість «перепиши весь файл»

Інструкція для агента з файлу AGENT_INSTRUCTIONS.md розписує три послідовні фази. У фазі 1 редагувати файл прямо заборонено: спершу агент пише тести в теку tests/ на поточну поведінку (нормальні, помилкові й межові випадки), мокає зовнішні залежності через unittest.mock чи pytest-mock і проганяє pytest, доки всі тести не позеленіють.

У фазі 2 агент не чіпає файл цілком — виконує python legacy_guardrail.py extract ШЛЯХ_ДО_ФАЙЛУ і планує правку, обираючи функцію «до 50 рядків за раз з мінімумом залежностей від іншого коду». У фазі 3 після кожної зміни він зобов'язаний запустити python legacy_guardrail.py validate ШЛЯХ_ДО_ФАЙЛУ; якщо помилка не зникає після точкової правки, інструкція наказує не продовжувати навмання, а відкотити через git checkout -- ШЛЯХ_ДО_ФАЙЛУ і звужувати область правки.

Прямо прописані заборони: більше 100 рядків змін за одну правку, переписування самого тесту, щоби він штучно пройшов, і коміт без прогону legacy_guardrail.py validate.

Три провали, які трапляються навіть із ґардрейлом

  • Масове заглушування моками. Коли в одній функції змішані зовнішні залежності (БД, мережа) і чиста обчислювальна логіка, агент не розрізняє, що мокати, і підміняє все підряд на MagicMock, щоб тест просто пройшов. Виправлення: прямо в системний промпт писати, що мокати можна лише зовнішні межі (мережа, файли, БД), а доменну логіку мокати заборонено, і підключити pytest-cov, щоб бачити, чи реально виконується тіло функції.
  • Цикл правок через відступи. У legacy-коді змішані таби й пробіли, і агент нескінченно переправляє те саме місце. Перед тим як давати код агенту, прогнати ruff format по всьому файлу, щоб уніфікувати відступи (4 пробіли) й кінці рядків; якщо помилка все ж трапляється, у промпті явно обмежувати вивід лише конкретною функцією з помилкою, а не регенерувати весь файл.
  • Видалення побічних ефектів. Заради «спрощення коду» агент прибирає оновлення глобального стану чи вивід аудит-логів у кінці функції, бо на перший погляд вони здаються непотрібними. У фазі 1 перевірку логів і змін глобального стану варто включати прямо в assert тестів, а перед фазою правок — окремим кроком змушувати агента виписати список побічних ефектів, які мають лишитися незмінними.
Раніше в теміGoogle: чому вайб-чек не ловить збої агентівПізніше в теміClaude Code: змінна, що вимикає останній захист
Читати в ТелеграміПрактика ШІ в бізнесі — щодня, без хайпу