Я много работаю с веб-проектами и параллельно постоянно использую нейросети. Довольно быстро стало ясно простое вещь: обсуждать с моделью реальный проект, перекидывая ей по одному файлу, бессмысленно. Ей нужен не фрагмент, а контекст — структура каталогов, реальные пути, живой код, а не абстрактные «примерчики».

Типичный сценарий выглядел так. Я отправляю модели один файл, получаю ответ, понимаю, что информация обрезана: нет конфига, нет шаблона, нет сервисного класса. Докидываю ещё один файл, потом третий, потом кусок верстки. В итоге я больше таскаю код туда-сюда и объясняю «как у нас всё устроено», чем решаю исходную задачу.

На этом фоне возник очень приземлённый запрос: один раз аккуратно «сфотографировать» проект в технически удобный формат, а дальше работать уже с этим снимком. Хранить его в репозитории рядом с кодом, прикладывать к задачам, загружать ассистентам GPT, подключать к своим RAG-сервисам. Так появился небольшой PHP-репозиторий scan2json.

Репозиторий лежит на GitHub: https://github.com/simai/scan2json. Его идея простая: в одну сторону он превращает проект в JSON/JSONL, а в другую — по JSONL может развернуть структуру папок и файлов в отдельную директорию. Дальше буду говорить, зачем мне это понадобилось, что именно я сделал и как этим можно пользоваться в реальной работе.

Почему «просто отправить пару файлов» не работает

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

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

Мне хотелось иметь именно артефакт. Файл или набор файлов, который существует независимо от IDE и конкретной машины. Его можно коммитить, версионировать, отдавать ассистентам GPT, использовать в своих сервисах. Внутри у него должен быть честный срез проекта: относительные пути, дерево каталогов и содержимое файлов. Не обязательно всего проекта — только той части, с которой я сейчас работаю. Но этот срез должен быть полным внутри себя: без «ой, забыл ещё вот этот файл».

Отсюда родилась формулировка: нужен скрипт, который из выбранной части проекта делает структурированный JSON, а при желании позволяет по этому JSON собрать отдельную копию. Всё остальное — детали реализации.

Что такое scan2json и из чего он состоит

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

  • scan.php — сканирует выбранную папку проекта и превращает её содержимое в JSONL, цельный JSON и JSON по частям;

  • restore.php — читает JSONL и по нему создаёт файлы и папки в отдельной директории.

Оба файла — обычные PHP-скрипты, которые я кладу под DOCUMENT_ROOT в нужном окружении. Они работают через браузер, защищены паролем и ни к чему не привязаны: им без разницы, это сайт, API-сервис или монолитное приложение.

Главным героем здесь является scan.php. Именно он решает основную задачу — сделать из живого проекта аккуратный JSON-снимок. restore.php появился чуть позже как естественное продолжение: когда стало понятно, что иногда хочется развернуть такой снимок обратно в отдельную песочницу.

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

Как устроен scan.php: выбор корня и фильтрация

Когда я открываю scan.php в браузере и ввожу пароль, первый вопрос, который нужно решить, — откуда вообще сканировать. Мне не всегда нужен весь проект целиком: иногда достаточно одного модуля, иногда только директории app или src, иногда — отдельной «рабочей» части большого монолита.

У меня есть два способа задать корневую папку. В простейшем варианте я просто указываю путь: корень сайта, конкретный каталог, модуль и так далее. Если структуру проекта я хорошо знаю, это самый быстрый путь.

Если путь вспоминать не хочется, удобнее воспользоваться встроенной навигацией. Интерфейс показывает текущую директорию, даёт возможность подняться на уровень выше и выбрать одну из подпапок. Я постепенно «проваливаюсь» туда, где лежит нужная область, и в какой-то момент просто останавливаюсь: вот отсюда и начинаем.

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

Под тонкую настройку я сделал в интерфейсе кнопку Choose items…. Логика простая: я задаю корень сканирования, нажимаю кнопку и вижу список всех элементов первого уровня внутри этой папки. Напротив каждого — чекбокс. По умолчанию всё включено. Дальше я пр��сто снимаю галочки с того, что не хочу видеть в итоговом JSON: отдельные каталоги, большие папки с файлами, единичные тяжёлые объекты. После применения выбора сканер обходит только отмеченные элементы верхнего уровня, а внутрь уже заходит автоматически.

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

Какие форматы сканер создаёт на выходе

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

  • JSONL — файл, где каждая строка соответствует одному файлу проекта и содержит поля file и content;

  • цельный JSON — один большой массив объектов {file, content};

  • разбитый JSON — тот же массив, но автоматически нарезанный на несколько файлов, если проект слишком большой для одного.

Базовым форматом для дальнейшей обработки у меня является JSONL. Он выглядит примерно так:

{"file":"local/components/app/orders/class.php","content":"..."}

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

Цельный JSON нужен там, где ожидают один валидный документ: ассистенты GPT, некоторые внешние утилиты, внутренние сервисы, которым проще работать с массивом. Для больших проектов одного такого файла может быть слишком много, и тогда на помощь приходит нарезка. Сканер ориентировочно считает объём данных и, когда он становится слишком большим, закрывает текущий файл и начинает следующий. Каждая запись {file, content} всегда попадает целиком в одну часть.

Все эти файлы складываются в директорию scan_tmp под DOCUMENT_ROOT. Там же на странице я вижу список того, что уже сформировано, могу скачать нужный вариант и посмотреть, сколько примерно «слов» получилось. Это не точный подсчёт токенов под конкретную модель, но хороший индикатор масштаба: сотни тысяч слов или десятки мегабайт — это уже сигнал подумать, как лучше нарезать данные для ассистента.

Кратко про restore.php: зачем он мне нужен

Изначально я делал инструмент только «в одну сторону» — чтобы получать JSON. Но довольно быстро стало понятно, что JSONL сам по себе — это уже почти автономный артефакт. Логично уметь по нему поднять отдельную копию проекта, пусть и не полную, а именно ту, которая была в срезе.

Под это я написал restore.php. Его задача минималистична: прочитать JSONL, который ранее сформировал scan.php, и для каждой строки с объектом вида {file, content} создать соответствующий файл в выбранной директории. Скрипт нормализует относительные пути, следит за тем, чтобы никакие .. не позволили выйти за пределы целевой папки, по мере необходимости создаёт недостающие каталоги.

Важно зафиксировать одно ограничение: restore.php не знает и не пытается знать, как проект выглядел «по-настоящему». Он восстанавливает только то, что попало в JSONL. Если при сканировании я исключил какие-то каталоги, их не будет и в развёрнутой копии. Если я сканировал не весь проект, а только один модуль, после восстановления у меня будет именно этот модуль, а не весь сайт.

На практике я использую restore.php реже, чем scan.php, но он несколько раз очень помог. Например, когда нужно было собрать из снимка отдельную песочницу для экспериментов, отдать срез проекта подрядчику в виде «вот вам JSONL и скрипт, сами развернёте» или выделить проблемный модуль в отдельный мини-проект, не выдёргивая его руками из большого репозитория.

Как я использую scan2json день за днём

Сейчас scan2json для меня — не разовая утилита, а часть обычного рабочего процесса. Сценарии при этом довольно разные.

Частая ситуация: мне нужно обсудить с ИИ доработку в существующем проекте. Я беру scan.php, кладу его под DOCUMENT_ROOT, настраиваю пароль и открываю в браузере. Дальше выбираю корень, откуда буду сканировать, через путь или навигацию, отрезаю лишнее через Choose items… и запускаю сканирование. На выходе получаю JSONL, который уже можно обрабатывать любыми своими инструментами.

Дальше поверх этого JSONL я строю контур для общения с моделью. Например, пишу небольшого агента, который по запросу пользователя отбирает файлы по маске пути, по имени класса или по ключевым словам, склеивает их содержимое в осмысленный контекст и только потом отправляет модели. В этом сценарии мы с ИИ обсуждаем не абстрактный «PHP-проект», а реально существующий код, полностью находящийся в данных.

Другой сценарий — ассистенты «по проекту» внутри GPT. Здесь я обычно беру цельный JSON или набор частей, создаю ассистента, загружаю эти файлы как знания и в инструкциях объясняю, что это конкретный проект: как устроены пути, где искать шаблоны, где бизнес-логику. После этого разработчик может прийти к ассистенту и задать прикладной вопрос: где реализована регистрация, какие классы отвечают за оплату, как правильно добавить новый статус сделки. Ассистент отвечает уже с опорой на реальные файлы.

Есть и сценарии исследования. Когда я разбираю чужой или legacy-проект, мне удобно сначала сделать срез через scan.php, а уже потом подключать ассистента или свои скрипты. Можно попросить модель описать архитектуру по каталогам, найти подозрительные места (копипаст, странные зависимости), накидать список технического долга. Можно, наоборот, использовать ��исто свои инструменты для анализа JSONL и только на итогах анализа звать ИИ.

Иногда бывает и обратная история: у меня уже есть старый JSONL, например, снимок конкретного релиза. Тогда я запускаю restore.php, разворачиваю срез в отдельную директорию и получаю «мини-проект» с той же структурой, но без всего, что было специально отрезано на этапе сканирования. В такой песочнице можно не бояться экспериментировать: переписывать код, подключать различные инструменты, давать доступ ассистентам — основную кодовую базу это никак не затрагивает.

Ограничения и нюансы

У инструмента есть несколько естественных ограничений и тонкостей.

Во-первых, scan2json работает по файловой системе честно и последовательно. Если в проекте много тяжёлых артефактов, медиа и бинарников, их лучше сразу исключать: либо через общие настройки, либо через Choose items…. Иначе JSON-файлы получатся слишком крупными и не очень пригодными для передачи ассистентам.

Во-вторых, restore.php не предназначен для резервного копирования. Он не восстанавливает «всё как было», он поднимает именно тот срез, который вы сами сформировали через scan.php. Для полноценной стратегии бэкапов нужны другие инструменты, а scan2json здесь может быть только вспомогательной частью — например, чтобы дополнительно хранить структурированные срезы кода.

В-третьих, к безопасности стоит относиться серьёзно. Скрипты живут под DOCUMENT_ROOT, запускаются из браузера и по этой причине должны быть защищены не только паролем в коде. Я всегда меняю пароль по умолчанию, при необходимости ограничиваю доступ по IP и предпочитаю держать инструмент либо в изолированной среде, либо на стендах, а не на открытом проде.

В-четвёртых, оценка объёма данных по словам в интерфейсе — это именно оценка, а не точный пересчёт токенов в конкретной модели. Её хватает, чтобы понять порядок величин и прикинуть, как лучше нарезать JSON на части, но если вы оптимизируете диалоги под жесткие лимиты, имеет смысл дополнительно проверять размеры уже на стороне совокупного пайплайна.

Всё остальное — детали использования и предпочтения. Кому-то удобнее всегда сканировать весь проект, кому-то — только отдельные модули. Кто-то использует JSONL только для своих внутренних анализаторов, кто-то строит поверх него полноценные ассистенты.

Для меня scan2json стал довольно простым, но полезным ответом на реальную задачу: перестать объяснять проект ИИ по кусочкам. Сейчас это два небольших скрипта, которые делают ровно то, что от них требуется: scan.php снимает структурированный снимок проекта в JSON/JSONL, а restore.php по этому снимку умеет поднять отдельную копию. Если вам близка сама идея, можно просто клонировать репозиторий, положить scan.php рядом с одним из своих проектов, снять первый срез и посмотреть, как такой формат ляжет на ваш процесс работы с ИИ.