Чимало PHP-коду тепер пишуть Claude Code, Codex чи Gemini CLI, і помилки в ньому мають свій почерк: порожній catch, що ховає збій, метод на двісті рядків, запит до бази всередині циклу, тіло методу, замінене коментарем // ... existing code .... Типи при цьому сходяться, форматування бездоганне, тож PHPStan і Pint мовчать. Sloppy від розробника heyosseus - статичний аналізатор саме для таких проблем супроводу, про який цього тижня написав Laravel News.
Він детермінований і працює локально: жодної моделі, API-ключа чи мережі. Той самий код завжди дає той самий звіт.
Що він перевіряє
За README у пакеті 34 правила, ще три перевірки, що порівнюють зміну з її базою, і п'ять опційних правил для архітектури, яку ви описуєте самі. Основні групи:
- складність і дублювання -
SL101 God Method, SL102 God Class, SL104 Duplicate Logic, SL111 Copy-Paste Drift (майже копія, чия єдина відмінність схожа на недороблене редагування);
- мертвий код - невикористані приватні методи й залежності конструктора,
SL112 Placeholder Implementation: заглушка або throw new Exception('Not implemented') замість тіла;
- обробка помилок -
SL107 Swallowed Exception;
- Laravel - бізнес-логіка в контролері, валідація прямо в дії, прямі виклики зовнішніх API,
SL203 Possible N+1, SL204 Query Inside Loop, SL205 Collection Instead Of Query;
- маршрути й міграції (з версії 1.8.0) - маршрут до методу контролера, якого не існує, GET-маршрут, що видаляє, міграція, яку відредагували після запуску, міграція без
down();
- підозрілі тести -
SL503 Weakened Test: тест пропущено, з нього прибрали перевірки або додали assertTrue(true), щоб він пройшов.
Кожна знахідка має розташування, виміряні значення, рівень впевненості й пораду, що змінити.
Два правила на прикладі
Ось архівація, яка тихо ковтає будь-яку помилку:
try {
Storage::disk('s3')->put("transcripts/{$sessionId}.txt", $transcript);
} catch (\Throwable $exception) {
}
Це SL107. Sloppy підкаже, що обробки бракує, але не вирішить за вас, що робити зі збоєм: залогувати, повторити чи дати винятку піти далі. Це залежить від операції.
Друге правило ловить фільтрацію в PHP того, що мала б відфільтрувати база:
// SL205: завантажує всі записи, а потім фільтрує колекцію
$publishedCount = PodcastEpisode::all()
->where('status', 'published')
->count();
// так рахує база
$publishedCount = PodcastEpisode::query()
->where('status', 'published')
->count();
Як підключити до агента
composer require --dev heyosseus/sloppy
vendor/bin/sloppy # у Laravel: php artisan sloppy
vendor/bin/sloppy agents install # --host=gemini, --host=codex
Потрібен PHP 8.3 або новіший, а правила для Laravel вмикаються на Laravel 12 і 13. Конфігураційний файл не обов'язковий: каталоги з кодом Sloppy бере з composer.json.
agents install дописує правила проєкту в CLAUDE.md і додає два хуки в .claude/settings.json. Після кожного Edit чи Write PHP-файлу агент отримує знахідки, які внесло саме це редагування, порівняно з HEAD. Коли агент намагається завершити задачу, фінальний хук повертає його виправляти нові знахідки від заданого порогу. Знахідки, які файл мав і раніше, агентові не показують, щоб той не кидався «виправляти» код, якого його не просили чіпати. З --local хуки й правила йдуть в особисті .claude/settings.local.json і CLAUDE.local.md. Для агентів, які мають перевіряти код на вимогу, є MCP-сервер vendor/bin/sloppy-mcp.
На старому проєкті - лише нове
Перший запуск на кодовій базі з історією знайде сотні дрібниць. Щоб вони не змішувалися з новими, є два режими відносно спільного предка гілки з main:
vendor/bin/sloppy diff main --merge-base
vendor/bin/sloppy review main --merge-base
diff показує лише те, що внесла гілка, review ще й сортує знахідки за ризиком, щоб було видно, який файл читати першим. Незакомічені файли теж враховуються, а знахідки прив'язані до відбитків, а не до номерів рядків, тож доданий use не робить стару проблему новою. sloppy baseline фіксує поточний борг, щоб збірку валили тільки нові знахідки. Механічні виправлення sloppy:fix --dry-run показує наперед і передає Rector, а потім форматує Pint. Обидва треба встановити в проєкт окремо.
Де межі
- Це не детектор AI. Автор прямо пише, що за кодом не можна надійно визначити, хто його написав, і Sloppy не намагається.
Confidence: 88% означає впевненість у тому, що шаблон справді є, а не ймовірність, що код згенерувала модель.
- Хуки - зворотний зв'язок, а не гейт. Фінальна перевірка блокує агента лише один раз: друга спроба завершити проходить, а невиправлене лишається в транскрипті. Якщо Sloppy не може запуститися, агент просто працює далі. Щоб нові знахідки валили збірку, потрібен
sloppy ci, GitHub Action або Pest-перевірка expectCleanSloppyDiff('main').
- Евристики помиляються. God Method, Duplicate Logic чи Narrative Comment - це оцінки, а не доведення. Автор пише, що правила налаштовані на тишу і що звичайний Laravel-код за тестами не дає жодної знахідки, але хибні спрацьовування він сам просить звітувати як баги.
- Проєкт молодий. Між 1.2.0 (28 вересня) і 1.8.0 (21 жовтня) минуло менше місяця, а у 1.8.0 змінилися типові налаштування: міграції більше не виключені за замовчуванням, хоча раніше опублікований
config/sloppy.php досі їх виключає.
Чи замінить він PHPStan
Ні, і сам не претендує. PHPStan і Psalm відповідають на питання, чи коректні типи, а Sloppy - чи схожий код на той, про який згодом пошкодують. README обіцяє, що де PHPStan може щось довести, Sloppy туди не лізе, а для тих, хто вже живе з PHPStan, є розширення heyosseus/phpstan-sloppy: ті самі знахідки як помилки PHPStan з ідентифікаторами на кшталт sloppy.SL107. Але частина правил перетинається зі знайомими інструментами: невикористані приватні методи й завжди істинні умови PHPStan частково ловить і сам, а N+1 у розробці добре видно через Model::preventLazyLoading(). Нове тут - насамперед проковтнуті винятки, заглушки, копії з дрібною відмінністю, ослаблені тести та міграції, відредаговані після запуску.
Якщо агенти пишуть у вас помітну частку коду, Sloppy варто спробувати: на окремій гілці запустіть sloppy review main --merge-base, подивіться, скільки знахідок справді корисні, і лише потім вмикайте хуки. А збірку довіряйте CI, не хукам.