Можно ли поймать вайб-кодера без нейросети? Сделал расширение для GitHub — рассказываю
- суббота, 6 июня 2026 г. в 00:00:08
Пару месяцев назад я опубликовал на GitHub небольшой проект — Timetable-bot, бот-расписание для нашего направления в ВГУ. Студентам нашим было неудобно листать Excel с расписанием, и я вместе с одногруппником сделал данного бота. Целиком самостоятельно, от первой строчки до деплоя. Когда дошёл до публикации в открытый доступ, лень было писать README, и я попросил Claude сгенерировать его по содержимому репозитория.
И вот пару дней назад я доделал свой собственный проект — Chrome-расширение, которое оценивает, насколько GitHub-репозиторий выглядит сгенерированным нейросетью. Естественно, первым же делом я прогнал его на своих репозиториях, чтобы проверить, что не сломал ничего глупого.
Расширение пометило Timetable-bot как AI-репозиторий. Детектор уверенно показал бейдж «AI».
Это и стало главным моментом, который заставил меня переписать всю архитектуру оценки. Потому что число «насколько похоже на AI» — это не то, что нужно знать пользователю. Пользователю нужно знать кто писал код. А это совсем другой вопрос.
В этой статье — как я к этому пришёл, что в итоге работает, что я выкинул на полпути, и почему в 2026 году эвристический детектор без единой нейросети — это вообще не парадокс, а логичный инструмент. Расширение на текущий момент выпущено в Chrome Web Store, исходники на GitHub под MIT. Форкайте, добавляйте свои правила.

Прежде чем рассказать про сам детектор, важно объяснить, зачем он вообще нужен. Иначе это выглядит как личная игрушка.
К моменту, когда я начал делать расширение, ситуация в индустрии открытого исходного кода выглядела так. Все ниже — реальные политики реальных проектов 2024-2026 годов:
Gentoo Linux — полный запрет на AI-сгенерированный код с 2024 года.
QEMU — формально принял политику отказа от AI-кода. Основная причина: такой код не удовлетворяет требованиям Developer Certificate of Origin.
GNOME extension hosting service — запрет с декабря 2025 года. Причина: проблемы с качеством и невозможность разработчика объяснить, как работает его собственный код.
AerynOS — запрет с января 2026 года. Этический аргумент: training data моделей, на которых обучен Copilot, содержит код под несовместимыми лицензиями.
Flathub — ввёл полный запрет 29 мая 2026 года. Покрывает весь спектр: код, метаданные, build-скрипты, pull request’ы. Permanent ban для повторных нарушений.
GNOME Circle — 30 мая 2026 года полностью приостановил приём новых заявок, ссылаясь на завал из AI-сгенерированных проектов, которые мейнтейнеры не успевают рецензировать.
Rust Foundation — в 03.06.2026 года открыли pull request с черновиком LLM Usage Policy для основного репозитория rust-lang/rust. Не полный запрет, но жёсткие границы: запрещены LLM-сгенерированные комментарии, тела issue, описания pull request’ов от личных GitHub-аккаунтов; запрещена документация, сгенерированная LLM, включая doc-комментарии, safety-комментарии и сообщения компилятора.
Параллельно — данные. Black Duck в отчёте Open Source Security and Risk Analysis 2026 пишет, что 65% опрошенных организаций столкнулись с атакой на цепочку поставок в 2025 году, а 68% проаудированных кодовых баз содержали лицензионные конфликты — крупнейший рост год к году за всю историю отчёта. Часть этого роста авторы прямо приписывают «отмыванию лицензий»: AI-помощники генерируют код, производный от copyleft-источников типа GPL, не сохраняя при этом лицензионные условия.
То есть запрет AI-кода в open source — это не луддизм. Это реакция мейнтейнеров на конкретные проблемы: невозможность ревью, лицензионная грязь, низкое качество, поток слабых pull request’ов.
И в этой обстановке у мейнтейнера, ревьюера или просто прохожего разработчика нет инструмента, чтобы быстро понять — а это репо вообще человек писал, или это очередной vibe-coded slop?
Триггер для моего проекта пришёл из соседней индустрии. Я увидел в Telegram пост про политику Rust Foundation, и подумал: если сообщество всерьёз начинает обозначать границы AI-кода, должен быть инструмент, который помогает их видеть на стороне читателя. Не SaaS за деньги, не нейросеть, которая сама может быть обманута другой нейросетью, а простой и прозрачный эвристический сигнал прямо в браузере.
Существующие детекторы AI-кода, которые я перебирал перед тем как сесть писать своё:
isvibecoded.com — закрытый SaaS, оценивает по непрозрачным правилам. Не подходит для GitHub-маршрута «открыл репо — увидел сигнал».
ai-gen-code-search — нужно запускать локально, требует ручной настройки, для разового любопытства слишком много трения.
Решения на ML — детекторы, использующие классификатор поверх эмбеддингов кода. Концептуальная проблема: детектор AI на AI в 2026 году — это позиция, которая сама подвергает себя критике. Если завтра выйдет более сильная LLM, чем та, на которой натренирован классификатор, классификатор начнёт ошибаться. Это гонка вооружений с заведомо проигрышной стороной.
Я хотел другого. Инструмент должен быть:
В браузере, без серверной части
Без LLM — детерминированные правила, читаемые объяснения каждого сработавшего сигнала
С открытым кодом, MIT, форкабельный
С прозрачным разбором — пользователь видит не только число, но и почему именно оно такое
Расширение для Chrome удовлетворяет всем четырём пунктам. Manifest V3, content script на странице репозитория, service worker для запросов к GitHub API, никакого бэкенда вообще.
После недели чтения чужого кода, разглядывания подозрительных репозиториев и собственного опыта работы с Claude Code и Cursor у меня в голове сложилась карта сигналов, по которым можно отличить AI-репозиторий от человеческого. Сигналы группируются в четыре категории.
Самый видимый слой. LLM пишут README со специфической эстетикой, которая выдаёт их быстрее всего:
Эмодзи в начале заголовков. Шаблон ## 🚀 Getting Started, ## ✨ Features, ## 📦 Installation. Если в README пять и более таких заголовков — это очень сильный сигнал.
Плотность LLM-фраз. Словарь из примерно сотни характерных конструкций на английском и русском: «production-ready», «blazingly fast», «battle-tested», «comprehensive solution», «seamless integration», «современное решение», «гибкая архитектура», «удобный интерфейс». Считаем долю таких фраз на тысячу слов.
Плотность длинного тире (—). Сами по себе тире — не сигнал, но AI ставит их в десять раз чаще среднего разработчика. Высокая плотность — индикатор.
Идеальная структура. Installation + Usage + Features + Contributing + License + API в строгом порядке. У реальных людей структура хаотичная, потому что они дописывают по мере необходимости.
Развёрнутое оглавление с якорными ссылками. В небольшом README на одну экранную страницу. Это сделано не для удобства, а для «солидности».
Таблицы фич с галочками и крестиками в духе «у нас ✅, у конкурентов ❌».
Гиперболическое вступление. Слова «революционный», «передовой», «лучший в индустрии» в первом абзаце.
Пять и более переводов README на разные языки (README.zh.md, README.ru.md, README.ja.md и так далее). Это подпись AI-агента — модель легко переводит README на десяток языков за минуту, человек никогда не будет тратить на это время для пет-проекта.
Избыточные shields-бейджи. Шесть и более бейджей в шапке: build status, version, license, downloads, stars, coverage, и так далее. У человеческих проектов их обычно один-два.
Развёрнутый ASCII project-tree прямо в README, особенно с эмодзи-иконками для папок.
Множественные collapsible-блоки — пять и более <details><summary> для дополнительной информации.
Это самый сильный слой из всех. AI-агенты оставляют в коммитах вполне детерминированные следы, которые сами по себе почти однозначно выдают их участие.
Co-Authored-By: Claude. Самый частый сигнал. Claude Code по умолчанию добавляет в каждый коммит Co-Authored-By: Claude <noreply@anthropic.com>. Если доля таких коммитов больше 50% — это уверенный AI-репозиторий. Аналогично — Co-Authored-By: Cursor, Devin, OpenHands, Aider, Codex.
🤖 Generated with Claude Code. Маркер, который Claude Code вставляет в тело коммита в дополнение к Co-Authored-By. Иногда — единственный след.
Сигнатура Cursor. cursor: generated или cursor: edited в начале commit-message.
AI-автор коммита. Поле commit.author или commit.committer совпадает с claude, cursor, aider, [bot], github-copilot, openhands.
Vibe-coded репо: 1-5 коммитов на проект с заметным объёмом кода. То есть человек один раз нажал на кнопку «commit all», и больше в проект не возвращался. Сигнал умножается на размер кодовой базы — если в одном коммите 3000 строк, это уже не «маленький pet», это однозначно vibe-coded.
Commit burst — пять и более коммитов за сутки в начале жизни репозитория. Человек обычно делает паузы между коммитами; агент пушит всё одним залпом.
Один автор + молодой репозиторий + значительный размер. Если репозитории три дня от роду, в нём 5000 строк кода, и весь он от одного автора — это либо очень продуктивный senior, либо AI.
Идеальные conventional commits. Сто процентов сообщений вида feat:, fix:, chore:, docs:. Подозрительно — потому что реальные люди забывают, ленятся, делают опечатки.
Огромный начальный коммит. Больше двух тысяч строк в первом же коммите проекта. Признак того, что в репозитории прячут историю «было написано не здесь».
Слой метаданных репозитория:
Звёзды есть, issue и pull request’ов нет. Репо набрал внимание, но никто им не пользуется, чтобы пожаловаться или предложить улучшение.
Сирота-репозиторий. Нет description, нет topics, нет homepage. Человек обычно хоть какое-то описание выдавливает.
Маркетинговая речь в описании. «Revolutionary AI-powered solution for…» — описание явно писалось как продающая копия, не как «вот что я сделал».
Файловая структура репозитория тоже выдаёт:
Перегруженный скелет на новом репо. Файлы CODE_OF_CONDUCT.md, SECURITY.md, .github/dependabot.yml в репо, которому меньше двух недель от роду. Это означает: «AI-агенту сказали следовать лучшим практикам, и он добавил всё подряд, не задумываясь, нужно ли это для пет-проекта».
Высокое отношение документации к коду. README по объёму больше, чем сам код. Признак того, что в проект больше вложено в полировку фасада, чем в саму суть.
Закоммиченная dist/ или build/ папка вместе с src/ и package.json. Это типичный паттерн AI-сгенерированного npm-пакета: модель не понимает, что сборка идёт через CI, и коммитит артефакты сборки прямо в репозиторий.
Отполированный одноразовый SDK. Package manifest есть, LICENSE есть, tsconfig.json есть, prettier-конфиг есть, но при этом всего 5-8 коммитов и один автор. Это подпись AI-сгенерированного SDK для несуществующего сервиса.
Технически детектор устроен максимально просто. Это сознательное решение — никаких хитрых инструментов, никаких отдельных моделей под подзадачи. Простой и читаемый код, в который любой может прийти и добавить своё правило.
manifest.json Manifest V3, host_permissions: api.github.com src/ ├── content/ │ ├── content.js Парсит URL, инжектит бейдж, реагирует на pjax-навигацию GitHub │ └── badge.css Стили бейджа и панели (с поддержкой dark mode) ├── background/ │ └── service-worker.js Принимает ANALYZE_REPO, кэширует результат в session storage ├── lib/ │ ├── github-api.js Тонкая обёртка над GitHub REST + поддержка персонального токена │ ├── scorer.js Агрегатор: правила → категории → общий вердикт │ └── rules/ │ ├── readme-rules.js 11 правил для README │ ├── commit-rules.js 7 правил для коммитов │ ├── meta-rules.js 3 правила для метаданных │ └── file-rules.js 5 правил для файловой структуры └── popup/ └── popup.html/js/css Настройка персонального токена
Поток данных простой. Content script видит, что пользователь открыл страницу вида github.com/owner/repo — посылает в service worker сообщение ANALYZE_REPO. Service worker сначала смотрит chrome.storage.session — если результат для этого репо уже есть и моложе часа, возвращает мгновенно. Если нет — параллельно запрашивает у GitHub API пять эндпойнтов: /repos, /readme, /commits?per_page=30, /pulls, /contents. Дополнительно — /commits/{sha} для первого коммита, чтобы получить статистику по строкам. Собирает контекст, прогоняет через четыре категории правил, агрегирует. Результат возвращается обратно в content script, бейдж обновляется.
Анонимный лимит GitHub API — 60 запросов в час с IP-адреса. На каждый просмотр репозитория детектор тратит шесть запросов. То есть за час пользователь успевает посмотреть десять репозиториев, и расширение упирается в rate limit.
Решений два, оба применяются вместе:
Кэш в session storage. Любой результат живёт в кэше один час. Если пользователь открыл репозиторий, потом ушёл, потом вернулся — повторного запроса нет.
Опциональный персональный токен. В popup расширения можно вставить GitHub Personal Access Token (только на чтение, без прав на запись). С токеном лимит становится 5000 запросов в час — этого хватит на любой объём просмотров. Токен хранится в chrome.storage.local, никуда не отправляется.
Отдельная тонкость: GitHub использует pjax для перехода между страницами. Когда пользователь кликает на другой репозиторий, URL меняется, DOM обновляется, но событие DOMContentLoaded не срабатывает. Чтобы расширение реагировало на смену репозитория, content script слушает pushState/replaceState/popstate и события pjax:end/pjax:complete/turbo:load.
Без этого расширение работало бы только на «холодный» вход в URL, что для GitHub неприемлемо.

Это самая полезная часть для разработчиков. Не все правила, которые казались сначала очевидными, реально работают.
Идея: у каждого разработчика свой стиль письма кода — отступы, длина переменных, частота комментариев, паттерны использования тернарного оператора, склонность к стрелочным функциям против обычных. LLM пишут код в усреднённой манере, без явного индивидуального стиля.
Почему не сработало: стиль слишком зависит от языка. Что в Python стандарт PEP 8, в JavaScript — Airbnb style guide. Линтеры в большинстве серьёзных проектов выравнивают стиль до общего знаменателя. То есть «единообразный стиль» сам по себе ничего не значит — это норма для проектов с CI, не отличительная черта AI.
Дополнительно: стилометрия требует парсинга кода. Это медленно в браузере, требует языковых парсеров, и легко обманывается тривиальным prettier-проходом.
Идея: AI-сгенерированный код проходит линтер без единой ошибки, потому что модель тренировалась на линтованном коде. У человека всегда найдётся хоть один console.log, который он забыл убрать.
Почему не сработало: слишком хорошие разработчики тоже линтуют свой код. Эта эвристика била по senior-разработчикам с дисциплиной — то есть давала ложное срабатывание на самой ценной части аудитории. Я её убрал в первый же день после прогона на тестовом наборе.
Идея: AI-код не оставляет console.log или print для отладки, потому что модели генерируют «чистовик». Человек оставляет следы отладки.
Почему не сработало: количество console.log зависит от стадии проекта, а не от автора. На любом репо, прошедшем стадию ревью, лога нет. На любом активно разрабатываемом — есть. AI vs человек тут вообще не при чём.
Идея: если 100% коммитов следуют конвенции type: description, это AI. У реальных людей всегда есть один wip или fix typo.
Почему частично не сработало: правило в коде осталось, но его вес я снизил почти до нуля. Причина: команды, использующие commit-lint в pre-commit hook, тоже выдают идеальные conventional commits. То есть правило ловило дисциплинированные команды наравне с AI-агентами. В коде оно есть, но в profile-классификации сознательно исключено из «сильных commit-сигналов» — оно срабатывает на честных human-репозиториях слишком часто.
Идея: AI-код имеет аномально низкую среднюю цикломатическую сложность функций, потому что модели любят разбивать всё на маленькие куски.
Почему не сработало: для этого нужен парсинг кода. Точная цикломатическая сложность требует AST. В браузере это нереально для произвольного языка. Я убрал идею до того, как начал её реализовывать.
Идея: проверять, что коммиты сделаны в разумные рабочие часы. AI-агенты могут пушить в 3 утра. Если все коммиты репозитория в 2-5 утра по местному времени — подозрительно.
Почему не сработало: разработчики со всего мира работают в разное время. Что для одного «странное время», для другого — рабочий день. Слишком много культурных предпосылок.
Возвращаюсь к истории про Timetable-bot, с которой начал статью.
Я прогнал детектор на собственном репозитории. Число — около 70 из 100. Бейдж красный. Вердикт: «AI-репозиторий». При этом весь код в Timetable-bot писал я сам, вручную, две недели вечерами. Единственное, что было сгенерировано — README, который я попросил Cursor сделать после.
То есть детектор работал технически правильно: он видел признаки AI в README (эмодзи в заголовках, идеальная структура, маркетинговый тон) и закономерно отдавал высокий score. Но вердикт для пользователя был неправильным. Пользователь видит «AI-репозиторий», думает «значит, тут код от модели». На самом деле там код от человека и AI README.
Числовая агрегация сглаживает разницу между категориями. Сильный сигнал в одной категории тонет в нулях остальных, либо доминирует над ними и даёт неверную интерпретацию.
Я переписал scorer так, чтобы он выдавал не только число, но и профиль — явную классификацию того, что именно AI-шное в репозитории.
Профиль | Условие срабатывания | Вердикт |
|---|---|---|
| Сильные сигналы и в коммитах, и в README/файлах | «AI код + AI документация» |
| Сильные сигналы в коммитах | «AI код» |
| README ≥ 55, нет сильных commit-сигналов | «AI README, код человека» |
| README ≥ 40 + files ≥ 50, нет сильных commit-сигналов | «AI-полировка, код человека» |
| Ничего из перечисленного | Вердикт только по числу |
«Сильный commit-сигнал» — это ai_commit_trailers >= 0.7, или vibe_coded_few_commits >= 0.7, или ai_committer >= 0.5, или massive_initial_commit >= 0.7, или polished_oneshot_sdk >= 0.85. Слабые сигналы вроде conventional_commits_perfection сознательно из этого списка исключены — у них слишком высокая доля ложных срабатываний.
Профильный вердикт идёт первым, числовой — справочной информацией. И — что важно — число подгоняется под профиль. Если профиль ai_code, число не может быть меньше 60. Если профиль ai_docs_only, число не может быть больше 45. Это сделано, чтобы цвет бейджа и вердикт не противоречили друг другу.
После переделки Timetable-bot получил профиль ai_docs_only и жёлтый бейдж с подписью «AI README, код человека». Это уже правильно.

Чтобы убедиться, что детектор не сломан, я собрал smoke-тест на синтетических контекстах, эмулирующих шесть реальных кейсов. Каждый кейс — это репозиторий, про который я точно знаю, что в нём AI, а что человек.
Кейс | Истинное положение | Ожидаемый профиль | Получено |
|---|---|---|---|
| AI |
|
|
| mixed |
|
|
| human |
|
|
Vibe-coded синтетика (2 коммита, AI README, devops-обвес) | AI |
|
|
| mixed |
|
|
Synapsea-style TS SDK (5 коммитов, dist+src, один автор) | AI |
|
|
Шесть из шести совпали с ожидаемыми вердиктами. Это не значит, что детектор идеальный — но базовая интуиция в правилах работает.
Полные тесты можно прогнать командой node test-smoke.mjs в корне репозитория.
Расширение на модерации в Chrome Web Store, исходники — на GitHub под лицензией MIT. Я планирую развивать проект в опен-сорсе и принимать pull request’ы с новыми правилами от сообщества.
В планах:
Правила по package.json/requirements.txt. LLM имеют характерные паттерны выбора версий зависимостей — например, всегда последние стабильные минорные версии, отсутствие caret/tilde-префиксов или их избыточность. Это отдельный класс сигналов, который я пока не покрыл.
Использование AI-related topics на репо как ground truth. Если репозиторий сам себя пометил тегом ai-generated, это можно использовать для тюнинга весов.
Экспорт результатов в JSON-формат. Чтобы публиковать как открытый датасет со скорами для trending repos. Полезно для исследовательских работ.
Опциональная отправка анонимных результатов в публичный пул. Это шаг к идее публично доступного индекса AI-сгенерированности для топовых открытых проектов. Если такой пул будет, его смогут использовать не только люди, но и AI-агенты с web search — то есть, когда другая модель ищет «хорошие примеры реализации X в open source», она сможет фильтровать по «писали ли это люди». Это, на мой взгляд, самая интересная развилка для проекта в долгосрочной перспективе.
Расширение я делал в первую очередь для себя — потому что мне самому нужен был быстрый способ оценивать чужие репозитории, не вчитываясь в каждую папку. Но логика говорит, что эта потребность не уникальна. Мейнтейнерам нужно фильтровать pull request’ы. Рекрутерам нужно отличать настоящий portfolio от «накодил за вечер с агентом». Исследователям open source нужны датасеты с разметкой по типу авторства.
Любое из этих применений возможно поверх MIT-кода в открытом репозитории. Форкайте, добавляйте свои эвристики, открывайте pull request’ы. Я приму всё, что не ломает существующие smoke-тесты.
Детектор AI без AI — это не парадокс. Это правильная инженерная позиция для 2026 года.
Аргументы против ML-детекторов: они проигрывают LLM, на которых их не обучали. Они закрытые. Они дороги в эксплуатации. Их вердикты непрозрачны. Их легко обмануть простой переформулировкой.
Аргументы за эвристический подход: он прозрачен. Каждое сработавшее правило имеет описание. Можно открыть код и понять, почему бейдж красный. Можно добавить своё правило. Можно отключить правило, которое тебя триггерит. Это инструмент, а не оракул.
И главное — сигналы, на которых работает эвристика, никуда не уходят, пока LLM остаются LLM. Co-Authored-By: Claude — это литеральный след, оставленный самим Claude Code. Чтобы от него избавиться, нужно специально его удалять. Vibe-coded репозиторий с двумя коммитами — это структурный артефакт рабочего процесса. Пять и более переводов README — это поведенческий паттерн модели. Всё это не размытые сигналы, они либо есть, либо нет, и никакой подстройкой промпта они не убираются (потому что нужно изменять сам процесс работы).
Поэтому я уверен — следующий год эта эвристика будет работать. Через два года — частично. Через три — придётся переписывать половину правил, потому что появятся новые рабочие процессы и новые маркеры. Это нормально. Open source проект как раз и нужен для того, чтобы такие правила добавлялись и убирались по мере эволюции индустрии.
И когда Rust Foundation, Flathub, GNOME, AerynOS, Gentoo и QEMU в следующий раз обновят свои политики против AI-кода — пусть будет хотя бы один открытый инструмент, который позволит эти политики видеть и проверять на стороне читателя.
P.S. Расширение совсем свежее, поэтому ещё может выдавать ошибки. Проект лишь на первом уровне развития, ближе к идее, чем к готовому продукту, так что прошу не судить строго, если будет выдавать ошибки :)