Пишем Java-скрипт, который собирает проект в один файл для контекста в чат DeepSeek или другие LLM

от автора

У нейросетей есть ограничение на количество символов в чате или на число запросов. И бывает так, что лимит уже закончился, а разработка проекта — нет.

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

Отдельная проблема — DeepSeek не всегда понимает ссылки на репозиторий и не смотрит код по ним так, как хотелось бы. Зато если дать ему сам контекст кода текстом, он включает его в анализ.

Идея

Пишем скрипт, который не надо компилировать, а сразу можно выполнить. Нужна java не ниже 11 версии. Идея в том что мы в один файл собираем весь контекст который нужен для анализа.

Вместо ручного копирования десятков файлов получается одна операция.

java ScanProject.java

Я обычно кладу данный файлик в корень папки с проектами и вызываю уже в самом проекте примерно так:

# из папки проектаjava ../../ScanProject.java

Или так:

# из корня папки с проектамиjava ScanProject.java .\java\scanner-profile\ result.txt
# Можно и для конкретной папки проекта, если нужен не весь код, а только часть.java ScanProject.java .\java\scanner-profile\src\main\java\ru\mcs\scanner\profile\domain\model result.txt

Вам нужно лишь создать файл ScanProject.java и поместить следующий код:

import java.io.*;import java.nio.file.*;import java.nio.file.attribute.BasicFileAttributes;import java.util.ArrayList;import java.util.List;public class ScanProject {    private static final List<String> INCLUDED_EXTENSIONS = List.of(        ".java", ".gradle", ".kt", ".kts", ".xml", ".yml", ".yaml",         ".properties", ".json", ".md", ".txt"    );        private static final List<String> EXCLUDED_DIRS = List.of(        ".git", ".gradle", "build", "target", "out", "bin", ".idea", "node_modules"    );        public static void main(String[] args) {        String projectRoot = args.length > 0 ? args[0] : ".";        String outputFile = args.length > 1 ? args[1] : "project_code.txt";                try {            scanProject(projectRoot, outputFile);            System.out.println("Project scanned successfully! Output: " + outputFile);        } catch (IOException e) {            System.err.println("Error scanning project: " + e.getMessage());            e.printStackTrace();        }    }        private static void scanProject(String rootPath, String outputFile) throws IOException {        Path root = Paths.get(rootPath).toAbsolutePath();        List<Path> files = new ArrayList<>();                // Собираем все файлы        Files.walkFileTree(root, new SimpleFileVisitor<Path>() {            @Override            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) {                String dirName = dir.getFileName().toString();                if (EXCLUDED_DIRS.contains(dirName)) {                    return FileVisitResult.SKIP_SUBTREE;                }                return FileVisitResult.CONTINUE;            }                        @Override            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {                if (isIncludedFile(file)) {                    files.add(file);                }                return FileVisitResult.CONTINUE;            }        });                // Сортируем файлы для удобства чтения        files.sort((p1, p2) -> {            int depthCompare = Integer.compare(p1.getNameCount(), p2.getNameCount());            return depthCompare != 0 ? depthCompare : p1.compareTo(p2);        });                // Записываем в выходной файл        try (PrintWriter writer = new PrintWriter(new FileWriter(outputFile))) {            writer.println("PROJECT STRUCTURE:");            writer.println("==================");            for (Path file : files) {                writer.println(root.relativize(file));            }                        writer.println("\n\nSOURCE CODE:");            writer.println("============");                        for (Path file : files) {                writer.println("\n" + "=".repeat(80));                writer.println("FILE: " + root.relativize(file));                writer.println("=".repeat(80));                                try {                    List<String> lines = Files.readAllLines(file);                    for (String line : lines) {                        writer.println(line);                    }                } catch (IOException e) {                    writer.println("ERROR READING FILE: " + e.getMessage());                }            }        }    }        private static boolean isIncludedFile(Path file) {        String fileName = file.getFileName().toString();        return INCLUDED_EXTENSIONS.stream()                .anyMatch(ext -> fileName.toLowerCase().endsWith(ext));    }}

Скрипт я писал под свой стек, но его легко изменить так как вам надо. Можно добавить например:

  • параметр для указания расширения фалов, которые вам нужны для обсуждения

  • параметр только для вывода структуры проекта

  • параметры для указания маски для выбора толко определныых файлов

Я этого не делал потому, что данный скрипт нужен, чтобы начать обсуждение проекта, а не постоянно собирать запрос для нейросети.

Что делает скрипт

  • рекурсивно обходит проект;

  • может исключать не нужные директории вроде .gitnode_modulesbuildtarget; (строчки 13-15)

  • Можно указать расширения файлов которые вам нужны (строчки 8-10)

  • собирает структуру проекта; (строчки 64-66)

  • добавляет содержимое нужных файлов в один итоговый .txt; (строчки 71-84)

В result.txt или project_code.txt будет примерно текст такого содержания:

  • структура проекта

  • исходники кода

Весь текст нет смысла показывать там много букв, но контекст будет примерно таким:

PROJECT STRUCTURE:==================build.gradledocker-compose.ymlREADME.mdsettings.gradledata\stats-data.jsongradle\wrapper\gradle-wrapper.propertiessrc\main\resources\application.propertiessrc\main\resources\questions\questions_en.jsonsrc\main\resources\questions\questions_ru.jsonsrc\main\resources\questions\questions_zh.jsonsrc\main\resources\templates\index.htmlsrc\main\resources\templates\lencioni-test.htmlsrc\main\resources\templates\result.htmlsrc\main\resources\templates\sher-test.htmlsrc\main\resources\templates\stats.htmlsrc\main\resources\templates\welcome.html....SOURCE CODE:============================================================================================FILE: build.gradle================================================================================<source code>================================================================================FILE: docker-compose.yml================================================================================<source code>....================================================================================FILE: src\main\java\ru\mcs\scanner\profile\ScannerProfileApplication.java================================================================================<source code>================================================================================FILE: src\main\java\ru\mcs\scanner\profile\controller\TestController.java================================================================================<source code>================================================================================FILE: src\main\java\ru\mcs\scanner\profile\service\AiRecommendationService.java================================================================================<source code>....

Зачем это вообще нужно

Многие могут возразить: а зачем, если есть GitHub Copilot, Codex или Claude, которые видят весь проект?

Отвечаю:

  • Стоимость. Copilot и Claude с доступом к репозиторию — платные. DeepSeek бесплатный и при этом хорошо работает с кодом.

  • Независимость от IDE. Скрипт работает из терминала. Никаких плагинов, расширений, привязки к VS Code или IDEA.

  • Работа с любой моделью. Сегодня DeepSeek, завтра — любая другая модель в браузере или API. Один файл подходит для всех.

  • Приватность. Ты видишь точно, что ты отдаёшь. Не «модель проиндексировала репозиторий», а конкретный текстовый файл, который можно открыть и проверить.

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

Не идеальный инструмент, а просто маленькая утилита, которая экономит время в реальной работе.

P.S. Надеюсь, скрипт окажется полезен кому-то ещё.

ссылка на оригинал статьи https://habr.com/ru/articles/1040420/