Разработчики, которые пихают LLM по одному файлу из проекта, похожи на врачей, пробующих лечить слона по кусочкам. Модель видит только локальный контекст — и начинает галлюцинировать, хоть у неё консьерж-класса контекстное окно. Я перепробовал все: ручное копирование, cat *.java, скрипты на Python. Но только когда написал на Java утилиту, которая склеивает весь проект в один файл-монстр, DeepSeek действительно начал понимать, о чём проект.
Идея проста: собрать все исходники, конфиги, README, Git-логи в один текстовый файл с чёткой структурой путей. LLM видит архитектуру целиком — догадки о том, что такое UserRepository и как оно связано с AuthFilter, превращаются в точные ответы.
Почему с LLM не работал обычный «cat»?
Классика: find . -name '*.java' -exec cat {} \; > bundle.txt. Получаем кашу из package, без заголовков, без структуры. Модель тратит 30% контекстного окна на разделение файлов. А если проектов десятки — контекст деградирует быстрее, чем кофе стынет. В статье про три рабочих способа борьбы с галлюцинациями показано, что структурированный контекст снижает ошибки модели на 40%.
Как работает скрипт
Утилита написана на чистой Java без зависимостей — java ProjectExporter.java /path/to/project output/result.md. Она рекурсивно обходит директории, собирая файлы с заданными расширениями, и генерирует Markdown-файл:
- Структура проекта — дерево каталогов, только важное (исключает
target,node_modules,.git). - Каждый файл — в блоке кода с указанием полного пути, например
src/main/java/com/example/service/AuthService.java. - Настройка — в начале скрипта массив
INCLUDESиEXCLUDES. - Статистика — количество файлов, строк, общий размер.
private static void writeFileContent(BufferedWriter bw, Path file, Path root) throws IOException {
bw.write("### " + root.relativize(file).toString() + "\n\n");
bw.write("```java\n");
bw.write(Files.readString(file));
bw.write("\n```\n\n");
}
Важно: скрипт не очищает чувствительные данные. Перед отправкой в LLM обязательно проверьте, не попали ли в бандл пароли, ключи API или приватные SSH-ключи. Лучше добавить EXCLUDES с application-secret.properties.
Живой пример: рефакторинг Feature Flags
Недавно я мигрировал систему фиче-флагов в проекте на Java 21. Старый код был размазан по 30 файлам. Вместо того чтобы объяснять DeepSeek архитектуру десятью разрозненными промптами, я скормил ему бандл. Модель сразу выявила дублирующиеся проверки и предложила использовать общий FeatureFlagFilter. Этот кейс подробно разобран в гайде по рефакторингу Feature Flags — разница в скорости между подачей по файлам и цельным контекстом составила 3×.
Сравнение с альтернативами
| Инструмент | Структура вывода | Игнор-файлы | Размер токенов (1000 файлов) | Простота настройки |
|---|---|---|---|---|
| ProjectExporter (Java) | Markdown + tree + пути | Да, массивы INCLUDE/EXCLUDE | ~350K (с путями) | Минимальная (batch-файл) |
tree + find + cat |
Простой текст, нет разделителей | Только руками | ~320K | Средняя (пишется каждый раз) |
| Repo2txt (Python) | Markdown, пути | Да, но свой формат | ~360K | Требует Python |
| Copy as Markdown (IDE-плагин) | Только выделенные файлы | Нет | Зависит от выбора | Ручная работа |
Главное преимущество Java-скрипта — его можно встроить прямо в CI/CD или в IDE как внешний инструмент. Никаких зависимостей, работает на любом JDK 17+. В отличие от Python-скрипта, не нужно возиться с виртуальным окружением. Для тех, кто работает с локальными LLM, это особенно важно — как в реальных кейсах локальных LLM, где каждый дополнительный слой зависимостей может сломать пайплайн.
Под капотом: как справиться с миллионным контекстом
DeepSeek V4 обещает до 1M токенов контекста. На практике, если склеить весь корпоративный проект на Java с кучей либ — это легко 500K токенов. Без структуры модель теряет связность уже на 100K. В статье про архитектуру KV cache показано, что миллионный контекст возможен только при правильной упаковке данных. Наш скрипт как раз этим и занимается: превращает кучу файлов в линейную, плотную историю.
Кому это спасёт жизнь
- Разработчикам, практикующим «вайб-кодинг» — когда вы просто бросаете в чат весь проект и просите починить баг. Без контекста LLM начинает деградировать, а с бандлом проблема решается за один промпт. Подробнее — в руководстве по управлению контекстом.
- Инженерам, которые проводят код-ревью — можно скормить весь PR-модуль DeepSeek и получить моментальный анализ.
- Авторам технической документации — сгенерировать бандл с JavaDoc и описанием архитектуры для LLM, чтобы модель могла написать мануал.
- Тем, кто проверяет, не пора ли делегировать LLM часть пайплайна. Есть ситуации, когда LLM не стоит пускать в продакшн — чек-лист от инженера даёт критерии. Но подготовка контекста — никогда не вредна.
Как НЕ надо делать: типичные ошибки
Ошибка 1: включить в бандл весь vendor/ или node_modules/. LLM утонет в чужих библиотеках. Настройте EXCLUDES на /target/, /build/, *.jar, *.class. Если проект на Maven/Gradle — исключите ~/.m2 и .gradle.
Ошибка 2: не добавлять маркер «конец контекста». В начале сгенерированного файла вставьте блок с задачей: /* PROJECT OVERVIEW: микросервис для работы с заказами, использует Spring WebFlux, Kafka. Запрос: ... */. Так вы явно задаёте фокус, а не надеетесь, что модель угадает.
Ошибка 3: использовать UTF-16 или другие кодировки. LLM ожидает UTF-8. В скрипте явно укажите StandardCharsets.UTF_8.
Куда двигаться дальше
Сейчас я экспериментирую с добавлением в скрипт семантического сжатия — выкидывать из методов тела, но оставлять сигнатуры и комментарии. Для DeepSeek с тестируемой поддержкой 1M токенов это может быть избыточно, но для Claude или GPT-5, где контекст поменьше, экономия токенов критична. Если интересно — напишите в комментариях, выложу модифицированную версию.
*.project-context.md в .gitignore. Генерируйте бандл по требованию, а если нужна автоматизация — вешайте на git-хук.