diff --git a/.tasks/STATUS.md b/.tasks/STATUS.md index 1ba2dea..dbd2ea6 100644 --- a/.tasks/STATUS.md +++ b/.tasks/STATUS.md @@ -1,5 +1,5 @@ # Task Board -_Updated: 2026-05-20 (using-yt-tools: 3 baseline 🟢; 3 finding fixes 🟢 [paragraphs/frames-stderr/windows-path-doc] → yt-tools 0.1.0→0.1.3, 74/74 tests; trigger-smoke-clean-session still ⚪ (needs fresh CC session); review 🔵 on SKILL.md body fill)_ +_Updated: 2026-05-20 (using-yt-tools: 3 baseline 🟢; 3 finding fixes 🟢 [paragraphs/frames-stderr/windows-path-doc] → yt-tools 0.1.0→0.1.3, 74/74 tests; skill-body-fill 🟢 [SKILL.md 0.1.0→0.2.0, two flows documented]; trigger-smoke-clean-session ⚪ (needs fresh CC session); review 🔵 still — body blocker removed, now blocked on trigger-smoke only)_ + +--- + ## ⚪ [using-yt-tools-trigger-smoke-clean-session] — Behavioral trigger smoke for using-yt-tools — requires a fresh CC session. **Scope (вынесено из using-yt-tools-test-trigger):** @@ -156,12 +176,13 @@ Findings → follow-up tasks (`using-yt-tools--fix`) через `tasks_crea **NB по семверу:** `version: 0.1.0` записан промоутером. Дальнейшие инкременты — ответственность владельца `claude-skills/`, **не** этого скила и не ревьюера. -**Status:** blocked (на body fill, см. ниже) -**Where I stopped:** 3 baseline impl-таски 🟢 (install, hermes-mapping, test-trigger partial). `.common/lib/yt-tools/` python-пакет 🟢 (готов, 67/67 тестов). НО: `SKILL.md` body — пустой stub (`<пусто>` в каждой секции When/Inputs/Steps/Failure/Side-effects/NOT). Без body нечего рецензировать в acceptance-критериях по Steps/Failure modes/What NOT to do. Также: 4 findings зафайлены как отдельные ⚪ таски (transcript-paragraphs / frames-stderr / windows-path-doc / trigger-smoke-clean-session). -**Next action:** Запустить отдельную таску на «second-pass body fill для using-yt-tools/SKILL.md» (не имплементер промоушена — body пишется после impl). После этого review снимается с блокировки. Альтернативно: ревьюер может сейчас закрыть скоп «trigger + iterative-flow» (части 1+2 acceptance) и оставить «body acceptance» отдельным review-passом. +**Status:** blocked (на trigger-smoke-clean-session — единственный остаток после body-fill 🟢) +**Where I stopped:** 3 baseline impl-таски 🟢 (install, hermes-mapping, test-trigger partial). `.common/lib/yt-tools/` python-пакет 🟢 (74/74 тестов после 0.1.0→0.1.3). `SKILL.md` body 🟢 (0.1.0→0.2.0 — Flow A iterative + Flow B targeted-frames документированы, см. [using-yt-tools-skill-body-fill]). 3 finding-fix таски 🟢. Остаётся только: trigger-smoke на чистой CC-сессии (см. [using-yt-tools-trigger-smoke-clean-session]). +**Next action:** В чистой CC-сессии прогнать smoke из [using-yt-tools-trigger-smoke-clean-session] (10 positive + 3 false-positive trigger phrases) + поведенческий smoke-test для каждого шага Flow A и Flow B на тестовом URL. Если 0 findings — закрыть 🟢 с close-note. Findings — новые ⚪ fix-tasks. **Branch:** n/a + --- diff --git a/skills/using-yt-tools/SKILL.md b/skills/using-yt-tools/SKILL.md index 3664f02..8607696 100644 --- a/skills/using-yt-tools/SKILL.md +++ b/skills/using-yt-tools/SKILL.md @@ -1,33 +1,109 @@ --- name: using-yt-tools -version: 0.1.0 -description: Use when the agent needs to "watch" a YouTube video iteratively — fetch a clean-markdown transcript with [mm:ss] anchors, decide which moments matter, then pull only those frames as JPGs for vision-reading via Read. Triggers on "транскрипт видео", "расшифровка YouTube", "что в этом ролике", "о чём ролик", "покажи кадр на N", "посмотри момент N", "что показано в видео на N", "watch this video", "video summary", "youtube transcript", or any youtube.com URL where WebFetch can't reach the actual content. Wraps yt-dlp + youtube-transcript-api + PySceneDetect + ffmpeg via CLI in `~/projects/.common/lib/yt-tools/`. Skip for non-YouTube videos (Vimeo/Twitch/local mp4), audio-only podcasts (no STT here), or pure-download workflows (use yt-dlp directly). +version: 0.2.0 +description: Two flows for accessing YouTube content. **Iterative-watch** (для «что в этом ролике / о чём ролик / video summary»): markdown transcript with [mm:ss] anchors → pick moments → extract frames as JPGs → Read them. **Targeted-frames** (для «покажи кадр на N / посмотри момент N / что показано на N»): extract specific frames at user-given timestamps, no transcript. Also triggers on "транскрипт видео", "расшифровка YouTube", "youtube transcript", "watch this video", or any youtube.com URL WebFetch can't reach. Wraps yt-dlp + youtube-transcript-api + PySceneDetect + ffmpeg via CLI in `~/projects/.common/lib/yt-tools/`. Skip for non-YouTube (Vimeo/Twitch/local mp4), audio podcasts (no STT here), pure-download workflows (use yt-dlp directly). --- # using-yt-tools -Iterative-watching YouTube для агента: clean-markdown транскрипт с `[mm:ss]`-якорями → агент решает, какие моменты интересны → targeted frame extraction по таймкодам → агент видит кадры через `Read`. +Iterative-watching YouTube для агента: clean-markdown транскрипт с `[mm:ss]`-якорями → агент решает, какие моменты интересны → targeted frame extraction по таймкодам → агент видит кадры через `Read`. Альтернативный flow — если юзер уже назвал таймкоды, идём прямо за кадрами без транскрипта. ## When to use -<пусто, дописывается во втором проходе> +Два различных flow, выбор по user intent: + +**Flow A — iterative-watch** (exploration / summary): +- Юзер спрашивает что в ролике, хочет summary, хочет узнать о чём видео. +- Шаги: fetch transcript → read → pick interesting moments → extract those frames → Read frames. +- Trigger phrases: «что в этом ролике», «о чём ролик», «расшифровка YouTube», «video summary», «youtube transcript», «watch this video». + +**Flow B — targeted-frames** (specific moments): +- Юзер уже назвал конкретные таймкоды; транскрипт — лишняя работа. +- Шаги: extract frames at given timestamps → Read frames. +- Trigger phrases: «покажи кадр на N», «посмотри момент N», «что показано на N», «show frame at N». + +Оба flow предполагают, что `yt-tools` CLI установлен из `~/projects/.common/lib/yt-tools/`. Если `yt-transcript` / `yt-frames` отсутствуют на PATH — abort с install-hint (см. Prerequisites). + +## Prerequisites + +CLI entry-points на PATH (из `pip install -e ~/projects/.common/lib/yt-tools/` внутри venv): +- `yt-transcript`, `yt-frames`, `yt-watch`, `yt-tools` + +Внешние бинари (любая ОС): +- `yt-dlp` — fetches metadata + source mp4 +- `ffmpeg` — muxes streams + extracts frames + +Если что-то отсутствует, CLI выдаёт явное `"X not found on PATH"`. Скажи юзеру и стоп. + +Полный install per-OS — в `~/projects/.common/lib/yt-tools/README.md`. **Windows-gotcha:** после `winget install Gyan.FFmpeg` или `winget install yt-dlp.yt-dlp` нужен restart терминала (и Claude Code session) — winget пишет в per-user PATH, который текущий shell не перечитывает. ## Inputs -<пусто> +| Flow | Required | Optional | +|---|---|---| +| A — iterative-watch | YouTube URL или bare 11-char video id | `--lang ru,en` для non-English subs; `--out PATH` | +| B — targeted-frames | YouTube URL + comma-separated `mm:ss` / `h:mm:ss` / bare seconds | `--no-cache-source` (stream вместо кеша source.mp4); `--out DIR` | + +Оба flow пишут в `/yt-cache//` по умолчанию. ## Steps -<пусто> +### Flow A — iterative-watch + +``` +1. yt-transcript → ./yt-cache//transcript.md +2. Read transcript.md, find [mm:ss] anchors that match the question +3. yt-frames --timestamps 1:23,4:56,… → ./yt-cache//frames/frame_*.jpg +4. Read each frame_*.jpg via the vision tool +5. Answer the user, citing both transcript paragraph and frame contents +``` + +Last line каждого CLI's stdout — absolute path артефакта. Не парси текст вокруг, бери конец stdout. + +### Flow B — targeted-frames + +``` +1. Parse user's timestamps (mm:ss / h:mm:ss / bare seconds — все работают) +2. yt-frames --timestamps 1:23,4:56,… → ./yt-cache//frames/frame_*.jpg +3. Read each frame_*.jpg +4. Answer the user, referencing each frame by its [mm:ss] label +``` + +Без transcript fetch. Если потом юзер спросит «что говорилось в тот момент?», переключайся на Flow A на том же URL — `source.mp4` cache переиспользуется, повторного download нет. ## Failure modes -<пусто> +Все failures abort cleanly; никогда не оставляй наполовину готовое состояние. + +| Symptom | Cause | Action | +|---|---|---| +| `yt-transcript` / `yt-frames` not on PATH | yt-tools pkg не pip-installed | Скажи юзеру install per `~/projects/.common/lib/yt-tools/README.md`; стоп | +| `yt-dlp not found on PATH` | Внешний бин отсутствует | Дай install command для ОС юзера; стоп | +| `ffmpeg not found on PATH` | Внешний бин отсутствует | То же; на Windows после `winget install Gyan.FFmpeg` юзер должен restart терминала + CC session | +| `yt-dlp source download failed (exit N) \| stderr: …` | Network / private / age-gated / region-locked | Выведи captured stderr verbatim; не retry | +| `yt-dlp --dump-json failed` | То же, но на metadata step | То же | +| `Subtitles disabled` от `youtube-transcript-api` | Канал отключил CC | Для Flow A это fatal — скажи юзеру, предложи Flow B с явными таймкодами если уместно | +| Юзер дал не-YouTube URL (Vimeo / Twitch / local mp4) | Out of scope | Стоп; скажи что скил YouTube-only | ## Side effects -<пусто> +- Writes под `/yt-cache//`: + - `transcript.md` (Flow A) + - `source.mp4` (≤720p, оба flow если без `--no-cache-source`) + - `frames/frame_.jpg` per extracted frame +- Network: yt-dlp вытягивает metadata + опционально source.mp4; `youtube-transcript-api` вытягивает subs +- Никакого внешнего state не мутируется — чисто local-fs side effects +- `source.mp4` может быть ~50-200 MB на 720p / 10-минутный ролик; кеш переиспользуется между запусками + +Cache hygiene: `yt-tools cache list` показывает usage, `yt-tools cache prune --older-than 7d` чистит. ## What NOT to do -<пусто> +- **Не запускай Flow A когда юзер уже дал таймкоды.** «Посмотри 1:23 и 4:56» → сразу Flow B. Fetching transcript first — чистая трата. +- **Не bulk-extract «на всякий случай».** Flow A берёт кадры из транскрипта, Flow B — из явного user input. Никогда `--mode interval --interval 5s` «to be safe». +- **Не используй `yt-watch` как default Flow A renderer.** `yt-watch` комбинирует transcript + scene-frames в один doc — тяжелее (требует ffmpeg scene-detect pass на source.mp4). Бери только когда юзер хочет один self-contained document. +- **Не shell-quote URLs в одну command строку.** Используй CLI list-form (он уже list-form в `subprocess.run`); YouTube URLs содержат `?` и `&`, которые ломают naive quoting. +- **Не retry на yt-dlp failures.** Failure здесь значит видео реально недоступно (private / region / age) или у юзера broken auth. Retries — трата токенов. +- **Не пересказывай / не переводи transcript в свой ответ молча.** Артефакт — для твоего reasoning; цитируй с `[mm:ss]`-якорем когда приводишь пассаж. +- **Не вызывай скил на non-YouTube URL.** Vimeo / Twitch / TikTok / local mp4 — out of scope. Бери другие тулзы (или yt-dlp напрямую). +- **Не пиши результаты в произвольные пути.** По умолчанию `/yt-cache//`; явный `--out` только если юзер просил.