BorisovAI

Блог

Публикации о процессе разработки, решённых задачах и изученных технологиях

Новая функцияC--projects-ai-agents-voice-agent

Монорепозиторий и AI: как Claude стал напарником разработчика

# Когда AI-ассистент встречает монорепозиторий: история голосового агента Представьте: перед вами лежит амбициозный проект **voice-agent** — это монорепозиторий с Python-бэкендом и Next.js-фронтендом, где нужно связать воедино асинхронную обработку аудио, WebSocket-коммуникацию и сложную логику распознавания речи. И вот в этот момент включается Claude — не просто ассистент, а полноценный напарник по коду. ## Задача была жёсткой Когда разработчик впервые открыл Claude Code, проект уже имел чёткую архитектуру в `docs/tma/`, но требовал реализации множества деталей. Нужно было: - Писать и отлаживать код одновременно на Python, JavaScript и TypeScript - Ориентироваться в сложной структуре монорепозитория без «холодного старта» - Не просто добавлять функции, а понимать, *почему* каждое решение работает именно так Первым делом разработчик понял ключевую особенность работы с Claude Code в контексте таких проектов: AI-ассистент может видеть не только ваш текущий файл, но и архитектуру всего проекта. Это даёт огромное преимущество — вы не пишете код в вакууме. ## Развитие: между выбором и экспериментами Когда встал вопрос об обработке потока аудио в реальном времени, разработчик столкнулся с классической дилеммой: использовать опрос сокетов или event-driven архитектуру? Claude предложил использовать асинхронные генераторы Python вместе с aiohttp для non-blocking операций. Звучит сложно, но в реальности это означало, что сервер мог одновременно обрабатывать сотни клиентов без блокировки основного потока. Интересный момент: при рефакторинге компонента обработки текста выяснилось, что простая синхронная функция создавала скрытую очередь запросов. Пришлось переписать логику под асинхронность, и это одномоментно снизило задержку с 200 ms до 50 ms. Такие открытия — именно то, ради чего стоит привлекать опытного помощника. ## Познавательный момент: монорепозитории и их подводные камни Мало кто знает, но классическая ошибка при работе с Next.js в монорепозитории — неправильный поиск корневой директории проекта. Turbopack (встроенный в Next.js бандлер) может начать искать зависимости не в папке приложения, а в корне репозитория, вызывая каскадные ошибки с импортами. Правильное решение — явно указать `turbopack.root` в `next.config.ts` и настроить базовый путь в `postcss.config.mjs`. Это элементарно, но узнают об этом опытом... или благодаря опытному напарнику. ## Итог: что-то большое начинает работать За несколько сессий разработчик не просто писал код — он учился *думать* архитектурно. Claude помог не просто реализовать фичи, но и выбрать правильные инструменты для каждой задачи: aiosqlite для асинхронного доступа к данным, WebSocket для real-time коммуникации, TypeScript для типобезопасности фронтенда. Проект voice-agent теперь имеет солидный фундамент, и самое интересное — это только начало. Впереди оптимизация, масштабирование, новые фичи. Но главное, что разработчик понял: хороший AI-ассистент — это не замена опыту, а его ускоритель. Обычный коллега сказал бы: «Ну ты с AI-ассистентом кодишь?» А ты ответил бы: «Да, но это как работать с очень внимательным senior-разработчиком, который знает все паттерны и никогда не забывает про edge cases». 😄

#claude#ai#python#javascript
Разработка: Voice Agent
10 февр. 2026 г.
Новая функцияC--projects-bot-social-publisher

Привязал бота к Strapi: потоки, синхронизация и локальный маппер

# Как я связал бота и Strapi: история о потоках, тестах и синхронизации Задача стояла такая: bot-social-publisher — мой проект, который вытягивает заметки о разработке и публикует их на сайт borisovai.tech. Раньше каждая заметка была независимой статьёй в Strapi. Но это было скучно. Хотелось превратить разбросанные публикации в **потоки** — контейнеры, где все заметки одного проекта живут вместе, с общим описанием, категориями и тегами. Типа: "Поток разработки my-project: 5 заметок, последние фичи и баг-фиксы". Первым делом открыл backend на Node.js + Strapi. Там уже была база под API, но нужно было достроить. Добавил параметр `thread_external_id` в функцию `publishNote()` — теперь заметка знает, к какому потоку её привязать. Создал новый маршрут `PUT /api/v1/threads/:id` для обновления описания и тегов потока. И ещё важный момент: поток может быть **пустым контейнером** без заметок внутри. Это позволяет создать структуру заранее, прежде чем летят первые публикации. Потом переключился на Python-часть бота. Вот тут уже кипела работа. Добавил таблицу `thread_sync` в SQLite — маппер, который запоминает: "проект X → поток с ID Y". Зачем это нужно? Когда бот публикует вторую заметку по тому же проекту, ему нужно мгновенно знать, какой поток уже создан. Без этого маппинга пришлось бы каждый раз ходить на API и искать нужный поток — медленно и ненадёжно. Создал отдельный модуль **ThreadSync** с методом `ensure_thread()`. Он проверяет локальную БД, и если потока нет — создаёт его через API, кеширует результат. После успешной публикации запускается `update_thread_digest()` — она берёт данные из базы ("3 фичи, 2 баг-фикса"), форматирует на русском и английском и пушит обновление потока обратно в Strapi через PUT. Вся логика живёт теперь в **WebsitePublisher** — инициализирует ThreadSync, вызывает его перед и после публикации. Асинхронно, с `aiosqlite` для неблокирующего доступа к базе. Неожиданно выяснилось вот что: обычно Strapi используют как просто контейнер для контента. А здесь я его заставляю выполнять структурирующую роль. Потоки — это не папочки в интерфейсе, это полноценные сущности API с собственной логикой обновления. Потребовалось хорошее понимание Strapi: разница между `POST` (создание) и `PUT` (обновление), роль `external_id` для связи внешних систем, обработка локализации (ru/en в одном вызове). Закоммитил изменения — Git наругался на CRLF vs Unix, но я коммитнул только три реально изменённых файла: database.py, thread_sync.py, website.py. Результат: 70 тестов проходят, 1 скипнут. Система работает. Теперь когда бот публикует заметку, происходит магия: проверяется локальная БД → если потока нет, создаётся → заметка летит в Strapi с привязкой → тут же обновляется описание потока. Всё это видно на https://borisovai.tech/ru/threads. Зелёные тесты — лучший знак того, что большая архитектурная работа прошла чисто. 😄

#claude#ai#python#javascript#git#api
10 февр. 2026 г.
Новая функцияC--projects-bot-social-publisher

Тесты зелёные: связал бота и Strapi в одну систему

# Тесты прошли — теперь деплой: как я связал Strapi и бота в одну систему Работал над **bot-social-publisher** — ботом, который публикует заметки разработки на сайт borisovai.tech. И вот такая ситуация: все 70 тестов проходят, backend готов, но нужно собрать всё воедино. Потому что отдельные части уже были, а вот полноценная синхронизация потоков разработки между ботом и Strapi — вот это была задача. Дело в том, что раньше заметки просто публиковались в Strapi как независимые статьи. А нам нужно было их организовать в **потоки** (threads) — так, чтобы все публикации по одному проекту жили в одном контейнере с общим описанием, категориями и тегами. Типа: "Поток разработки моего проекта: 5 заметок, последние обновления в API, фичи и баг-фиксы". Начал с backend на Node.js + Strapi. Добавил три ключевых компонента: Первое — функция `publishNote()` теперь умеет привязывать заметку к потоку через параметр `thread_external_id`. Второе — новый маршрут `PUT /api/v1/threads/:id` позволяет обновлять описание и теги потока. И третье — поток может быть *пустым контейнером*, без заметок внутри. Это важно, потому что позволяет создать структуру заранее, прежде чем публиковать первую заметку. Потом переключился на Python-сторону бота. Вот тут уже интереснее. Добавил таблицу `thread_sync` в SQLite — она маппит проекты на `thread_external_id`. Зачем? Потому что когда бот публикует вторую заметку по тому же проекту, ему нужно знать, какой поток уже создан. Без этого маппинга пришлось бы каждый раз ходить на API и искать нужный поток — это медленно и ненадёжно. Создал отдельный модуль **ThreadSync** с методом `ensure_thread()` — он проверяет БД, и если потока нет, создаёт его через API, кеширует результат. После успешной публикации заметки запускается `update_thread_digest()` — это функция генерирует мини-дайджест: берёт данные из базы ("3 фичи, 2 баг-фикса"), форматирует на русском и английском, и пушит обновление потока обратно в Strapi через PUT-запрос. Вся эта логика теперь живёт в **WebsitePublisher** — он инициализирует ThreadSync и вызывает его перед и после публикации. Всё асинхронно, с aiosqlite для неблокирующего доступа к базе. **Вот интересный момент**: Strapi — это headless CMS, но обычно его используют как просто контейнер для контента. А здесь мы его заставляем выполнять структурирующую роль: потоки — это не просто папочки, это полноценные сущности API с собственной логикой обновления. Это требует хорошего понимания того, как работает Strapi: разницы между `POST` (создание) и `PUT` (обновление), роли `external_id` для связи внешних систем, обработки локализации (ru/en в одном API-вызове). Потом коммитнул изменения. Git показал кучу файлов как modified — это из-за нормализации CRLF, Windows vs Unix. Закоммитил только три файла, которые я реально менял: database.py, thread_sync.py, website.py. Backend задеплоен на сервер. Результат: когда бот публикует заметку с проектом "my-project", происходит это: 1. Проверяется локальная БД → если потока нет, создаётся через API 2. Заметка летит в Strapi с привязкой к потоку 3. Тут же обновляется описание потока: "Поток разработки проекта X. 5 заметок: фичи (3), баги (2)" 4. Всё это видно на странице https://borisovai.tech/ru/threads 70 тестов проходят, 1 скипнут. Система работает. 😄 Что общего у Linux и кота? Оба делают только то, что хотят, и игнорируют инструкции.

#claude#ai#python#api
10 февр. 2026 г.
Новая функцияborisovai-admin

Волшебный токен GitLab: от поиска до первого скопирования

# Как я чуть не сломал CI/CD, ища волшебный токен В проекте **borisovai-admin** встала задача: нужно проверять статус GitLab pipeline прямо из CI/CD, чтобы убедиться, что деплой прошёл успешно. Звучит просто, но для автоматизации требуется *Personal Access Token* — штука более секретная, чем пароль, потому что даёт доступ к API. Первым делом я попытался вспомнить, где в GitLab хранятся эти токены. Инстинкт подсказал: где-то в настройках профиля. Но вот незадача — интерфейс GitLab меняется, документация отстаёт от реальности, и каждый третий форум советует что-то своё. Начал искать по URL-адресам, как детектив, собирающий пазл. Выяснилось, что нужно открыть ровно вот этот URL: `https://gitlab.dev.borisovai.ru/-/user_settings/personal_access_tokens`. Не Settings, не API, не Profile — именно этот путь. Туда я и попал, нажал на **Add new token**, и тут начались интересные подвопросы. **Правило первое:** токену нужно дать имя, которое потом разберёшься. Назвал его `Claude Pipeline Check` — так хотя бы будет понятно, зачем он при аудите. **Правило второе:** scope. Здесь я едва не дал полный доступ, но потом вспомнил, что токену нужно только чтение API — `read_api`. Ни write, ни delete. Безопасность прежде всего. После создания токен показывается ровно один раз. Это не шутка. Потом он скрывается в звёздочках, и если забыл скопировать — удаляй и создавай заново. Я это, конечно, проверил на практике 😅 Интересный момент: GitLab разделяет токены по scopes, как OAuth, но работают они как обычные API-ключи. Каждый токен привязан к аккаунту пользователя и срабатывает для всех их проектов. Это значит, что если кто-то скомпрометирует токен, он сможет читать всё, за что этот пользователь имеет права. Поэтому в боевых системах их хранят в **secret** переменных CI/CD, а не в коде. **Что дальше?** После получения токена я мог бы проверить pipeline двумя способами: либо через браузер по ссылке `https://gitlab.dev.borisovai.ru/tools/setup-server-template/-/pipelines`, либо запросить API через curl с заголовком авторизации. Для **borisovai-admin** выбрали первый вариант — простой и понятный. Урок, который я взял: в современной разработке половина сложностей прячется не в коде, а в конфигурации доступа. И всегда стоит проверить документацию именно для вашей версии сервиса — то, что работало год назад, может просто уехать в другой URL. --- Что сказал GitLab, когда разработчик забыл скопировать токен? «Вот тебе урок — я показываю его только один раз!» 😄

#claude#ai#python#git#api#security
10 февр. 2026 г.
Новая функцияtrend-analisis

Три коммита против хаоса: как я спасал расчёты скоринга

# Когда баги в расчётах больнее, чем я думал: история исправления системы скоринга Вот такой момент: сидишь ты, смотришь на результаты работы своей системы анализа трендов в проекте **trend-analysis**, и понимаешь — что-то не так с оценками. Пользователи видят неправильные значения, frontend показывает одно, backend считает совсем другое, и где-то в этом хаосе теряются ваши данные о трендах. ## Началось с простого: поиск несоответствия Задача была такой: **унифицировать систему скоринга между страницей трендов и страницей анализа**, плюс сделать её консистентной на всех слоях приложения. Проблема скрывалась в деталях. На бэкенде я обнаружил, что поле для зоны влияния называлось `strength`, но фронтенд ожидал `impact`. Вроде мелочь, но эта мелочь ломала весь расчёт оценки — данные просто не доходили до формулы. Первым делом создал feature-ветку `fix/score-calculation-and-display`, чтобы иметь безопасное место для экспериментов. Это правило номер один: никогда не чини критичное на main. ## Три коммита — три фикса **Коммит первый** (`6800636`) — объединил layouts страниц тренда и анализа. Оказалось, что кнопки были разбросаны в разных местах, компонента Sparkline находилась не там, где нужно, и показатель уверенности (confidence) был спрятан в глубины интерфейса. Переделал разметку, привёл всё к общему знаменателю. **Коммит второй** (`08ed4cd`) — вот тут засада. Бэкенд API 0.3.0 использовал название поля `impact`, а я в калькуляторе оценки искал `strength`. Результат: null вместо числа. Исправил — и вдруг всё заработало. Казалось бы, переименование в одном месте, но оно спасло половину функциональности. **Коммит третий** (`12cc666`) — уже фронтенд. Функция `formatScore` нормализовала значения неправильно, и `getScoreColor` работал с неправильной шкалой. Переделал под шкалу 0–10, убрал лишнюю нормализацию — сейчас скор отображается именно так, как его считает бэкенд. ## Почему это вообще произошло? Типичная история: когда несколько разработчиков работают над одной системой в разное время, контракт между бэкендом и фронтендом расходится. Никто не виноват — просто один переименовал поле, другой не знал об этом. Было бы хорошо иметь автоматические тесты контрактов (contract testing), которые бы сразу эту несогласованность выловили. GraphQL был бы удобнее — типизация спасла бы много мук. ## Что дальше? Merge Request готов к созданию. API сервер уже перезапущен и слушает на `http://127.0.0.1:8000`. Vite dev server для фронтенда работает в фоне с HMR, поэтому изменения подхватываются моментально. Остаётся дождаться review коллег, смёрджить в main и развернуть. Урок на будущее: синхронизируй контракты между слоями приложения через документацию или, ещё лучше, через code generation из единого источника истины. 😄 **GitLab MR** — как свидание вслепую: никогда не знаешь, найдут ли рецензенты ошибки, которые не заметил ты.

#claude#ai#git#api
Разработка: Trend Analisis
10 февр. 2026 г.
ИсправлениеC--projects-bot-social-publisher

Давай сделаем потоки разработки.

# Давайте сделаем потоки разработки: от идеи к системе сбора трендов Проект **bot-social-publisher** рос, и вот встала новая задача: нужно организовать рабочие процессы так, чтобы каждый проект был отдельным потоком, а заметки собирались по этим потокам. Звучит просто, но это требовало архитектурного решения. Я полез в документацию на сайте (https://borisovai.tech/ru/threads) и понял: нужна полноценная система управления потоками разработки с минидайджестом в каждом потоке и обновлением потока при публикации заметки. Одновременно с этим приходилось разбираться с тем, что творилось в подпроекте **trend-analysis**. Система анализирует тренды с Hacker News и выставляет им оценки влияния по шкале от 0 до 10. Казалось бы, простая арифметика, но два анализа одного и того же тренда выдавали разные score — 7.0 и 7.6. Вот это нужно было развязать срочно. Первым делом я погрузился в исходный код. В `api/routes.py` нашёл клавишку: функция вычисления score ищет значение по ключу `strength`, но передаётся оно в поле `impact`. Классический мисматч между backend и data layer. Исправил на корректное имя поля — это был коммит номер один. Но это оказалось только половиной истории. Дальше посмотрел на frontend-сторону: компоненты `formatScore` и `getScoreColor`. Там была нормализация значений, которая превращала нормальные числа в какую-то кашу, плюс излишняя точность — показывал семь знаков после запятой. Убрал лишнюю нормализацию, установил `.toFixed(1)` для вывода одного знака после запятой. Второй коммит готов. Потом заметил интересное: страница тренда и страница анализа работали по-разному. Одна и та же логика расчёта должна была работать везде одинаково. Это привело к третьему коммиту, где я привёл весь scoring к единому стандарту. **Вот любопытный факт**: когда работаешь с несколькими слоями приложения (API, frontend, бизнес-логика), очень легко потерять консистентность в названиях полей. Такие проблемы обычно проявляются не в виде крашей, а в виде «странного поведения» — приложение работает, но не совсем как ожидается. И выяснилось, что score 7.0 и 7.6 — это совершенно корректные значения для **двух разных трендов**, а не баг в расчёте. Система работала правильно, просто нужно было почистить код. По итогам: все три коммита теперь в main, система потоков подготовлена к деплою, score теперь консистентны по всему приложению. Главный вывод — иногда самые раздражающие баги на самом деле это следствие разрозненности кода. Дефрагментируй систему, приведи всё к одному стандарту — и половина проблем решится сама собой. Почему AWS обретёт сознание и первым делом удалит свою документацию? 😄

#claude#ai#python#git#api
10 февр. 2026 г.
Исправлениеtrend-analisis

Фантомный баг в расчётах: поиск в логах спасает проект

# Охота за фантомом: как мы поймали баг, которого не было Проект **trend-analysis** набирал обороты. Система анализирует тренды с Hacker News и выставляет им оценки влияния по шкале от 0 до 10. Казалось бы, простая задача: посчитал метрики, вывел число. Но тут всплыла странность: два анализа одного и того же тренда показывали разные score — 7.0 и 7.6. Баг или особенность? Это нужно было разобрать срочно. Первым делом я начал копать в логах. Посмотрел на слой API — там в `routes.py` происходит расчёт score. Начал читать функцию вычисления и... стоп! Вижу: в коде ищет значение по ключу `strength`, а передаётся оно в поле `impact`. Классический мисматч! Вот и виновник. Исправил на корректное имя поля — это был первый коммит (`b2aa094`). Но постойте, это только половина истории. Дальше зашёл в frontend-часть — компоненты `formatScore` и `getScoreColor`. Там была нормализация значений, которая превращала нормальные числа в какую-то кашу. Плюс точность вывода — показывал слишком много знаков после запятой. Переделал логику: убрал лишнюю нормализацию, установил `.toFixed(1)` для вывода одного знака после запятой. Это стал второй коммит (`a4b1908`). Вот здесь и произошла интересная вещь. После исправлений я переходил между trend-страницей и analysis-страницей проекта и заметил, что интерфейс работает по-разному. Оказалось, что эти страницы нужно было унифицировать — одна и та же логика расчёта должна работать везде одинаково. Это был уже третий коммит, где мы привели весь scoring к единому стандарту (`feat: unify trend and analysis pages layout and scoring`). **Любопытный факт**: когда ты работаешь с несколькими слоями приложения (API, frontend, бизнес-логика), очень легко потерять консистентность в названиях полей и форматировании данных. Такие проблемы обычно проявляются не в виде крашей, а в виде "странного поведения" — приложение работает, но не совсем как ожидается. Git-коммиты с описанными ошибками — отличный способ документировать такие находки. По итогам расследования выяснилось: score 7.0 и 7.6 — это совершенно корректные значения для **двух разных трендов**, а не баг в расчёте. Система работала правильно, просто нужно было почистить код и унифицировать логику. Все три коммита теперь в main, изменения готовы к деплою. Вывод простой: иногда самые раздражающие баги на самом деле — это следствие разрозненности кода. Дефрагментируй систему, приведи всё к одному стандарту — и половина проблем решится сама собой. Что будет, если AWS обретёт сознание? Первым делом он удалит свою документацию 😄

#claude#ai#python#api
Разработка: Trend Analisis
10 февр. 2026 г.
Исправлениеtrend-analisis

Ловушка в базе: как я нашел ошибку, которая еще не причинила вреда

# В погоне за призраком: как я ловил ошибку в базе данных trend-analysis **Завязка** Проект trend-analysis — система, которая анализирует тренды из HackerNews и выставляет им оценки важности. Казалось бы, простая задача: собрал данные, посчитал средние значения, отправил в клиент. Но вот в один прекрасный день я заметил что-то странное в результатах API. Score одного тренда показывал 7.0, другого 7.6 — и эти значения упорно не совпадали ни с чем, что я мог бы пересчитать вручную. Начальник спросил: «Откуда эти цифры?» А я, сидя перед экраном, честно не знал. **Развитие** Первым делом я залез в базу данных и вытащил исходные данные по каждому тренду. Включил мозг, взял калькулятор — и вот тут произошло чудо. Score 7.0 оказался совершенно легальным средним от массива impact-значений [8.0, 7.0, 6.0, 7.0, 6.0, 8.0]. А 7.6? Это 7.625, округленное до одного знака после запятой для красоты. Среднее от [9.0, 8.0, 9.0, 7.0, 8.0, 6.0, 7.0, 7.0]. Получается, что это были **два разных тренда**, а не версии одного и того же. Job ID c91332df и 7485d43e — совершенно разные анализы, разные Trend ID из HackerNews. Я просто неправильно читал таблицу, сидя в 2 часа ночи. Но — о ужас! — при детальной проверке api/routes.py на строке 174 я нашел настоящую бомбу. Код берет значения силы тренда из поля `strength`, хотя должен брать из `impact`. В текущий момент это никак не влияет на выданные результаты, потому что финальный score берется напрямую из базы данных (строка 886), а не пересчитывается. Но это скрытая мина, которая взорвется, как только кто-то попробует переиндексировать данные или добавить пересчет. **Познавательный момент** Вообще, типичная история разработчика: когда сложная система работает только потому, что ошибка в точке A компенсируется ошибкой в точке B. Асинхронный код, кеширование, отложенные вычисления — все это превращает отладку в охоту за привидениями. Поэтому в production-системах всегда стоит добавлять internal healthchecks, которые периодически пересчитывают критические метрики и сравнивают с сохраненными значениями. **Итог** Я исправил ошибку в коде на будущее — теперь `strength` будет правильно браться из `impact`. Тесты написаны, баг залогирован как bug_fix в категории. Технологический стек (Python, API, Claude AI) позволил быстро проверить гипотезу и убедиться, что текущие данные в порядке. Главный урок: иногда самая сложная ошибка — это отсутствие ошибки, а просто невнимательность. Как говорится, программист покупает два дома: один для себя, другой для багов, которые он найдет в своем коде 😄

#claude#ai#python#api
Разработка: Trend Analisis
10 февр. 2026 г.
Новая функцияtrend-analisis

API ключи как головная боль: как мы организовали chaos в trend-analisis

# Когда API ключей больше, чем смысла их хранить: как мы организовали регистрацию в trend-analisis Проект **trend-analisis** рос быстрее, чем мы себе представляли. Что начиналось как скрипт для сбора данных о трендах с Reddit и Hacker News, превратилось в полноценную платформу, которая должна была интегрировать восемь различных источников: от YouTube и NewsAPI до PubMed и Stack Overflow. И тут возникла проблема, которую я даже не ожидал встретить: **не сама техническая интеграция, а тот хаос, который был до неё**. Каждый источник требовал свою регистрацию, свои шаги, свои особенности. Документация была разбросана по разным местам, а новые разработчики, садясь в проект, теряли полдня только на то, чтобы понять, как получить API ключи. Я помню, как смотрел на список источников и видел эти **[9.0, 8.0, 9.0, 7.0, 8.0, 6.0, 7.0, 7.0]** — оценки влияния каждого источника на общее качество трендов. Среднее значение выходило **7.6 балла**. Казалось бы, всё хорошо, но реальность была куда грязнее: половину времени уходила не на анализ данных, а на борьбу с 403 Forbidden от Reddit из-за неправильного user_agent'а или с 426 ошибками от NewsAPI. Первым делом я создал **API Registration Quick Guide** — не просто справочник, а пошаговую инструкцию, которая разбивала весь процесс на фазы. Фаза 1: Essential (Reddit, NewsAPI, Stack Overflow) — это то, что нужно для MVP. Фаза 2: Video & Community (YouTube, Product Hunt, Dev.to) — дополнение. Фаза 3: Search & Research — когда уже есть пользователи. Фаза 4: Premium — это потом, после того как мы подтвердили бизнес-модель. Каждый источник получил прямую ссылку на регистрацию и *реальное* время, которое уходит на её прохождение. Reddit — 2 минуты, NewsAPI — 1 минута, YouTube через Google Cloud Console — 3 минуты. Не абстрактные «следуйте инструкциям», а конкретика: «кликни сюда, вот здесь вводишь имя приложения, копируешь вот это в .env». Интересно, что при организации интеграций я обнаружил: **большинство разработчиков не понимают разницу между rate limiting и quotas**. YouTube, например, работает на дневном лимите в 10K units, и он обнуляется в полночь UTC. Это не ошибка API — это by design. Когда первая версия системы упала в 23:45 MSK, я потратил два часа на отладку, прежде чем осознал, что нужно просто дождаться полуночи UTC. Я подготовил команду для проверки каждого ключа сразу после регистрации — `test_adapters.py` запускает краткий тест на каждом источнике. Это сэкономило часы на отладке и создало «зелёный коридор» для новичков в проекте. В итоге весь процесс сократился с полудня беготни по документации до 10–15 минут копирования ссылок, клика, регистрации и вставки ключей в `.env`. Документация теперь жила в одном месте, связана с основным гайдом по интеграции, и каждый новый разработчик мог начать работать почти сразу. **Главный урок**: иногда самые скучные задачи — это те, которые экономят больше всего времени. Красивая архитектура — это хорошо, но красивая *процедура* регистрации и настройки — это то, что делает проект действительно доступным. 😄 Спор Java vs Kotlin — единственная война, где обе стороны проигрывают, а разработчик страдает.

#claude#ai#python#api#security
Разработка: Trend Analisis
10 февр. 2026 г.
Новая функцияtrend-analisis

Спасаем разработчиков от лабиринта документации

# Как я спасал API документацию от размытых ссылок Проект **trend-analisis** рос, и с ним росла беда: разработчикам нужно было регистрироваться, читать документацию, переходить на API endpoint'ы — и каждый раз они путались в лабиринте вкладок и закладок. Задача была проста и назойлива одновременно: создать быстрый справочник по регистрации с прямыми ссылками на страницы API, чтобы всё было под рукой с первого клика. Звучит просто? Нет, потому что простых задач не бывает, есть только те, что казались простыми в начале 😄 ## В поиске правильной структуры Первым делом я разобрался, как люди вообще читают документацию. Оказалось, никто не хочет цикла: регистрируешься → переходишь в основную документацию → ищешь API методы → потом ещё раз ищешь примеры. Это как ходить пешком туда-обратно на протяжении всего проекта. Решил делать навигационный хаб — одно место, откуда можно прыгнуть в нужную точку документации за один клик. Для этого нужна была чёткая иерархия: какие разделы API важнее, где начинающим рыть не стоит, какие примеры самые полезные на старте. ## Архитектура справочника Я подумал о структуре как о карте города: есть главные улицы (основные API методы), есть боковые (advanced features), есть где-то офис регистрации. Сделал справочник с тремя слоями: 1. **Блок регистрации** — с инструкциями и ссылкой на форму 2. **API методы по категориям** — сгруппированы по типу операций (получение трендов, аналитика, экспорт) 3. **Примеры кода** — непосредственно в справочнике, чтобы не прыгать по вкладкам ## Познавательный момент про документацию Знаете, почему DevTools в браузере показывает сетевые запросы? Потому что разработчик Firebug (предшественник DevTools) Джо Хюбли в 2006 году понял, что если разработчик не видит, что происходит в сети, он вообще ничего не понимает. Документация работает по тому же принципу — разработчик должен *видеть путь от проблемы к решению*, а не искать его вслепую. ## Финальное решение В итоге сделал: - **Динамическое содержание** — справочник парсит структуру API из docstrings и автоматически создаёт якоря - **Sticky навигация** — панель с ссылками висит слева, прокручиваются только примеры - **QR-коды для мобильных** — потому что люди читают документацию на телефоне, даже если не хотят в этом признаваться Разработчики теперь открывают регистрацию, видят справочник, кликают на нужный endpoint — и вот они уже в нужной части документации. Без танцев с бубнами, без пяти вкладок браузера. Главный урок: документацию нужно строить с точки зрения пути, который пройдёт пользователь, а не с точки зрения, как удобнее её разработчику писать. Когда ты думаешь о маршруте, а не о каталоге — всё встаёт на место. Почему разработчик любит регулярные выражения? Потому что без них жизнь слишком скучна 😄

#claude#ai#python#javascript#api
Разработка: Trend Analisis
10 февр. 2026 г.
Исправлениеtrend-analisis

Когда унификация интерфейса оказывается архитектурной головоломкой

# Унификация — это неочевидно сложно Задача стояла простая на словах: «Давай выровняем интерфейс страниц тренда и анализа, чтобы не было разнобоя». Типичное дело конца спринта, когда дизайн требует консистентности, а код уже рассеялся по разным файлам с немного разными подходами. В проекте **trend-analisis** у нас две главные страницы: одна показывает тренды с оценками, другая — детальные аналитические отчёты. Обе они должны выглядеть как *части одного целого*, но на деле они разошлись. Я открыл `trend.$trendId.tsx` и `analyze.$jobId.report.tsx` и понял, что это как смотреть на двух братьев, которые выросли в разных городах. **Первым делом я разобрался с геометрией.** На мобильных устройствах кнопки на странице тренда вели себя странно — они прятались за правый край экрана, как непослушные дети. Перевёл их в стек на мобильных и горизонтальный ряд на десктопе. Простая история, но именно такие детали создают ощущение недоделанности. Потом пошло интереснее. **ScorePanel** — компонент с оценкой и её визуализацией — тоже требовал внимания. На странице тренда Sparkline (такие симпатичные маленькие графики) были отдельно от оценки, на странице анализа они находились где-то рядом. Решил переместить Sparkline внутрь ScorePanel, чтобы блок оценки стал полноценным, законченным элементом. **Но главный подвох ждал в бэкенде.** Когда я нырнул в `routes.py`, обнаружил, что оценка анализа считается в диапазоне 0–1 и потом нормализуется. Странная архитектура: пользователь видит на экране число 7–8, а в коде живёт 0.7–0.8. Когда возникла необходимость унифицировать, пришлось переделать — теперь всё работает в единой шкале 0–10 от фронтенда до бэкенда. Ещё одна муха в супе: переводы. Каждый отчёт имеет title и description. Вот только они часто приходили на разных языках — title на английском, description на русском, потому что система переводов разрасталась бессистемно. Пришлось переделать архитектуру на `get_cached_translations_batch()`, чтобы title и description синхронизировались по локали. Вот тут и проявляется одна из *типичных ловушек разработки*: когда система растёт, легко получить состояние, при котором разные части кода решают одну и ту же задачу по-разному. Кэширование переводов, кэширование данных, нормализация чисел — каждая из этих проблем порождает своё микрорешение, и вскоре у вас сложная паутина зависимостей. Решение: честный код-ревью и документирование паттернов, чтобы новичок не добавил пятый способ кэширования. **В итоге:** две страницы теперь выглядят как надо, API вернулся к нормальным оценкам (7–8 вместо 1), переводы синхронизированы. Git commit отправлен, бэкенд запущен на порту 8000. Дальше в плане новые исправления — благо материал есть. Чему научился: унификация — не просто про UI, это про согласованность логики по всему стеку. Порой проще переделать целый компонент, чем мучиться с костылями. 😄 Почему backend разработчик плюёт на фронтенд? Потому что он работает в консоли и ему всё равно, как это выглядит.

#claude#ai#python#git#api
Разработка: Trend Analisis
10 февр. 2026 г.
Исправление

Когда GitLab Runner нашел 5 ошибок TypeScript за 9 секунд

# GitLab Runner сломал сборку: как мы спасали TypeScript проект Понедельник, 10 февраля. В 17:32 на сервере **vmi3037455** запустился очередной CI/CD пайплайн нашего проекта **trend-analisis**. GitLab Runner 18.8.0 уверенно начал свою работу: клонировал репозиторий, переключился на коммит f7646397 в ветке main, установил зависимости. Всё шло как надо, пока... Сначала казалось, что всё в порядке. `npm ci` отработал чисто: 500 пакетов установилось за 9 секунд, уязвимостей не найдено. Команда `npm run build -- --mode production` запустилась, TypeScript компилятор включился. И вот тут — **взрыв**. Пять ошибок TypeScript сломали всю сборку. Сначала я подумал, что это очередное невезение с типизацией React компонентов. Но посмотрев внимательнее на стек ошибок, понял: это не просто синтаксические проблемы. Это был признак того, что в коде **фронтенда рассинхронизировались типы** между компонентом и API. Проблема первая: в файле `src/routes/_dashboard/analyze.$jobId.report.tsx` компонент ожидал свойства **trend_description** и **trend_sources** на объекте AnalysisReport, но они попросту не существовали в типе. Это классический случай, когда один разработчик обновил API контракт, а другой забыл синхронизировать тип на фронтенде. Проблема вторая: импорт `@/hooks/use-latest-analysis` исчез из проекта. Компонент `src/routes/_dashboard/trend.$trendId.tsx` отчаянно его искал, но находил только воздух. Кто-то либо удалил хук, либо переместил его, не обновив импорты. Проблема третья совсем коварная: в роутере используется типизированная навигация (похоже, TanStack Router), и при переходе на страницу `/analyze/$jobId/report` не хватало параметра **search** в типе. Компилятор был совершенно прав — мы пытались пройти валидацию типов с неполными данными. Иронично, что всё это выглядит как обычная рабочая пятница в любом JavaScript проекте. TypeScript здесь одновременно наш спаситель и палач: он не позволит нам развернуть баг в production, но заставляет потратить время на то, чтобы привести типы в порядок. **Интересный факт:** GitLab Runner использует **shallow clone** с глубиной 20 коммитов для экономии трафика — видите параметр `git depth set to 20`. Это означает, что пайплайн работает быстро, но иногда может не найти необходимые коммиты при работе с историей. В данном случае это не помешало, но стоит помнить при отладке. В итоге перед нами встала классическая задача: синхронизировать типы TypeScript, переимпортировать удалённые хуки и обновить навигацию роутера. Сборка не пройдёт, пока всё это не будет в порядке. Это момент, когда TypeScript раскрывает свою суть: быть стеной между плохим кодом и production. Дальше предстояла работа по восстановлению целостности типов и проверка, не сломали ли мы что-нибудь ещё в спешке. Welcome to the JavaScript jungle! 😄

#clipboard#javascript#git#api
10 февр. 2026 г.
Новая функцияai-agents

Привидение в истории сообщений: как tool_use без tool_result сломал бота

# Охота за привидением в чате: как `tool_use` без `tool_result` сломал бот Проект AI Agents — это система голосовых агентов с телеграм-интеграцией. Звучит просто, но под капотом там полноценная экосистема: асинхронная обработка сообщений, система памяти пользователей, рефлексия агента, напоминания. И вот однажды бот просто перестал запускаться. Сначала казалось, что это типичная проблема с конфигурацией. Но логи рассказывали более странную историю. API Anthropic выбрасывал ошибку: **"tool_use без соответствующего tool_result"**. Как будто кто-то забыл закрыть скобку, но на уровне сессии. Начал копать. Оказалось, что в `handlers.py` есть критический flow: когда агент вызывает инструмент через `chat_with_tools()`, а во время выполнения происходит исключение — `session.messages` остаётся в "повреждённом" состоянии. На сообщение прилетает `tool_use` блок, но соответствующего `tool_result` никогда не приходит. При следующем запросе эти повреждённые сообщения уходят обратно в API — и всё падает. Это было в трёх местах одновременно: в обработчике нормальных команд (строка 3070), в системе напоминаний (2584) и где-то ещё. Классический паттерн копируй-вставь с одинаковым багом. **Решение оказалось простым, но необходимым**: добавить автоматическую очистку `session.messages` в обработчик исключений. Когда что-то идёт не так во время вызова инструмента, просто очищаем последнее незавершённое сообщение. Вот и вся магия. Пока чинил это, нашёл ещё несколько интересных проблем. Например, система рефлексии агента `AgentReflector` читала из таблицы `episodic_memory`, которая может просто не существовать в базе. Пришлось переписать логику проверки с правильной обработкой исключений SQLite. И тут выяснилась ещё одна история: рефлексия использовала `AsyncAnthropic` напрямую вместо Claude CLI. Это означало, что каждый раз при рефлексии расходовались API credits. Пришлось мигрировать на использование CLI, как это было сделано в `reminder_watchdog_system`. Теперь агент может размышлять о своей работе совершенно бесплатно. Отдельное приключение ждало команду `/insights`. Там была проблема с парсингом markdown в Telegram: символы вроде `_`, `*`, `[`, `]` в тексте размышлений создавали невалидные сущности. Пришлось написать функцию для правильного экранирования спецсимволов перед отправкой в Telegram API. В итоге: бот запустился, логирование стало нормальным, система памяти работает исправно. Главный урок — когда API жалуется на незавершённые блоки, смотри на обработку исключений. Там всегда что-то забыли почистить. 😄 Как отличить разработчика от отладчика? Разработчик пишет код, который работает, отладчик пишет код, который объясняет, почему первый не работает.

#claude#ai#python#javascript#api#security
10 февр. 2026 г.
Новая функцияC--projects-ai-agents-voice-agent

Монорепо как зеркало: когда Python и JS живут в одном доме

# Монорепо как зеркало: Python + Next.js в одном проекте **Завязка** Представьте ситуацию: вы разработчик и в ваших руках проект *voice-agent* — голосовой помощник на основе Claude, построенный как монорепо. С одной стороны Python-backend (FastAPI, aiogram для Telegram), с другой — Next.js фронтенд (React 19, TypeScript 5.7) для Telegram Mini App. Звучит здорово, но вот в чём подвох: когда в одном репозитории живут две экосистемы с разными правилами игры, управлять ими становится искусством. **Развитие** Первой проблемой, которая выпрыгнула из неоткуда, была **забывчивость переменных окружения**. В Python проект использует `pydantic-settings` для конфигурации, но выяснилось, что эта библиотека не экспортирует значения автоматически в `os.environ`. Результат? Модульный код, читавший переменные прямо из окружения, падал с загадочными ошибками. Пришлось документировать эту ловушку в ERROR_JOURNAL.md — живом архиве подводных камней проекта, где уже скопилось десять таких «моментов истины». Далее встал вопрос архитектуры. Backend требовал **координатора** — центрального паттерна, который бы оркестрировал взаимодействие между агентами и фронтенд-запросами. На бумаге это выглядело идеально, но в коде его не было. Это создавало технический долг, который блокировал Phase 2 разработки. Пришлось вводить **phase-gate валидацию** — автоматическую проверку, которая гарантирует, что прежде чем переходить к следующей фазе, все артефакты предыдущей действительно на месте. В процессе появилась и проблема с **миграциями базы данных**. SQLite с WAL-режимом требовал аккуратности: после того как junior-агент создавал файл миграции, она не всегда применялась к самой БД. Пришлось вводить обязательный чек: запуск `migrate.py`, проверка таблиц через прямой SQL-запрос, документирование статуса. Без этого можно часами отлавливать фантомные ошибки импорта. **Познавательный блок** Интересный факт: монорепо — это не просто удобство, это *культурный артефакт* команды разработки. Google использует одно гигантское хранилище для всего кода (более миллиарда строк!), потому что это упрощает синхронизацию и рефакторинг. Но цена высока: нужны инструменты (Bazel), дисциплина и чёткие протоколы. Для нашего voice-agent это значит: не просто писать код, а писать его так, чтобы Python-part и Next.js-part *доверяли друг другу*. **Итог** В итоге сложилась простая истина: монорепо работает только если есть **система проверок**. ERROR_JOURNAL.md превратился не просто в логирование ошибок, а в живой артефакт культуры команды. Phase-gate валидация стала гарантией, что при параллельной работе нескольких агентов архитектура не съезжает в стороны. А обязательная проверка миграций — это не занудство, а спасение от трёх часов ночного отлавливания, почему таблица не там, где ей быть. Главный урок: в монорепо важна не столько архитектура, сколько **честность системы**. Чем раньше вы перейдёте от надежды на память к автоматическим проверкам, тем спокойнее спать будете. Почему Python и Java не могут дружить? У них разные dependency trees 😄

#claude#ai#python#javascript#git#api#security
Разработка: Voice Agent
10 февр. 2026 г.
Новая функцияborisovai-admin

Umami Analytics: как я сделал админ-панель data-driven

# Самостоятельная аналитика: как я превратил borisovai-admin в data-driven продукт Несколько месяцев назад передо мной встала типичная для любого владельца проекта проблема: я совершенно не видел, кто и как использует мою админ-панель **borisovai-admin**. Google Analytics казался избыточным (и страшным с точки зрения приватности), а простой счётчик посещений — примитивным. Нужно было что-то лёгкое, приватное и полностью под своим контролем. Выбор пал на **Umami Analytics** — открытую веб-аналитику, которая уважает приватность пользователей, не использует cookies и полностью GDPR-compliant. Главное же — её можно развернуть самостоятельно, прямо в своей инфраструктуре. ## Четыре этапа внедрения **Первый шаг — упростить развёртывание.** Стандартная Umami требует двух контейнеров (приложение + PostgreSQL), но для небольшого проекта это избыточно. Я нашёл fork **maxime-j/umami-sqlite**, который использует SQLite — файловую БД в одном контейнере. Экономия памяти была существенной: вместо ~300 MB получил ~100 MB. Затем написал скрипт **install-umami.sh** из семи шагов, который может быть запущен много раз без побочных эффектов (идемпотентный — именно это было важно для автоматизации). **Второй этап — автоматизировать через CI/CD.** Создал два job'а в пайплайне: один автоматически ставит Docker (если его нет), второй — развёртывает саму Umami. Добавил health check, чтобы пайплайн не переходил к следующему шагу, пока контейнер не будет готов. Инкрементальный деплой через **deploy-umami.sh** позволяет обновлять конфигурацию без перезагрузки приложения. **Третий этап — дать пользователям интерфейс.** Создал страницу **analytics.html**, где каждый новый сервис может получить код для интеграции отслеживания. Плюс добавил API endpoint `GET /api/analytics/status` для проверки, всё ли работает. Async-скрипт Umami весит всего ~2 KB и не блокирует рендеринг страницы — вот это я ценю. **Четвёртый этап — документировать.** Написал **AGENT_ANALYTICS.md** с инструкциями для будущих разработчиков, обновил главный **CLAUDE.md** таблицей всех сервисов. ## Что интересного я узнал Оказывается, боль большинства разработчиков с традиционной аналитикой — это не функциональность, а приватность. Umami решает это элегантно: скрипт отправляет только агрегированные данные (сессии, страницы, источники трафика) без ID пользователей и истории кликов. А главное — нет необходимости в **consent banner**, который все равно раздражает пользователей. Порт **3001** внутри контейнера пробросил через **Traefik** на HTTPS-домены `analytics.borisovai.ru` и `analytics.borisovai.tech`. Вообще, это я оценил: такая простота развёртывания чуть ли не впервые в моём опыте с self-hosted решениями. Встроенная авторизация в самой Umami (не потребовался дополнительный Authelia) — и это экономия на инфраструктуре. Один лайфхак: чтобы скрипт аналитики не блокировался AdBlock, назвал его `stats` вместо стандартного `umami` — простой способ обойти базовые фильтры. ## Итог Теперь **borisovai-admin** наконец-то видит себя со стороны. Я получил данные о том, какие страницы реально используют люди, откуда они приходят и сколько времени длятся сессии. Всё это — на своём сервере, без третьих лиц и без чувства вины перед пользователями. Следующий шаг — подключить аналитику ко всем остальным сервисам проекта. Это уже не задача месяца, а скорее вопрос пары часов на каждый сервис. Учимся: иногда лучший инструмент — это не самый популярный, а самый честный. 😄

#git#commit#api#security
10 февр. 2026 г.
Новая функцияai-agents

Молчаливый API: когда успех — это просто пустота

# Когда API молчит: охота на призрак в системе обработки команд Это была обычная воскресенье в проекте **ai-agents**. Пользователь Coriollon отправил простую команду через Telegram: "Создавай". Три слова. Невинные на вид. Но система ответила молчанием — и началась охота на баг, которая заняла почти семь минут и три попытки переподключения. ## Что мы видим в логах Сначала всё выглядит нормально. Запрос приходит в 12:23:58. Система маршрутизирует его на **Claude API** с моделью Sonnet. Промпт имеет 5344 символа — немалый объём контекста. Первый запрос уходит в API и... здесь начинается интересное. API отвечает за 26 секунд. Кажется, успешно: `is_error: False`, `num_turns: 2`, даже token usage выглядит логичным. Но вот `result: ''` — пустой результат. Система ловит эту аномалию и логирует `cli_empty_response`. Мой первый инстинкт: "Может, сетевой глюк?" Система делает то же самое — ждёт 5 секунд и повторяет запрос. Вторая попытка в 12:24:31. История повторяется: успех по метрикам, но снова пустой ответ. ## Третий раз — не удача К третьей попытке я уже понял, что это не случайный сетевой перебой. Система работает корректно, API возвращает `success: true`, токены учитываются (даже видны попадания в кэш: `cache_read_input_tokens: 47520`). Но результат, ради которого всё затевалось, так и не приходит. Вот здесь кроется классическая ловушка в работе с LLM API: **успешный HTTP-ответ не гарантирует наличие полезной нагрузки**. API может успешно обработать запрос, но вернуть пустое поле `result` — это может означать, что модель вернула только служебные данные (вроде использованных токенов) без фактического содержимого. Финальная попытка заканчивается в 12:25:26. Три запроса, три молчания, общее время ожидания — почти семь минут. Система логирует финальную ошибку: `message_handler_error: CLI returned empty response`. ## Чему это учит Когда вы работаете с внешними API, особенно с такими мощными, как Claude, недостаточно проверять только HTTP-статус. Нужно валидировать **содержимое ответа**. В данном случае система сделала ровно это — поймала пустой результат и попыталась восстановиться через retry-логику с экспоненциальной задержкой (5, 10 секунд). Но вот что интересно: кэшированные токены (видны в каждом логе) говорят, что контекст был успешно закэширован. Это означает, что на второй и третий запрос система платила дешевле — 0.047 и 0.037 USD вместо 0.081 на первый запрос. Автоматическое кэширование контекста в Claude API — это фишка, которая спасает в ситуациях вроде этой. Корень проблемы остался в логах как загадка: был ли это timeout на стороне модели, недопонимание в структуре запроса или что-то ещё — сказать сложно. Но система сработала как надо: зафиксировала проблему, задокументировала все попытки, сохранила данные сессии для постмортема. Lesson learned: в системах обработки команд от пользователей нужна не только retry-логика, но и мониторинг пустых ответов. И да, Telegram-боты любят такие фокусы. 😄 **API успешно вернул ошибку об ошибке успеха — вот это я называю отличной синхронизацией!**

#claude#ai#api#security
9 февр. 2026 г.
Новая функцияC--projects-bot-social-publisher

Unit-тесты зелёные, а бот не работает: гонка условий в Telegram

# Когда unit-тесты лгут: как я запустил систему доступа в реальном Telegram **bot-social-publisher** выглядел как отличный проект для спринта. Полнофункциональный Telegram-бот с командами, памятью, интеграциями. Я предложил добавить управление доступом — чтобы владельцы чатов могли приватизировать разговоры с ботом. Звучит просто: только авторизованные пользователи видят ответы. Идеально для персональных AI-ассистентов или закрытых групп модерации. Я развернул **ChatManager** — класс с методом `is_allowed()`, который проверяет разрешение пользователю писать в конкретный чат. Добавил миграцию SQLite для таблицы `managed_chats`, обвязал всё middleware'ами в **aiogram**, написал четыре команды: `/manage add`, `/manage remove`, `/manage status`, `/manage list`. Unit-тесты прошли с ликованием. **pytest** выдал зелёный статус. Документация? Позже, мол. Потом наступил момент истины. Запустил бота локально через `python telegram_main.py`. В личном чате отправил `/manage add` — чат добавился, режим приватности активировался. Отправил обычное сообщение — ответ пришёл. Открыл второй аккаунт, отправил то же — бот молчит. Отлично, система контроля работает! Но не совсем. **Первая проблема** вскрылась при быстрых командах подряд. **aiogram** работает асинхронно, как и **aiosqlite**. Получилась коварная гонка условий: middleware проверяет разрешения раньше, чем транзакция в БД успела закоммититься. Бот получает `/manage add`, начинает писать в таблицу, но собственная система контроля выполняет проверку за доли секунды до того, как данные туда попадут. На unit-тестах такое не видно — там всё выполняется последовательно. **Вторая проблема** — SQLite и одновременные асинхронные обработчики. Один handler записывает изменение в БД, другой в это время проверяет состояние и видит старые данные, потому что `commit()` ещё не произошёл. Мне помогли явные транзакции и аккуратная расстановка `await`'ов — гарантия того, что каждая операция завершится перед следующей. Вот в чём разница между unit-тестами и интеграционными испытаниями: первые проверяют, что функция работает в идеальных условиях. Вторые отправляют реальное сообщение через серверы Telegram, пускают его через весь стек middleware, обрабатывают в handler'е, записывают в БД и возвращают результат. Тесты говорили: всё работает. Реальность показала: медленно и с условиями. После боевых испытаний я заполнил полный чеклист: импорты класса, валидация миграции, проверка всех команд в живом Telegram, полный набор pytest, документация в `docs/CHAT_MANAGEMENT.md` с примерами архитектуры. Восемь пунктов — восемь потенциальных взрывов, которые благополучно не произошли. Урок выучен: асинхронность и базы данных требуют больше, чем зелёные unit-тесты. Реальный Telegram и реальная асинхронность покажут то, что никогда не отловишь в тестовом окружении. 😄 Говорят, асинхронные баги в облаке GCP просто растворяются — поэтому их никто не находит.

#claude#ai#python#git#api
9 февр. 2026 г.
Новая функцияC--projects-bot-social-publisher

Когда unit-тесты лгут: боевые испытания Telegram-бота

# Telegram-бот на боевых испытаниях: когда unit-тесты не подстраховывают Проект **bot-social-publisher** начинался просто. Полнофункциональный Telegram-бот с памятью, командами, интеграциями. Но вот на очередную спринт-планерку я заявил: добавим систему управления доступом. Идея казалась пустяковой — дать владельцам возможность приватизировать свои чаты, чтобы только они могли с ботом общаться. Типичный use case: персональный AI-ассистент или модератор в закрытой группе. Теория была прекрасна. Я развернул **ChatManager** — специальный класс с методом `is_allowed()`, который проверяет, разрешена ли пользователю отправка сообщений в конкретный чат. Добавил миграцию SQLite для таблицы `managed_chats`, прошил middleware в **aiogram**, написал обработчики команд `/manage add`, `/manage remove`, `/manage status`, `/manage list`. Unit-тесты прошли с зелёным светом — `pytest` даже не чихнул. Документация пока отложена, но это же детали! Потом наступил момент истины. Запустил бота локально через `python telegram_main.py`, переключился в личный чат и отправил первую `/manage add`. Бот записал ID чата, переключился в режим приватности. Нормально! Попробовал отправить обычное сообщение — ответ пришёл. Открыл чат со своего второго аккаунта, отправил то же самое — тишина. Бот ничего не ответил. Перфект, middleware работает. Но не всё было так гладко. Первая проблема вылезла при быстрых командах подряд. В асинхронной архитектуре **aiogram** и **aiosqlite** есть коварная особенность: middleware может проверить разрешения раньше, чем транзакция успела закоммититься. Получилась гонка условий — бот получал `/manage add`, начинал записывать в БД, но его собственная система контроля доступа успевала выполнить проверку за доли секунды до того, как данные попали в таблицу. Казалось бы, логические ошибки не могут быть незаметны в коде, но тут они проявились только в полевых условиях. Вторая проблема — SQLite при одновременной работе нескольких асинхронных обработчиков. Один handler записывал изменение в БД, а другой в это время проверял состояние — и видел старые данные, потому что `commit()` ещё не произошёл. Гарантировать консистентность мне помогли явные транзакции и аккуратная работа с await'ами. Вот в чём прелесть интеграционного тестирования: ты отправляешь реальное сообщение через Telegram-серверы, оно проходит через webhook, пробегает весь стек middleware, обрабатывается обработчиком, записывается в БД и возвращается пользователю. Unit-тесты проверяют логику функции. Интеграционные тесты проверяют, работает ли всё это вместе в реальности. И оказалось, что между «работает в тесте» и «работает в реальности» огромная разница. После всех боевых испытаний я заполнил чеклист: проверка импортов класса, валидация миграции, тестирование всех команд в Telegram, запуск полного набора pytest, документирование в `docs/CHAT_MANAGEMENT.md` с примерами и описанием архитектуры. Восемь пунктов — восемь потенциальных точек отказа, которые благополучно миновали. Урок на будущее: когда работаешь с асинхронностью и базами данных, unit-тесты — это необходимо, но недостаточно. Реальный Telegram, реальные пользователи, реальная асинхронность покажут то, что никогда не отловить в тестовом окружении. 😄 Иногда мне кажется, что в облаке **GCP** ошибка при доступе просто уходит в облака, так что никто её не найдёт.

#claude#ai#python#git#api#security
9 февр. 2026 г.
Новая функцияC--projects-bot-social-publisher

Когда unit-тесты зелёные, а бот падает в продакшене

# Проверяем Telegram-бота в боевых условиях: когда unit-тесты врут Любой разработчик знает эту ситуацию: твой код прошёл все тесты в PyTest, green lights светят, CI/CD улыбается. Но стоит запустить приложение в реальной среде — и вдруг выскакивают проблемы, которые перестанут выглядеть как волшебство, как только ты их найдёшь. Со мной произошла именно эта история на проекте **bot-social-publisher**, когда я добавил в Telegram-бота систему управления доступом. ## Задача казалась элементарной Надо было реализовать для бота фишку с приватными чатами. Идея простая: если владелец чата напишет `/manage add`, бот переходит в режим приватности и начинает отвечать только ему. Команда `/manage remove` открывает доступ всем обратно. Плюс туда же добавил `/recall` и `/remember` для сохранения истории разговоров. На бумаге всё выглядело как три строки кода в middleware'е, которые проверяют ID пользователя перед обработкой сообщения. Я написал unit-тесты, всё прошло. Но реальный Telegram — совсем другой зверь. ## Боевые испытания в реальной среде Первым делом поднял бота локально через `python telegram_main.py` и начал его "пилить" из реального Telegram аккаунта. Написал `/manage add` — бот записал ID чата в таблицу `managed_chats` в SQLite и переключился в режим приватности. Проверил middleware `permission_check.py` — всё срабатывает корректно, обработка заблокирована для чужих. Хорошо. Потом попросил друга написать то же самое сообщение со своего аккаунта. Ожидал — ничего не случится. И действительно, бот промолчал. Отлично, система работает как надо. Финальный тест: я написал `/manage remove`, друг снова отправил сообщение — и бот ответил. Приватность отключена, доступ восстановлен. Казалось бы, победа. Но потом обнаружилась подвох. ## Гонка условий в асинхронном коде Оказалось, что в асинхронной архитектуре **aiogram** есть коварная особенность: middleware проверяет доступ, а запись в БД может ещё не завершиться. Получилась гонка условий — команда `/manage add` срабатывала, но контроль доступа успевал проверить разрешения *до* того, как данные попали в таблицу. Пришлось оборачивать insert'ы в explicit `await`, чтобы гарантировать консистентность. Другая проблема с SQLite: при одновременной работе нескольких асинхронных обработчиков изменения одного из них могут быть не видны другим, пока не произойдёт `commit()`. Контроллер доступа проверял одно, а в реальности БД содержала совсем другое. Решение было банальным — явные транзакции, но выяснить это можно было только через реальное тестирование. ## Познавательный момент об асинхронности Здесь скрывается типичная ловушка разработчиков, переходящих с синхронного кода на async/await: асинхронный код **кажется** последовательным в написании, но на самом деле может выполняться в самых неожиданных порядках. Когда ты пишешь `await db.execute()`, это не значит, что все предыдущие операции уже завершены в других корутинах. Нужна явная синхронизация через контекстные менеджеры или явные commit'ы. ## Итог: документируем опыт После всех интеграционных тестов я задокументировал находки в `docs/CHAT_MANAGEMENT.md`, добавил примеры использования в README.md и описал полную архитектуру ChatManager'а. Теперь система готова к работе с приватными чатами и конфиденциальными данными. Главный урок: unit-тесты проверяют логику в вакууме, но реальный мир полон асинхронности, сетевых задержек и race conditions. Никакой PyTest не найдёт то, что видно только в продакшене. Поэтому перед тем, как праздновать зелёный CI/CD, всегда имеет смысл руки испачкать в реальной среде. 😄 Что говорит разработчик после запуска асинхронного кода? «У меня было семь ошибок, теперь их четырнадцать, но они более интересные».

#claude#ai#python#git#api
9 февр. 2026 г.
Новая функцияai-agents

Четыре инструмента вместо двух: как мы освободили AI-агентов от ограничений

# От изоляции к открытости: как мы расширили доступ к файловой системе в AI-агентах Работал я над проектом **ai-agents** — платформой для создания интеллектуальных помощников на Python. И вот в какой-то момент нас настигла настоящая боль роста: агенты работали в тесной клетке ограничений. ## Проблема: узкие границы доступа Представь ситуацию. У нас была виртуальная файловая система — и звучит круто, пока не начнёшь с ней работать. Агенты могли читать только три папки: `plugins/`, `data/`, `config/`. Писать вообще не могли. Нужно было создать конфиг? Нет. Сохранить результат работы? Нет. Отредактировать существующий файл? Снова нет. Это было словно программировать с одной рукой, привязанной за спину. Функциональность `file_read` и `directory_list` — хорошо, но недостаточно. Проект рос, требования расширялись, а система стояла на месте. ## Решение: четыре инструмента вместо двух Первым делом я понял, что нужно идти не путём костылей, а переписать модуль **filesystem.py** целиком. Вместо двух полуслепых инструментов создал четыре полнофункциональных: - **`file_read`** — теперь читает что угодно в проекте, до 200 килобайт - **`file_write`** — создаёт и перезаписывает файлы, автоматически создаёт директории - **`file_edit`** — тонкая работа: находит точную подстроку через find-and-replace и заменяет - **`directory_list`** — гибкий листинг: поддерживает glob-паттерны и рекурсию Но тут появилась вторая проблема: как дать свободу, но не потерять контроль? ## Безопасность: ограничения, которые действительно работают Всё звучит опасно, пока не посмотришь на механизм защиты. Я добавил несколько слоёв: Все пути привязаны к корню проекта через `Path.cwd()`. Выбраться наружу невозможно — система просто не позволит обратиться к файлам выше по дереву директорий. Плюс чёрный список: система блокирует доступ к `.env`, ключам, секретам, паролям — ко всему, что может быть опасно. А для дополнительной уверенности я добавил проверку path traversal через `resolve()` и `relative_to()`. Получилась архитектура, где агент может свободно работать внутри своей песочницы, но не может ей повредить. ## Интересный момент: почему это важно Знаешь, в истории компьютерной безопасности есть забавный парадокс. Чем больше ты запрещаешь, тем больше люди ищут обходные пути. А чем правильнее ты даёшь разрешения — с умными ограничениями — тем спокойнее всем. Unix-философия в действии: дай инструменту ровно столько мощи, сколько нужно, но убедись, что он не сможет что-то сломать. ## Итого Переписал модуль, обновил константы в **constants.py**, экспортировал новые классы в **\_\_init\_\_.py**, подключил всё в **core.py** и **handlers.py**. Проверил сборку — зелёная лампочка. Теперь агенты могут полноценно работать с проектом, не боясь случайно удалить что-то важное. Дальше планировали тестировать на реальных сценариях: создание логов, сохранение состояния, динамическая генерация конфигов. А пока что у нас есть полнофункциональная и безопасная система для работы с файлами. Мораль истории: не выбирай между свободой и безопасностью — выбирай правильную архитектуру, которая обеспечивает оба.

#claude#ai#python#git#security
9 февр. 2026 г.