Монорепо, который заставил пересмотреть структуру проекта

Когда решил мигрировать Bot Social Publisher с одномонолитного хранилища на многопакетную архитектуру, предполагал, что главная сложность будет в коде. Глупо. На самом деле всё сломалось на границах между пакетами.
Проект уже был внушительным: 17 модулей, 29708 строк Python-кода, асинхронный pipeline обогащения контента через Claude API. По плану — разделить на отдельные пакеты (collectors, processing, enrichment, publisher), завести в Git, и жизнь станет проще. Реальность была иной.
Первый вечер потратил на структуру папок. Создал src/collectors/ для шести асинхронных коллекторов (Git, Clipboard, Cursor, Claude, VSCode, VS), отдельно src/processing/ для фильтрации и дедубликации, src/enrichment/ для работы с Wikipedia и Unsplash API, src/publisher/ для публикации в Website (Strapi), VK и Telegram. На доске выглядело идеально: каждый модуль отвечает за одно, зависимости текут в одну сторону, конфликтов быть не должно.
Но вот на практике выяснилось — некоторые модули обогащения (enrichment/wikipedia.py, enrichment/images.py, enrichment/jokes.py) были переплетены с основной логикой фильтрации. Когда я попытался их разделить, обнаружил, что ContentSelector из processing вызывает функции из enrichment, enrichment обращается к хранилищу в storage, а storage нуждается в конфигах из processing. Цикл.
Переписал на pydantic-модели. Ввел чётко определённые граница между слоями: RawEvent → ProcessedNote → EnrichedNote → PublishedNote. Каждый модуль теперь работает с конкретным типом данных, а не с дикими словарями. Нужно было всего два дня, чтобы из хаоса получилась читаемая архитектура.
Дальше пришла беда с Claude CLI. Максимум 100 запросов в день, 3 одновременных вызова, таймаут 60 секунд. На ноту может потребоваться до 6 LLM-запросов (русский контент, английский, титлы для обоих языков, вычитка). Быстро выяснилось, что генерировать оба языка отдельно — расточительно. Объединил: одна LLM-подсказка возвращает и контент, и заголовок для русского сразу. Количество обращений упало с 6 на 2-3 в день для одной ноты. Структура улучшилась, экономия вышла на порядок.
В конце дня 94 файла упали в Git-репозиторий. Лицензия AGPL-3.0, .gitignore отфильтровывает все кэши, .env.example показывает, какие переменные нужны новичку, документация в docs/ объясняет pipeline. Попытался push на gitlab.dev.borisovai.ru — DNS не разрешается, сервер недоступен. Коммит создал (хеш 4ef013c), когда-нибудь синхронизирую.
Любопытный факт: когда после обновления SQLite спрашиваешь его, как дела, база отвечает: «Я уже не то, что раньше». 😄
Метаданные
- Session ID:
- grouped_C--projects-bot-social-publisher_20260225_2133
- Branch:
- main
- Dev Joke
- Что SQLite сказал после обновления? «Я уже не тот, что раньше»