Привет, на связи Лука.
Знаете, есть такая поговорка: "тише едешь – дальше будешь". Работая с LLM, я пришёл к выводу, что аккуратность и точность в подаче контекста – это один из самых важных ключиков к хорошему результату. Иначе получится как в другой поговорке - про дурака и стеклянный орган.
Чего греха таить – все мы пользуемся LLM в различных ситуациях. От генерации бойлерплейта до неожиданного, но изящного решения сложной логики. Ничего такого – очередной инструмент, которым можно, как молотком, забить гвоздь, а можно и... ну, вы поняли.
Но когда речь заходит о системе, в которой контекст содержится не в одном, не в двух, и даже не в десятке файлов – вопрос становится ребром. Просто скормить модели весь проект? Ну, можно, конечно. Модель, захлебнувшись в потоке зачастую ненужной информации, вряд ли выдаст что-то вменяемое. Она потратит драгоценные вычислительные ресурсы на анализ совершенно нерелевантных частей. Неэкологичненько.
А если как-то по-другому? Может, вручную отбирать файлы? Это мучительно долго, особенно если структура проекта ветвистая. К тому же, легко что-то упустить. Может, архивировать нужное и прикреплять архив? Тоже вариант, но не все LLM-интерфейсы это гладко переваривают, да и предварительная подготовка всё равно нужна.
Когда ты фулстек и горишь идеей быстренько поправить и контроллер, и репозиторий, и сервисный слой, и пару DTO, да ещё и на фронте композиты подправить под новый эндпоинт – вот тут-то и понимаешь, что существующие подходы к подаче контекста LLM напоминают попытку просунуть верблюда в игольное ушко.
Тогда у меня и родилась идея сборщика файлов, которая со временем, как снежный ком, начала обрастать новым функционалом.
Представляю вам projectson – утилиту на Go, которая собирает выбранные файлы вашего проекта в один аккуратный JSON-файл.
Почему именно JSON?
Да всё просто:
Компактность: JSON – это текстовый формат, но при должной обработке (о чём ниже) он может быть весьма лаконичным.
Удобство для LLM: Большинство современных LLM отлично понимают JSON. Его можно скормить модели как единый файл, что гораздо удобнее, чем вставлять километровые листинги кода в текстфилд, в результате чего весь интерфейс пыхтит и кряхтит в процессе рендера сего действа.
Структурированность: JSON сам по себе структурирован, что помогает модели лучше разделять и понимать отдельные фрагменты кода или текста.
Политика максимального сохранения контекстного окна
Одна из главных фишек – это стремление максимально эффективно использовать ограниченное контекстное окно LLM. Для этого предусмотрены:
Опции для вырезки "мусора": Можно настроить правила для удаления из файлов определённых участков кода или текста (например, комментарии, ненужные в контексте текущего вопроса блоки).
Удаление лишних пробелов и переносов строк (по умолчанию): Автоматически сжимаем содержимое файлов, удаляя множественные пробелы и лишние переносы строк, заменяя их на одиночные пробелы. Это сокращает объём текста без потери семантики (для большинства языков программирования - привет, python). В будущем планируется добавить возможность более тонкой настройки этого процесса.
Таким образом, мы не просто собираем файлы, а предварительно причёсываем их, чтобы LLM получила только самое важное.
Философия и формат конфигурации
Сборка JSON-файла конфигурируется простым YAML файлом, который описывает, что и как собирать.
-
root
(Корневой путь):Тип:
String
Обязательно: Да
Описание: Абсолютный путь к корневой директории вашего проекта.
-
Пример:
root: "/path/to/your/project" # На Windows: # root: "C:\\Users\\YourName\\Projects\\MyProject"
-
include
(Что включаем):Тип:
List of Strings
Обязательно: Нет (По умолчанию сканируется вся
root
директория, с учётомformats
иexclude_patterns
)-
Описание: Список файлов или директорий для включения. Пути указываются относительно
root
. Каждый элемент может определять путь и режим сбора:"path/to/item"
: Собирает и путь, и содержимое (режим по умолчаниюboth
). Еслиitem
– директория, сканируется рекурсивно."path/to/item:path"
: Собирает только путь."path/to/item:content"
: Собирает только содержимое."path/to/directory/*"
: Собирает файлы непосредственно внутриpath/to/directory
(нерекурсивно), режим по умолчаниюboth
."path/to/directory/*:mode"
: Аналогично предыдущему, но с указанным режимом (path
илиcontent
).
-
Пример:
include: - "src" # Включить всё из src/, рекурсивно, путь и контент - "README.md:content" # Только контент из README.md - "assets/*:path" # Только пути файлов из assets/ (нерекурсивно) - "docs/api.md" # Путь и контент docs/api.md
-
formats
(Форматы файлов):Тип:
List of Strings
Обязательно: Да
Описание: Список расширений файлов (без точки), которые нужно включать.
-
Пример:
formats: - "go" - "vue" - "ts" - "js" - "py" - "html"
-
output
(Выходной файл):Тип:
String
Обязательно: Да
Описание: Полный путь к выходному JSON-файлу.
-
Пример:
output: "project_data_output.json" # output: "/tmp/my_project_collection.json"
-
exclude_patterns
(Паттерны исключения по имени):Тип:
List of Strings
Обязательно: Нет
-
Описание: Список паттернов для исключения файлов или директорий. Применяются после правил
include
иformats
.Glob-паттерны: Стандартные (например,
node_modules
,.log
,dist/
). Метчатся по имени файла/директории или по относительному пути.Регулярные выражения: Go-совместимые, должны быть заключены в слеши (например,
/\.git/
,/private_.*\.key$/
).
-
Пример:
exclude_patterns: - "node_modules" - ".git" - "*.min.js" - "dist" - "/test_data/" # Regex для исключения папок с именем test_data - "/^\\.(svn|hg|DS_Store)/" # Regex для служебных файлов
-
content_exclusions
(Исключения содержимого из файлов):Тип:
List of Objects
Обязательно: Нет
-
Описание: Список правил для удаления определённых секций внутри файлов перед их добавлением в JSON. Это главное отличие от
exclude_patterns
, которые работают с именами файлов/папок.type
(String, Обязательно):delimiters
(по начальному и конечному тегам) илиregexp
(по регулярному выражению).file_pattern
(String, Обязательно): Glob-паттерн, на какие файлы (по расширению или имени файла, если оно уникально как расширение) применять правило.*
– для всех файлов, подходящих подformats
. Метчится с расширениями (vue
) или расширениями с точкой (.vue
).start
(String, Опционально): Дляtype: "delimiters"
. Начальный тег.end
(String, Опционально): Дляtype: "delimiters"
. Конечный тег.pattern
(String, Опционально): Дляtype: "regexp"
. Регулярное выражение.
-
Пример:
content_exclusions: - type: "delimiters" file_pattern: "*.vue" # или просто "vue" start: "<style>" end: "</style>" - type: "regexp" file_pattern: "*.js" # или "js" pattern: "(//.*)|(/\\*[\\s\\S]*?\\*/)" # Удалить JS комментарии - type: "regexp" file_pattern: "*" # Для всех типов файлов pattern: "SECRET_API_KEY = '.*?'" # Удалить строку с секретным ключом
Пример конфигурационного файла projectson_config.yaml:
root: "/Users/username/goprojects/my-awesome-app"
include:
- "cmd"
- "internal/services"
- "internal/models:content" # Только контент моделей
- "pkg/*:path" # Только пути из pkg
- "README.md"
- "go.mod"
- "main.go"
formats:
- "go"
- "md"
- "mod"
output: "./collected_project_context.json"
exclude_patterns:
- "internal/services/legacy_service.go" # Исключить конкретный файл
- "/.*_test\\.go$/" # Исключить все тестовые файлы (regex)
- "vendor"
- "*.tmp"
content_exclusions:
- type: "delimiters"
file_pattern: "go" # Применить ко всем .go файлам
start: "// @exclude-start"
end: "// @exclude-end"
- type: "regexp"
file_pattern: "md" # Применить ко всем .md файлам
pattern: "[\\s\\S]*?"
Не YAML единым: графический интерфейс
Config: Здесь вы можете указать основные параметры:
root
,formats
,output
, а также управлять спискомinclude
(добавлять, удалять, изменять пути и режимы) иexclude_patterns
.

Exclusions: Эта вкладка посвящена правилам
content_exclusions
. Вы можете добавлять новые правила, выбирать их тип (delimiters
илиregexp
), указыватьfile_pattern
и соответствующие параметры (start
/end
илиpattern
). Каждое правило наглядно представлено, и его легко отредактировать или удалить.

Config Docs: Чтобы не держать всю структуру YAML в голове, на этой вкладке вы найдете подробную документацию по всем полям конфигурационного файла. Очень удобно, когда нужно быстро освежить назначение параметра.

Preview: Прежде чем запускать полную сборку, вы можете посмотреть, какие именно файлы будут обработаны согласно текущей конфигурации. На этой вкладке отображается список файлов. Выбрав файл, можно увидеть его оригинальное содержимое и то, как оно будет выглядеть после применения правил
content_exclusions
. Это помогает отладить правила исключений и убедиться, что в итоговый JSON попадет только нужное.

Run: Сборка JSON. Здесь вы видите краткую сводку текущей конфигурации и кнопку для запуска. После завершения можно сохранить результат.

Stats: После каждого запуска (или успешного Preview) здесь собирается статистика: количество обработанных файлов, общий размер выходных данных, время выполнения. Также отображается распределение файлов по форматам, режимам сбора и размерам (на основе данных из последнего Preview), и активные правила
content_exclusions
.

Что дальше?
Пилите YAML-конфиги и скармливайте результат нейросети. Сохраняйте и переиспользуйте свои конфиги.
Aistudio отлично читает такие JSON'ы. Копировать код пока приходится ручками - но есть задумки на получение от LLM ответа в виде JSON-патча.
Приглашаю вас на GitHub проекта. Предложения приветствуются.
Комментарии (7)
zartdinov
14.05.2025 10:33Зависит от модели, для лучшего результата советуют свои форматы:
Claude - XML
ChatGPT - Markdown
Gemini - любой вроде
worldown
14.05.2025 10:33Подскажите. Правильно ли я понял суть. Структурно упаковать проект для того чтобы скормить его LLM. Что это дает? Чем такой подход лучше, нежели использования copilot llm в IDE?
looqey Автор
14.05.2025 10:33Не использовал копилот, не знаю, как он питается контекстом проекта. Но вот курсор мне совсем не понравился (не говоря о том, что я слишком прирос к IDEA), он не есть контекст нормально, сколько его ни тыкай, а на тыканье уходит время.
К тому же, aistudio меня более чем устраивает по качеству ответов и удобству испольжования, а оплачивать API по ряду причин я не готов.
В общем, мне проще целенаправленно собрать контекст, по которому я хочу задать вопрос, вырезав весь не нужный мне в данный момент код. Это сработало эффективней всего :)
siberianlaika
Repomap №... :) Для парсинга кода с отсечением лишнего в подобных проектах часто обращаются к treesitter (см. например https://aider.chat/2023/10/22/repomap.html). На Go к нему есть бинды. Однако, утилит делающих repomap на Go + treesitter до сих пор не встретил. Сам использую https://github.com/wyattcupp/codesnap, там тоже без treesitter. Жду, кто бы прикрутил это вместе :)
zartdinov
Уже не первый пост встречаю запаковки кода, а вы не пробовали https://repomix.com?