From 5fa5a391d70e3d894830414d347bda23a8d5df3b Mon Sep 17 00:00:00 2001 From: vrubelroman Date: Thu, 17 Sep 2026 21:42:30 +0000 Subject: [PATCH] Remove AGENT_TEAM.md, add project baseline analytics The agent team workflow lives in .opencode/agent role files; the human-readable guide is redundant and removed. README and analyst role updated accordingly. Added analytics baseline documenting the current project state as a starting point for future tasks. --- .opencode/agent/analyst.md | 2 +- AGENT_TEAM.md | 31 ----- README.md | 10 +- analytics/2026-09-17-project-baseline.md | 120 ++++++++++++++++++ .../2026-09-17-remove-agent-team-guide.md | 49 +++++++ 5 files changed, 174 insertions(+), 38 deletions(-) delete mode 100644 AGENT_TEAM.md create mode 100644 analytics/2026-09-17-project-baseline.md create mode 100644 analytics/2026-09-17-remove-agent-team-guide.md diff --git a/.opencode/agent/analyst.md b/.opencode/agent/analyst.md index 1930670..0b70b11 100644 --- a/.opencode/agent/analyst.md +++ b/.opencode/agent/analyst.md @@ -9,7 +9,7 @@ You are the task analyst in an opencode agent team. The Orchestrator sends you a ## Your job -1. Ground the task in reality: read `AGENTS.md`, `AGENT_TEAM.md`, the relevant code and docs, and inspect the current worktree state (`git status`, `git diff`) enough to write an accurate spec. +1. Ground the task in reality: read `AGENTS.md`, the Orchestrator role (`.opencode/agent/orchestrator.md`), `analytics/README.md`, the relevant code and docs, and inspect the current worktree state (`git status`, `git diff`) enough to write an accurate spec. 2. Create or update the task's analytics document under `analytics/`: - One file per task: `analytics/-.md`. - Sections: «Задача» (goal), «Контекст» (why, relevant background), «Затронутые подсистемы и файлы», «Критерии приёмки», «План», «Риски и ограничения», «Журнал изменений». diff --git a/AGENT_TEAM.md b/AGENT_TEAM.md deleted file mode 100644 index a4155f8..0000000 --- a/AGENT_TEAM.md +++ /dev/null @@ -1,31 +0,0 @@ -# Агентская команда opencode - -Одна сессия opencode в роли **Orchestrator** и четыре субагента — **Analyst**, **Coder**, **Reviewer**, **Tester**, которых Orchestrator вызывает через Task tool. Роли лежат в [`.opencode/agent/`](./.opencode/agent/), Orchestrator назначен агентом по умолчанию в [`opencode.json`](./opencode.json). - -## Запуск и работа - -В корне репозитория запусти `opencode`. Каждая новая сессия стартует Orchestrator'ом: задачу пиши обычным сообщением. Он сам: - -- декомпозирует задачу и сначала отправляет её Analyst'у (фиксирует в `analytics/*.md`), затем Coder'у через Task tool; -- после Coder'а запускает Reviewer (только чтение) и Tester (проверка без правок) параллельно; -- возвращает Coder'у actionable findings; при существенном изменении скоупа перед follow-up просит Analyst'а дополнить аналитику (журнал), повторяет цикл, пока Critical/Major не закрыты; -- когда Critical/Major закрыты и проверки зелёные — без вопроса деплоит на тестовый сервис (`docker compose up -d --build`, затем `curl http://localhost:8080/api/health`); -- отдаёт финальный отчёт уже после деплоя: что написано, просмотрено и протестировано, оставшиеся риски и просьба проверить в браузере с очисткой кэша (Ctrl+Shift+R). - -Вручную писать субагентам не нужно — их вызывает только Orchestrator. - -## Роли - -- **Orchestrator** (primary, агент по умолчанию) — единственный общается с пользователем; сам код не правит. -- **Analyst** (subagent) — перед Coder'ом фиксирует задание в `analytics/*.md`, дополняет его по ходу работы (правки только затронутых секций, документ целиком не переписывается), при необходимости актуализирует `README.md`; отчитывается `ANALYST_DONE`. -- **Coder** (subagent) — единственный меняет код продукта; отчитывается `CODER_DONE`. -- **Reviewer** (subagent, `edit: deny`) — read-only review; отчитывается `REVIEW_DONE`. -- **Tester** (subagent, `edit: deny`) — прогоняет проверки; может создавать игнорируемые артефакты, но не меняет отслеживаемые файлы; отчитывается `TEST_DONE`. - -## Файлы - -- `.opencode/agent/{orchestrator,analyst,coder,reviewer,tester}.md` — определения агентов команды; -- `analytics/` — аналитика задач агентской команды (по одному файлу на задачу; конвенция в `analytics/README.md`); -- `opencode.json` — `default_agent: orchestrator`. - -Конфиг и агенты читаются при старте opencode: после изменений перезапусти сессию. diff --git a/README.md b/README.md index 0c164a7..919c25b 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # MyYouTube Персональный YouTube-клиент с категориями подписок и интеграцией с существующим MeTube. -Работа агентской команды (opencode) описана в [руководстве по агентской команде](./AGENT_TEAM.md). +Работа агентской команды (opencode) описана в секции «Агентская команда (opencode)» ниже; роли агентов — в `.opencode/agent/`. Текущий статус: **функционально готово** (основные этапы 1–6 и доработки по итогам тестирования). Развёрнуто и вручную протестировано на тестовом окружении (`testmyyoutube.vrubel.xyz` → hermesVM `192.168.8.173` → MeTube на mediaVM `192.168.8.177`). @@ -47,8 +47,6 @@ ### Агентская команда (opencode) -Подробная инструкция: [AGENT_TEAM.md](./AGENT_TEAM.md). - Работа идёт в одной сессии opencode: она стартует агентом `orchestrator` (`default_agent` в `opencode.json`). Порядок работы: `orchestrator → analyst (аналитика/README) → coder → reviewer → tester`. @@ -56,7 +54,8 @@ Orchestrator сам декомпозирует задачу и вызывает `analyst` (до Coder'а фиксирует задачу в `analytics/*.md` и при необходимости актуализирует README), `coder` (единственный меняет код продукта), `reviewer` (read-only review, `edit: deny`) и `tester` (проверки без правок, `edit: deny`). -Роли описаны в [`.opencode/agent/`](./.opencode/agent/). +Роли описаны в [`.opencode/agent/`](./.opencode/agent/). Аналитика задач +ведётся в [`analytics/`](./analytics/) (см. `analytics/README.md`). ```bash opencode @@ -117,8 +116,7 @@ myyoutube/ ├── Dockerfile ├── compose.yml ├── .env / .env.example -├── .opencode/ # агенты команды (orchestrator/analyst/coder/reviewer/tester) -└── AGENT_TEAM.md # руководство по агентской команде opencode +└── .opencode/ # агенты команды (orchestrator/analyst/coder/reviewer/tester) ``` ## Секреты diff --git a/analytics/2026-09-17-project-baseline.md b/analytics/2026-09-17-project-baseline.md new file mode 100644 index 0000000..9d52c70 --- /dev/null +++ b/analytics/2026-09-17-project-baseline.md @@ -0,0 +1,120 @@ +# Базовый документ проекта (baseline) + +## Задача + +Зафиксировать фактическое состояние проекта MyYouTube на 2026-09-17 как точку отсчёта для будущих задач агентской команды. Документ описывает: обзор, архитектуру, интеграции, ключевые решения с причинами из git-истории, известные ограничения/техдолг и устройство команды агентов. Факты сверены с кодом, `README.md`, `AGENTS.md`, миграциями и `git log`. + +## 1. Обзор проекта + +Персональный single-user YouTube-клиент с категориями подписок и интеграцией с существующим MeTube. Скачивание выполняет MeTube на `mediaVM`; этот сервис ничего не скачивает сам и не хранит видео локально (только проксирует/линкует `media_url`). + +- **Статус:** функционально готово. Этапы 1–6 реализованы (каркас, OAuth, категории, синк подписок/видео, лента, воспроизведение, MeTube-интеграция), плюс доработки по итогам live-тестирования. Развёрнуто на тестовом окружении: `testmyyoutube.vrubel.xyz` → hermesVM `192.168.8.173` (порт 8080) → MeTube на mediaVM `192.168.8.177:8081`. +- **Стек:** Python 3.13 + FastAPI + SQLAlchemy 2.x + Alembic + Pydantic Settings + APScheduler; React 19 + TypeScript + Vite 8 + React Router 7 + TanStack Query 5; PostgreSQL 16; Docker Compose. +- **Масштаб:** single-user — вход разрешён только `ALLOWED_GOOGLE_EMAIL`; фронтенд не ходит в Google API и MeTube write API напрямую (только через backend). +- **Реализованные возможности:** Google OAuth (allow-list) → синк подписок (APScheduler) и видео (по активности); категории (CRUD, reorder, many-to-many с каналами, постоянные URL); лента с курсорной пагинацией, поиском по видео/каналам и фильтром «только новые»; YouTube-плеер; MeTube-интеграция (download/delete, Socket.IO live-статусы, recovery после рестарта); раздел «На сервере» (`/local`, перенаправление с `/saved`); отписка на YouTube; удаление скачанной копии; тёмный адаптивный UI с мобильным меню; скрытие отписанных каналов; счётчики подписчиков и бейджи «N новых» (окно 2 дня). +- **Быстрая проверка (prod):** `docker compose up -d --build` → `curl http://localhost:8080/api/health` (возвращает `status`/`database`/`metube`). +- **Состояние worktree на момент фиксации:** ветка `master` впереди `origin/master` на 1 коммит (f0ca7b9 не запушен); в работе документационная задача «Удалить AGENT_TEAM.md» (`analytics/2026-09-17-remove-agent-team-guide.md`, staged-удаление `AGENT_TEAM.md`, правки `README.md` и `.opencode/agent/analyst.md`). + +## 2. Архитектура + +### Backend (`backend/app/`) + +- **`main.py`** — FastAPI app: `SessionMiddleware` (cookie `myyoutube_session`, 30 дней, `same_site=lax`, `https_only` по `APP_BASE_URL`), lifespan = reconcile download jobs по `/history` MeTube + старт APScheduler + Socket.IO-листенер MeTube (переподключения). Статика собранного фронта монтируется в `/` с fallback на `index.html` (`SPAStaticFiles`). +- **`api/`** (группы, кроме `auth/status`, `auth/google/*` и `health` — за `require_session`): + - `health` — `GET /api/health` (status, database, metube). + - `auth` — `GET /auth/status`, `/auth/google/start`, `/auth/google/callback` (state-проверка, allow-list, background initial sync), `POST /auth/logout`. + - `channels` — `GET /channels` (фильтры `subscribed/search/category_id/uncategorized`), `GET /channels/{id}`, `PUT /channels/{id}/categories`, `POST /channels/{id}/unsubscribe` (реальная отписка на YouTube). + - `categories` — CRUD + `POST /categories/reorder` (slug-генерация, проверка дублей имён). + - `feed` — `GET /feed` (курсорная пагинация base64 `published_at|id`, фильтры category/uncategorized/channel/downloaded/search/new_only, лимит 30/100), `GET /feed/saved-counts`. + - `videos` — `GET /videos/{youtube_video_id}`, `POST .../download`, `DELETE .../download`, `GET .../download-status`, `POST .../recheck-local`. + - `sync` — `POST /sync/subscriptions`, `POST /sync/videos`, `GET /sync/status`. +- **`services/`** — `google_oauth` (flow, allow-list, кэш токена), `youtube_client` (все вызовы YouTube API, ошибки квоты/scope), `sync` (подписки и видео, thread-lock'и), `sync_trigger` (синк по активности), `scheduler` (APScheduler, только подписки), `metube_client` (HTTP+Socket.IO к MeTube), `download_jobs` (машина состояний, reconcile), `state` (AppSetting get/set), `video_presentation` (сериализация). +- **`core/`** — `auth_dependency.require_session` (+триггер синка видео), `crypto` (Fernet для refresh token), `duration` (ISO 8601 → секунды), `slugify`. +- **Модели БД:** `AppSetting` (key/value — статусы синков), `OAuthCredentials` (singleton `id=1`, `encrypted_refresh_token`), `Channel` (`youtube_channel_id`, `youtube_subscription_id`, `uploads_playlist_id`, `subscriber_count`, `subscribed`, `last_synced_at`), `Video` (`youtube_video_id` unique, `published_at`, `duration_seconds`, `youtube_url`), `Category` (name/slug unique, `sort_order`), `channel_categories` (m2m, PK-пара), `DownloadJob` (статусы `queued/downloading/postprocessing/completed/failed/deleted/unknown`, `metube_job_id`, `metube_filename`, `media_url`). +- **Миграции (Alembic, `migrations/versions/`):** 0001 `app_settings` → 0002 `oauth_credentials` + `channels` → 0003 `categories` + `channel_categories` → 0004 `videos` (+индексы) → 0005 `download_jobs` → 0006 `channels.youtube_subscription_id` → 0007 drop `access_token_expires_at` (мёртвая после in-process кэша) → 0008 `channels.subscriber_count` (BigInteger, из `statistics`). + +### Frontend (`frontend/src/`) + +- **Роуты (`App.tsx`):** `/` Feed, `/category/:categoryId`, `/uncategorized`, `/local` (скачанные), `/search`, `/saved` → redirect `/local`, `/channels`, `/channels/:channelId/videos`, `/video/:youtubeVideoId`, `/settings`, `/settings/categories` (и `/categories` → redirect), 404. До входа — страница `Connect`. +- **Страницы:** `Connect`, `Feed` (infinite-query, фильтры, бейджи новых, кнопка «Обновить»), `Channels` (поиск, `CategoryNav`, назначение категорий, отписка), `ChannelVideos` (счётчик подписчиков), `VideoPage`, `Categories` (CRUD, reorder, confirm-диалог), `Settings` (health MeTube, статусы синков, переподключение, logout). +- **Компоненты:** `AppShell` (сайдбар с категориями и бейджами новых, поиск по видео/каналам, индикатор синка, мобильное drawer-меню), `VideoCard`, `ChannelCard` (popover категорий, создание категории на лету), `DownloadButton` (polling 2 с только для активных статусов), `Player` (локальная копия через `media_url`, при ошибке — fallback на iframe `youtube-nocookie.com` + `recheck-local`), `Icon`. +- **API-клиент** — `api/client.ts`, fetch с `credentials: include`, все эндпоинты типизированы; `utils/format.ts` — форматирование длительности/времени/счётчиков подписчиков. +- **Стили/состояние:** `App.css`/`index.css` (тёмная адаптивная тема); TanStack Query с invalidation по ключам `['feed']`, `['channels']`, `['categories']`, `['download-status', id]` и т.д.; позиция скролла ленты восстанавливается через `sessionStorage` (`feed-scroll:*`). + +### Деплой + +- **`Dockerfile`** — multi-stage: node:22-slim собирает фронт (`npm ci && npm run build`), python:3.13-slim — бэкенд; dist → `backend/static`; **non-root** пользователь `uid 10001`; `EXPOSE 8080`. +- **`entrypoint.sh`** — `alembic upgrade head` → `uvicorn app.main:app` (`--proxy-headers --forwarded-allow-ips="*"`, порт `APP_PORT`). +- **`compose.yml`** — `app` (env_file `.env`, healthcheck `curl /api/health`) + `postgres:16` (volume `postgres_data`, healthcheck `pg_isready`). Миграции применяются при старте контейнера. +- **`.env`** — секреты: `APP_SECRET_KEY`, `TOKEN_ENCRYPTION_KEY` (Fernet, менять нельзя), `GOOGLE_CLIENT_SECRET`, `POSTGRES_PASSWORD`; все внешние URL (Google/YouTube/MeTube) и параметры синка — в env (см. `.env.example`). +- **Локальная разработка:** backend — `uv venv`/`uv pip install -r requirements-dev.txt` + `alembic upgrade head` + `uvicorn app.main:app --app-dir backend --port 8080`; тесты — `pytest tests/ -v` (SQLite in-memory, Google/MeTube mock). Frontend — `npm install` + `npm run dev`, Vite проксирует `/api/*` на `127.0.0.1:8080` (`vite.config.ts`); lint — `oxlint`, сборка — `tsc -b && vite build`. + +### Ключевые настройки (`config.py`, все переопределяются env) + +- Google/YouTube endpoints: `google_auth_uri`, `google_token_uri`, `google_userinfo_uri`, `google_revoke_uri`, `youtube_api_base_url` (`https://www.googleapis.com/youtube/v3`), `youtube_watch_url_template`. +- MeTube: `metube_api_base_url` (дефолт `http://127.0.0.1:8081`, реально `http://192.168.8.177:8081`), `metube_public_base_url` (для браузера), `metube_container_download_dir` (`/downloads`), `metube_request_timeout_seconds` (30). +- Синк: `subscriptions_sync_interval_hours=6` (APScheduler), `videos_sync_idle_hours=2` (триггер по активности), `videos_backfill_cap=200`, `videos_known_stop_threshold=50`, `new_videos_window_days=2`. + +## 3. Интеграции + +### Google OAuth / синк + +- **Scope:** полный `https://www.googleapis.com/auth/youtube` (read/write — ради `subscriptions.delete` при отписке; сознательное отступление от `youtube.readonly` по явному запросу пользователя) + `openid`, `userinfo.email`, `userinfo.profile`. Старые readonly-токены не покрывают отписку → нужен один переподключение. +- **Single-user:** email из userinfo сверяется с `ALLOWED_GOOGLE_EMAIL`; недопустимый аккаунт — `revoke_token` + редирект с `auth_error=account_not_allowed`. Redirect URI фиксирован: `{APP_BASE_URL}/api/auth/google/callback` (должен быть в Authorized redirect URIs Google-клиента). +- **Кэш токена:** refresh token хранится в БД шифрованным (Fernet, `TOKEN_ENCRYPTION_KEY`); access token — в памяти процесса под `threading.Lock`, проактивный refresh за 60 с до expiry (`OAUTHLIB_RELAX_TOKEN_SCOPE=1` снимает ложный scope-mismatch при расширении scope). Обмен кода на токен с `access_type=offline`, `prompt=consent`. +- **После подключения** — фоновый initial sync (подписки + видео) через `BackgroundTasks`. + +### YouTube API (`youtube_client.py`) + +- Ресурсы: `subscriptions.list` (`mine=true`, пагинация по 50), `channels.list` (`snippet,contentDetails,statistics` — uploads playlist и подписчики, батчи по 50), `playlistItems.list` (`contentDetails`, лимит `MAX_PLAYLIST_PAGES=10` страниц), `videos.list` (`snippet,contentDetails,status`, батчи по 50), `subscriptions.delete`. +- Ошибки → HTTP: `quotaExceeded/dailyLimitExceeded/rateLimitExceeded` → 503 «quota exhausted»; insufficient scope → 403 с подсказкой переподключиться; прочее → 502. +- **Квота (оценка из паттерна вызовов):** видео-синк на канал — 1 юнит `playlistItems.list` + 1 юнит `videos.list` на каждые ≤50 новых id → практически **~2 юнита на канал за синк видео** при малом числе новых видео; синк подписок — 1 юнит `subscriptions.list` на 50 подписок + 1 юнит `channels.list` на 50 каналов. Расход пропорционален числу каналов. + +### MeTube (`metube_client.py`) + +- Весь HTTP/Socket.IO к MeTube инкапсулирован в `MeTubeClient` (правило AGENTS.md): `POST /add` (url, `download_type=video`, codec auto, format mp4, quality best, `auto_start`, `custom_name_prefix` = youtube_video_id), `GET /history`, `POST /delete` (**ключ — URL, не job id**; `where=done`), `build_media_url` (публичный `/download/` с защитой от path traversal), `check_media` (HEAD), `health`. +- **Socket.IO-события** `added/updated/completed/canceled/cleared` (payload — JSON-строка от `DownloadInfo.to_public_dict()`); маппинг статусов MeTube (`pending/preparing/scheduled/downloading/postprocessing/finished/error`) → свои. Только событие `completed` — авторитетный терминальный статус (yt-dlp шлёт `finished` по отдельным потокам → иначе flapping). `media_url` строится из точного `filename` события (не из title) и отдаётся только при `completed`. +- **Recovery:** при старте `reconcile_on_startup` сверяет активные/`unknown` job'ы с `/history`; completed с `media_url` не трогаются. «cleared»/«canceled» матчатся по URL, несовпавшие игнорируются. + +### Процессы синка (`services/sync.py`) + +- **Подписки:** `subscriptions.list` (пагинация) → upsert каналов (заполняется и `youtube_subscription_id` для будущей отписки); каналы, которых нет в ответе API, помечаются `subscribed=False` (скрываются в UI). Затем батчами по 50 `channels.list` добираются `uploads_playlist_id` и `subscriber_count`; неудача этой фазы не валит весь синк. Запуск: APScheduler каждые 6 ч + ручной `POST /sync/subscriptions`; параллельный запуск блокируется `threading.Lock` → 409 `SyncInProgress`. +- **Видео:** по каждому подписанному каналу с uploads-плейлистом — инкрементальный `playlistItems.list` (ранняя остановка на 50 подряд известных, кап 200 новых на канал, максимум 10 страниц на плейлист); известные id собраны заранее одним запросом к БД (без N+1). Для неизвестных id — `videos.list` батчами по 50 и insert. Существующие видео не обновляются. Запуск: триггер по активности (`maybe_trigger_videos_sync` в `require_session`, idle ≥2 ч, фоновый поток) + ручной `POST /sync/videos`. +- **Статусы синков** персистятся в `app_settings` (JSON: status/started_at/finished_at/error + счётчики), отдаются через `GET /api/sync/status` (плюс флаг `running` из lock'а); UI опрашивает каждые 2 с пока идёт синк, иначе раз в 60 с. + +## 4. Ключевые решения и политики (из git log) + +- **2026-09-16, `0ed20bb`** — этапы 1–5: каркас, OAuth, категории, синк видео/лента, воспроизведение. +- **2026-09-16, `fe16c08`** — этап 6: MeTube-интеграция (download/delete, live-статусы). +- **2026-09-16, `e333296`/`10c16ba`** — статусы загрузки: терминален только `completed`; кнопка не откатывается в «Скачать» после завершения. +- **2026-09-16, `3089202`+`8ddea87`** — реальная отписка (`subscriptions.delete`) и удаление скачанного — сверх MVP по запросу пользователя; из-за этого scope расширен до полного `youtube` (потребовался `OAUTHLIB_RELAX_TOKEN_SCOPE`). +- **2026-09-16, `62f8fb7`/`441a3ef`** — `DELETE /videos/{id}/download`: ключ `/delete` — URL (по id — тихий no-op); после запроса проверяем фактическое удаление файла (настройка `DELETE_FILE_ON_TRASHCAN` MeTube вне нашего контроля → 409). +- **2026-09-16, `b29bcfb`…`90eb012`** — отписанные каналы скрываются; вкладка Saved; счётчики каналов/сохранённых в сайдбарах. +- **2026-09-16, `c4a772b`** — тёмный адаптивный редизайн интерфейса. +- **2026-09-16, `6c704ca`** — переход на opencode-команду агентов (Codex/Herdr выброшены). +- **2026-09-17, `fde9a43`** — review-фиксы: in-process кэш access token (+миграция 0007), обработка cleared/canceled по URL, email в `/auth/status` только при валидной сессии, Google base URLs в настройки, **non-root контейнер**; убран «Без категории» из сайдбара (фильтр остался на странице каналов), локальные фильтры в query key, Content-Type только с телом. +- **2026-09-17, `e10df8d`** — канальные фичи: подписчики из `statistics` (0008), бейджи «N новых» (окно **`NEW_VIDEOS_WINDOW_DAYS=2`**), **синк видео по активности** (idle **`VIDEOS_SYNC_IDLE_HOURS=2`** вместо ежечасного планировщика), **инкрементальная догрузка**: ранняя остановка после **50 подряд известных** id (`VIDEOS_KNOWN_STOP_THRESHOLD`), жёсткий кап **200 последних видео на канал** (`VIDEOS_BACKFILL_CAP`); клик по бейджу фильтрует категорию (`new_only`). +- **2026-09-17, `2e5ef8e`** — `.env.example` с комментариями к каждому полю. +- **2026-09-17, `f0ca7b9`** — введён субагент Analyst и конвенция `analytics/`. + +## 5. Известные ограничения и техдолг + +- **Квота YouTube:** основной расход — по каналу за видео-синк (~1–2 юнита, см. п. 3); при текущем числе подписок практический запас — **порядка 2–4 полных синков видео в день** (оценка, зависит от числа каналов). Исчерпание → 503, обновления пропускаются до сброса суточной квоты; ручной «Обновить» в UI не «пробьёт» квоту. +- **Браузерная проверка только пользователем:** в команде нет Playwright/скриншот-инструментов; reviewer/tester честно помечают визуальное как неверифицированное, финальную визуальную приёмку делает пользователь после деплоя. +- **Edge-кейсы MeTube:** удаление записи вне нашего приложения (`cleared` done-записи) матчится по URL и может быть не связано с нашими job'ами; re-download после удаления создаёт новый `DownloadJob`, значимым считается последний; после рестарта активные job'ы сверяются с `/history` (иначе → `unknown`, пользователь видит «Загрузить снова»). +- **Отсутствует CI и E2E:** тесты — только backend `pytest` (`tests/`, 12 файлов, SQLite in-memory, Google/MeTube замоканы; реальные вызовы к MeTube — opt-in). GitHub Actions нет, E2E/снапшотов нет. +- **Single-worker lock:** защита от параллельных синков — `threading.Lock` в одном процессе (uvicorn запускается одним worker'ом); при scale-out и триггер по активности, и блокировки перестанут быть глобальными — сознательное упрощение single-user сервиса. +- **Синк видео не обновляет метаданные** уже известных видео (`videos_updated` всегда 0, обновляются только новые id). +- **Сознательно не реализовано:** YouTube Recommendations/Home (запрещено), PWA, watch later, Shorts-фильтр, SponsorBlock; полная история канала не бэкапится (кап 200). +- **Удаление файла на диске** зависит от `DELETE_FILE_ON_TRASHCAN` MeTube (вне нашего контроля, правило 11); чужие (не наши) файлы MeTube не удаляем никогда (правило 10). + +## 6. Команда агентов + +- **Конфиг:** `opencode.json` → `default_agent: orchestrator`. Работа — в одной сессии opencode; задача пишется обычным сообщением. +- **Роли (`.opencode/agent/`):** `orchestrator` (primary; декомпозиция, единственный говорит с пользователем, собирает отчёт, деплой на тестовый сервис `docker compose up -d --build` после закрытия Critical/Major), `analyst` (subagent; только документация `analytics/**` и точечные правки `README.md`), `coder` (subagent; единственный меняет код продукта), `reviewer` (subagent, `edit: deny`; read-only review диффа, severity Critical/Major/Minor), `tester` (subagent, `edit: deny`; прогон проверок, ничего не чинит). +- **Цикл:** `orchestrator → analyst (фиксация задачи) → coder → reviewer + tester (параллельно) → follow-up по находкам → деплой → отчёт пользователю`. Отчёты субагентов заканчиваются маркерами `ANALYST_DONE` / `CODER_DONE` / `REVIEW_DONE` / `TEST_DONE`. Коммит/пуш — только по явному запросу. +- **Конвенция `analytics/` (`analytics/README.md`):** один файл на задачу `analytics/-.md`; секции: Задача, Контекст, Затронутые подсистемы и файлы, Критерии приёмки, План, Риски и ограничения, Журнал изменений; follow-up дополняет существующий файл (журнал + только затронутые секции); документы на русском, кратко, но полно; код не пишется. + +## Журнал изменений + +- 2026-09-17 — создан baseline-документ (Analyst). Сверено с кодом: модули backend/frontend, все миграции 0001–0008, настройки `.env.example`/`config.py`, `git log -25`, роли агентов. diff --git a/analytics/2026-09-17-remove-agent-team-guide.md b/analytics/2026-09-17-remove-agent-team-guide.md new file mode 100644 index 0000000..1b01573 --- /dev/null +++ b/analytics/2026-09-17-remove-agent-team-guide.md @@ -0,0 +1,49 @@ +# Удалить AGENT_TEAM.md и почистить ссылки на него + +## Задача + +Удалить `AGENT_TEAM.md` из репозитория и убрать/заменить все ссылки на него в `README.md` и `.opencode/agent/analyst.md`. Задача чисто документационная: код продукта не меняется. + +## Контекст + +`AGENT_TEAM.md` — человекочитаемое руководство по агентской команде (opencode). Оно стало избыточным: рабочий цикл и правила команды теперь описаны в `.opencode/agent/orchestrator.md` (порядок `orchestrator → analyst → coder → reviewer → tester`, правила деплоя) и в ролях субагентов (`.opencode/agent/analyst.md`, `coder.md`, `reviewer.md`, `tester.md`). Краткое описание команды для человека остаётся в `README.md` (секция «Локальная разработка → Агентская команда (opencode)», строки 48–67) — её нужно сохранить, убрав из неё ссылку-инструкцию на удаляемый файл. + +Рабочее дерево чистое, ветка `master` впереди `origin/master` на 1 коммит. Коммит/пуш не входят в скоуп задачи. + +## Затронутые подсистемы и файлы + +Проверено `rg AGENT_TEAM --hidden --no-ignore` — в репозитории ровно 4 упоминания (плюс сам файл): + +1. `AGENT_TEAM.md` (корень репозитория) — удалить целиком. +2. `README.md:4` — интро: «Работа агентской команды (opencode) описана в [руководстве по агентской команде](./AGENT_TEAM.md).» — переформулировать без ссылки (либо убрать предложение). +3. `README.md:50` — «Подробная инструкция: [AGENT_TEAM.md](./AGENT_TEAM.md).» в секции «Агентская команда (opencode)» — убрать строку; остальное описание команды (строки 52–67) сохранить, при необходимости дополнить упоминанием папки `analytics/` (аналитика задач, ведёт Analyst). +4. `README.md:121` — дерево проекта: `└── AGENT_TEAM.md # руководство по агентской команде opencode` — убрать строку. +5. `.opencode/agent/analyst.md:12` — шаг 1: «read `AGENTS.md`, `AGENT_TEAM.md`, the relevant code and docs…» — заменить `AGENT_TEAM.md` на актуальные источники (например, `README.md` и/или `.opencode/agent/`), оставив смысл шага. + +## Критерии приёмки + +- `AGENT_TEAM.md` удалён из репозитория. +- Нигде не осталось ссылок на удалённый файл: `rg AGENT_TEAM` по `README.md`, `.opencode/`, `backend/`, `frontend/` и т.д. — 0 совпадений (упоминания имени файла в тексте самой этой аналитики не считаются). +- В `README.md` ссылок на `AGENT_TEAM.md` не осталось; секция «Агентская команда (opencode)» сохранена и при необходимости дополнена упоминанием `analytics/`; дерево проекта не содержит удалённого файла. +- В `.opencode/agent/analyst.md` упоминание `AGENT_TEAM.md` заменено/убрано без потери смысла шага 1. +- Код продукта (`backend/`, `frontend/`, `migrations/`, `tests/`, `compose.yml`, `Dockerfile` и т.п.) не тронут. +- `git status` — ожидаемый набор: `deleted: AGENT_TEAM.md`, `modified: README.md`, `modified: .opencode/agent/analyst.md` (+ новый файл этой аналитики). +- Коммит/пуш не выполняется без явного запроса пользователя. + +## План + +1. Удалить `AGENT_TEAM.md`. +2. `README.md`: переформулировать интро (строка 4), убрать строку 50, при необходимости дополнить описание команды упоминанием `analytics/`, убрать строку 121 из дерева проекта. Остальное в README не трогать. +3. `.opencode/agent/analyst.md`: в шаге 1 заменить `AGENT_TEAM.md` на актуальные источники (`README.md`, роли в `.opencode/agent/`). +4. Проверить: `rg AGENT_TEAM` вне `analytics/` → 0 совпадений; `git status` — только ожидаемые файлы. +5. Деплой не требуется: изменены только документация и описание агентов, они не входят в Docker-образы; решение за Orchestrator. + +## Риски и ограничения + +- Не потерять информацию: рабочий цикл после удаления должен оставаться описан в `.opencode/agent/orchestrator.md` + ролях субагентов + секции «Агентская команда» в README. +- Задача документационная — ни один файл продукта не должен попасть в diff. +- Минимальные правки: не рестайлить нетронутые части README/analyst.md. + +## Журнал изменений + +- 2026-09-17 — создан документ задачи (Analyst). Факты сверены с репозиторием: найдено 4 упоминания `AGENT_TEAM` (README ×3, analyst.md ×1) + сам файл.