Compare commits

...

2 Commits

Author SHA1 Message Date
1dc286ce9f feat(using-yt-tools): add yt-listen support (audio/FFT) v0.3.2 2026-05-25 13:38:34 +03:00
ffeb95b9cc feat(build): add --prune flag to build.{sh,ps1} [skip-tdd: wrapper]
Symmetric to install-side --prune (e871c20). Removes
dist/<name>.skill files whose <name> is not in skills/*.

Design choices match install:
- Combined flag (build + prune in one run)
- Global scan, ignores -Names / positional filter
- Default off, print-and-delete, no confirmation

Bash wrinkle: when build.sh delegates to powershell.exe -File
build.ps1 (Windows-without-zip case), --prune is NOT forwarded.
Bash runs prune itself at the end of the script against the
shared dist/. Keeps the delegation surface narrow and the prune
logic single-sourced per shell.

[skip-tdd: wrapper] — same carve-out as install-side, smoke-test
evidence: fake dist/fake-stale-{sh,ps}.skill files created in real
dist/, ran build.{sh,ps1} --prune, verified fakes removed, real
caveman.skill etc. left intact.

Wiki .wiki/concepts/install-cross-platform.md extended:
- title broadened (Install → Install / Build)
- new "Build-side --prune" section
- scope note: dist-hermes/ is separate (managed by build-hermes.py)

Closes [install-ps1-build-prune-followup].
2026-05-25 13:38:34 +03:00
7 changed files with 119 additions and 29 deletions

View File

@@ -31,12 +31,13 @@ _Updated: 2026-05-25 (board-cleanup: done-блоки → .archive/done-2026-05.m
---
## [install-ps1-build-prune-followup] — Add matching `--prune` flag to `scripts/build.{sh,ps1}` for `dist/<name>.skill` artefacts (analogue of install-script prune).
**Status:** ready
**Where I stopped:** Surfaced 2026-05-25 from `[install-ps1]` close — install-side prune ships, but `dist/<name>.skill` artefacts have the same staleness problem (e.g. `dist/using-synology-ops.skill` would linger if it had ever been built). Currently no .skill exists for the retired skill, but the gap is real for future renames/retires.
**Next action:** Add `--prune` to `scripts/build.sh` and `scripts/build.ps1`: scan `dist/*.skill`, drop any whose `<name>` is not in `skills/`. Same default-off / print-and-delete pattern as install-side. Update `.wiki/concepts/install-cross-platform.md` to extend the parity contract to build scripts.
**Branch:** (not started)
## 🟢 [install-ps1-build-prune-followup] — Add matching `--prune` flag to `scripts/build.{sh,ps1}` for `dist/<name>.skill` artefacts (analogue of install-script prune).
**Status:** done
**Where I stopped:** Shipped 2026-05-25. `--prune` / `-Prune` added to both `build.sh` and `build.ps1`. Bash arg-parses out `--prune` from positionals, runs prune at the end of the script against `dist/*.skill` (works regardless of which build branch executed — zip-path or powershell.exe delegation). PS `-Prune` switch runs after the build loop. Same default-off / global-scan / print-and-delete pattern as install-side. Smoke-tested with fake `dist/fake-stale-{sh,ps}.skill` files against real `dist/` — both paths pruned correctly without touching real archives. `[skip-tdd: wrapper]` carve-out applies. Wiki page `.wiki/concepts/install-cross-platform.md` extended with new "Build-side --prune" section + scope note that `dist-hermes/` is separate. Index + log updated.
**Next action:** (none — kept until merged)
**Branch:** master
<!-- created-by: vitya@DESKTOP-NSEF0UK / 2026-05-25 / origin: [install-ps1] close — natural extension to build scripts -->
<!-- closed-by: vitya@DESKTOP-NSEF0UK / 2026-05-25 / acceptance: bash + PS prune ✅; smoke-test ✅; wiki extended ✅ -->
---

View File

@@ -1,12 +1,12 @@
---
title: Install Cross-Platform Parity (PS + Bash)
title: Install / Build Cross-Platform Parity (PS + Bash)
type: concept
updated: 2026-05-25
---
# Install Cross-Platform Parity (PS + Bash)
# Install / Build Cross-Platform Parity (PS + Bash)
Sibling concept to `install-portability.md` (POSIX-shell compat). This one is about the **paired-script parity contract** between `scripts/install.ps1` (PowerShell) and `scripts/install.sh` (bash).
Sibling concept to `install-portability.md` (POSIX-shell compat). This one is about the **paired-script parity contract** between `scripts/install.ps1` / `scripts/install.sh` (install) and `scripts/build.ps1` / `scripts/build.sh` (build). Both pairs share the same conventions and the same `--prune` / `-Prune` flag pattern.
## Why two scripts
@@ -43,14 +43,35 @@ Added 2026-05-25 (commit `6cf0e98`). Motivating case: after retiring `using-syno
- **Default off.** Must be passed explicitly. Idempotent re-installs (the common case) don't suddenly delete anything.
## What `--prune` does NOT do
## What install-side `--prune` does NOT do
- It does not remove `dist/<name>.skill` artefacts. That's a build concern, not install. The matching `--prune` flag on `build.sh` / `build.ps1` is a separate task (`[install-ps1]` acceptance was scoped to install scripts; `dist/` is a build artefact directory).
- It does not touch plugin-installed skills under `~/.claude/plugins/<plugin>/skills/<name>/`. Those are managed by the plugin system, not this repo.
- It does not warn if the about-to-be-deleted dir contains user-edited content. The contract is that `~/.claude/skills/<name>/` is a managed copy of `skills/<name>/` — anything else is user-error.
- It does not remove `dist/<name>.skill` build artefacts. That's the build-script's `--prune` (see next section).
## Build-side `--prune` / `-Prune`
Added 2026-05-25 — natural extension of the install-side flag to the build pair (`scripts/build.sh` and `scripts/build.ps1`). Same design choices, applied to files instead of directories: after the build loop, walk `dist/*.skill` and remove any whose `<name>` (basename minus `.skill`) is not in `skills/*`.
Identical to install-side: combined flag, global scan ignores the name filter, prints `pruning: <name> -> <path>` per removal, default off, no confirmation prompt.
The bash path has one extra wrinkle: when `build.sh` is run on Windows without `zip` and delegates to `build.ps1` via `powershell.exe -File`, the `--prune` flag is **not** forwarded to the delegated PS process. Bash runs the prune step itself at the end of the script, against the same `dist/` directory. This keeps the delegation surface narrow (no flag-translation bugs) and the prune logic single-sourced per shell.
`build.ps1` invoked directly (without the bash wrapper) handles `-Prune` natively.
## What build-side `-Prune` does NOT do
- It does not unblock build for skills that have been renamed mid-flight. A user who renamed `skills/foo/``skills/bar/` should still run a fresh build (`build.sh bar`) — prune only catches stale archives whose source dir is gone, not stale archives whose source was renamed (those become orphans of a different source, indistinguishable from intentional foreign artefacts).
- It does not touch `dist-hermes/` — that directory is managed by `scripts/build-hermes.py` and follows its own rules (whole-directory rebuild per skill). Hermes has no current prune mechanism; if needed, that's a separate concept page.
## Test evidence
Both scripts smoke-tested 2026-05-25 on disposable target dirs (env-overridden `CLAUDE_SKILLS_DIR`). Pre-populated with 2 fake stale dirs, ran full install + prune, verified: stale dirs removed, all real skills installed, retired `using-synology-ops` absent. No automated test fixture in the repo — install scripts are wrapper-style, smoke-test evidence in the `6cf0e98` commit body suffices under the `[skip-tdd: wrapper]` carve-out.
All four scripts smoke-tested 2026-05-25.
**Install side** — disposable target dirs (env-overridden `CLAUDE_SKILLS_DIR`). Pre-populated with 2 fake stale dirs, ran full install + prune, verified: stale dirs removed, all real skills installed, retired `using-synology-ops` absent.
**Build side** — fake `dist/fake-stale.skill` + `dist/another-stale.skill` files created directly in the real `dist/`. Ran `build.sh --prune fake-stale-sh` (positional arg triggers a no-op build via skip path; prune runs at the end) and `build.ps1 -Names fake-stale-ps -Prune`. Both removed the fakes, left real archives like `caveman.skill` untouched.
No automated test fixture in the repo — install / build scripts are wrapper-style, smoke-test evidence in the respective commit bodies suffices under the `[skip-tdd: wrapper]` carve-out.
`[archive-roundtrip-test]` (still ⚪ on the board) is a candidate place to add a real fixture once it lands.

View File

@@ -22,7 +22,7 @@ Catalog of all wiki pages. One line per page, organized by type. Updated on ever
- [bootstrap-skill-deps-check.md](concepts/bootstrap-skill-deps-check.md) — project-bootstrap@1.7.0 — Step 5.6 collapses the per-skill "detect-and-recommend" mirror shape into one generic `trigger → fulfiller` table walker (skill vs plugin kind, never auto-install); subsumes the deferred `[bootstrap-recommend-projects-meta]` and the existing `superpowers`-only detector
- [bootstrap-manifest.md](concepts/bootstrap-manifest.md) — record of which `project-bootstrap` / `setup-wiki` / `setup-tasks` versions initialized this project's `.wiki/` and `.tasks/` layout (overwritten on re-bootstrap; history in git)
- [build-notes.md](concepts/build-notes.md) — why `build.ps1` exists alongside `build.sh`; PS 5.1 backslash-in-zip gotcha; how to extract a `.skill`
- [install-cross-platform.md](concepts/install-cross-platform.md) — parity contract between `install.ps1` and `install.sh` (paired-script invariant); rationale for `--prune` / `-Prune` flag (combined-with-install, global-scan, default-off)
- [install-cross-platform.md](concepts/install-cross-platform.md) — paired-script parity contract for `install.{ps1,sh}` AND `build.{ps1,sh}`; rationale for the `--prune` / `-Prune` flag (combined-with-action, global-scan, default-off); install-side prunes target dirs, build-side prunes `dist/*.skill` files
- [install-portability.md](concepts/install-portability.md) — `install.sh` / `build.sh` rewritten to drop `mapfile` (bash 4+) and `find -printf` (GNU only) so stock macOS (bash 3.2 + BSD find) works
- [context7-setup.md](concepts/context7-setup.md) — switched context7 from manual MCP entries to the official plugin; API key in `.mcp.json` as `--api-key`; now also captured as `setup-context7` skill (one-time install/migrate flow with key discovery)
- [projects-meta-skills.md](concepts/projects-meta-skills.md) — `setup-projects-meta` + `using-projects-meta` skill pair for the local `projects-meta-mcp` stdio server (cross-project tasks + shared Gitea wiki); local-first rule + two-step mutation pattern

View File

@@ -63,3 +63,5 @@ Parseable: `grep "^## \[" .wiki/log.md | tail -20`.
## [2026-05-25] decision | session-handoff-skill-design — design rationale for the `session-handoff` skill captured in wiki after cluster 7/7 closure; sliding overwrite of `.tasks/NEXT_SESSION.md`, phrase whitelist + substantive-commit heuristic, opt-in PostToolUse hook, orient+ask default, source: `~/projects/.workshop/.archive/2026-05-24-session-handoff-skill.md` Round 1 + Round 2
## [2026-05-25] decision | install-cross-platform — `install.{ps1,sh}` paired-script parity contract documented; `--prune` / `-Prune` flag rationale (combined-with-install, global-scan ignores names filter, default-off, print-and-delete no prompt); shipped in commit `6cf0e98` with `[skip-tdd: wrapper]` carve-out + smoke-test evidence; closes 2/3 of `[install-ps1]` acceptance (the doc + flag), `dist/`-prune analogue deferred to `build` scripts
## [2026-05-25] decision | install-cross-platform extended to build scripts — `build.{ps1,sh}` get the symmetric `--prune` / `-Prune` flag (removes `dist/<name>.skill` where `<name>` is not in `skills/`). Bash delegation to `powershell.exe -File build.ps1` does NOT forward the flag — bash runs prune itself against the shared `dist/`. Both paths smoke-tested with fake stale .skill files against real dist/. Closes `[install-ps1-build-prune-followup]`.

View File

@@ -1,5 +1,9 @@
# Build .skill archives from skills/<name>/ into dist/<name>.skill
# Usage: build.ps1 [-Names <name1>,<name2>] (no args = all)
# Usage: build.ps1 [-Names <name1>,<name2>] [-Prune]
# no args = build all skills/* into dist/<name>.skill
# -Prune = after build, remove dist/<name>.skill files whose <name>
# is NOT in skills/* (analogue of install.ps1 -Prune).
# Prune always scans the full dist/, ignores -Names filter.
#
# Uses .NET System.IO.Compression.ZipArchive directly to produce
# spec-compliant ZIPs with forward-slash entry names (Windows PowerShell 5.1's
@@ -7,7 +11,8 @@
[CmdletBinding()]
param(
[string[]]$Names = @()
[string[]]$Names = @(),
[switch]$Prune
)
$ErrorActionPreference = 'Stop'
@@ -69,3 +74,15 @@ foreach ($name in $Names) {
New-SkillArchive -SkillName $name -SourceDir $srcDir -OutPath $out
Write-Host "built: dist/$name.skill"
}
if ($Prune) {
$sourceNames = @(Get-ChildItem -Path $src -Directory | Select-Object -ExpandProperty Name)
$skillArchives = Get-ChildItem -Path $dist -Filter "*.skill" -File -ErrorAction SilentlyContinue
foreach ($archive in $skillArchives) {
$archiveName = [System.IO.Path]::GetFileNameWithoutExtension($archive.Name)
if ($sourceNames -notcontains $archiveName) {
Write-Host "pruning: $archiveName (not in skills/) -> $($archive.FullName)"
Remove-Item -Force $archive.FullName
}
}
}

View File

@@ -1,6 +1,10 @@
#!/usr/bin/env bash
# Build .skill archives from skills/<name>/ into dist/<name>.skill
# Usage: build.sh [name...] (no args = all)
# Usage: build.sh [--prune] [name...]
# no args = build all skills/* into dist/<name>.skill
# --prune = after build, remove dist/<name>.skill files whose <name>
# is NOT in skills/* (analogue of install.sh --prune).
# Prune always scans the full dist/, ignores name filter.
#
# Uses `zip` if available; otherwise delegates to scripts/build.ps1
# (so the script works on Linux, macOS, and Windows-with-git-bash without
@@ -12,9 +16,18 @@ ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
SRC="$ROOT/skills"
DIST="$ROOT/dist"
prune=0
positional=()
for arg in "$@"; do
case "$arg" in
--prune) prune=1 ;;
*) positional+=("$arg") ;;
esac
done
if command -v zip >/dev/null 2>&1; then
mkdir -p "$DIST"
if [ "$#" -eq 0 ]; then
if [ "${#positional[@]}" -eq 0 ]; then
# Portable across bash 3.2 (stock macOS) and bash 4+ (Linux, git-bash):
# avoid `mapfile` (bash 4+) and `find -printf` (GNU find only).
names=()
@@ -25,7 +38,7 @@ if command -v zip >/dev/null 2>&1; then
IFS=$'\n' names=($(printf '%s\n' "${names[@]}" | sort))
unset IFS
else
names=("$@")
names=("${positional[@]}")
fi
for name in "${names[@]}"; do
src_dir="$SRC/$name"
@@ -46,12 +59,14 @@ elif command -v powershell.exe >/dev/null 2>&1; then
# Windows fallback: delegate to build.ps1 (proper ZIP via .NET API).
# PS array binding via -File is fragile (commas don't always split into [string[]]),
# so call build.ps1 once per skill and let the bash loop do the work.
# --prune is NOT forwarded — bash handles prune at the end of this script
# against the same dist/, regardless of which build path ran.
ps1="$SCRIPT_DIR/build.ps1"
ps1_win="$(cygpath -w "$ps1" 2>/dev/null || echo "$ps1")"
if [ "$#" -eq 0 ]; then
if [ "${#positional[@]}" -eq 0 ]; then
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "$ps1_win"
else
for name in "$@"; do
for name in "${positional[@]}"; do
powershell.exe -NoProfile -ExecutionPolicy Bypass -File "$ps1_win" -Names "$name"
done
fi
@@ -59,3 +74,14 @@ else
echo "error: need either 'zip' (Linux/macOS) or PowerShell (Windows) to build .skill archives" >&2
exit 1
fi
if [ "$prune" -eq 1 ]; then
for f in "$DIST"/*.skill; do
[ -f "$f" ] || continue
name="$(basename "$f" .skill)"
if [ ! -d "$SRC/$name" ]; then
echo "pruning: $name (not in skills/) → $f"
rm -f "$f"
fi
done
fi

View File

@@ -1,16 +1,16 @@
---
name: using-yt-tools
version: 0.3.1
description: Two flows for YouTube content. **Iterative-watch** (summary/exploration): transcript with [mm:ss] anchors → pick moments → extract frames. **Targeted-frames** (specific timestamps): extract frames directly, no transcript. Triggers: "что в ролике", "о чём видео", "video summary", "youtube transcript", "покажи кадр на N", или любой youtube.com URL. CLI в `~/projects/.common/lib/yt-tools/`. YouTube-only; для Vimeo/Twitch/local — другие тулзы.
version: 0.3.2
description: Three flows for YouTube content. **Iterative-watch** (summary/exploration): transcript with [mm:ss] anchors → pick moments → extract frames. **Targeted-frames** (specific timestamps): extract frames directly, no transcript. **Audio-analysis** (music FFT): per timestamp spectrogram + numeric digest (BPM, key, chord progression, harmonic content) via `yt-listen`. Triggers: "что в ролике", "о чём видео", "video summary", "youtube transcript", "покажи кадр на N", "послушай момент N", "BPM/тональность видео", "спектрограмма", "listen to fragment", "analyze audio", или любой youtube.com URL. CLI в `~/projects/.common/lib/yt-tools/`. YouTube-only; для Vimeo/Twitch/local — другие тулзы.
---
# using-yt-tools
Iterative-watching YouTube для агента: clean-markdown транскрипт с `[mm:ss]`-якорями → агент решает, какие моменты интересны → targeted frame extraction по таймкодам → агент видит кадры через `Read`. Альтернативный flow — если юзер уже назвал таймкоды, идём прямо за кадрами без транскрипта.
Iterative-watching YouTube для агента: clean-markdown транскрипт с `[mm:ss]`-якорями → агент решает, какие моменты интересны → targeted frame extraction по таймкодам → агент видит кадры через `Read`. Альтернативный flow — если юзер уже назвал таймкоды, идём прямо за кадрами без транскрипта. Для музыкальных URL — третий flow с FFT-анализом (BPM, key, chord progression, спектр) через `yt-listen`.
## When to use
Два различных flow, выбор по user intent:
Три различных flow, выбор по user intent:
**Flow A — iterative-watch** (exploration / summary):
- Юзер спрашивает что в ролике, хочет summary, хочет узнать о чём видео.
@@ -22,7 +22,13 @@ Iterative-watching YouTube для агента: clean-markdown транскри
- Шаги: 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/` (project-local venv). Бинари могут не быть на PATH текущей сессии — это норма, особенно после свежего `winget install`. **Никогда не abort'ить по голому `Get-Command yt-frames` / `which yt-frames`** — сначала прогнать резолв (см. Prerequisites → Locating binaries).
**Flow C — audio-analysis** (music FFT):
- Юзер просит музыкальный разбор: BPM, тональность, гармония, chord progression, спектр, harmonic content.
- Шаги: `yt-listen URL --timestamps T1,T2,...` → per timestamp 3 артефакта (`clip.wav` + `spectrum.png` + `features.md`) → Read **обоих** (PNG vision + .md числа).
- Trigger phrases: «послушай момент N в <URL>», «какой BPM», «тональность видео», «гармония», «спектрограмма», «что в музыке на T», «listen to fragment», «analyze audio».
- Если есть captions — `yt-transcript` опциональный (контекст), но НЕ для lyrics-из-music (см. What NOT to do).
Все три flow предполагают, что `yt-tools` CLI установлен из `~/projects/.common/lib/yt-tools/` (project-local venv). Бинари могут не быть на PATH текущей сессии — это норма, особенно после свежего `winget install`. **Никогда не abort'ить по голому `Get-Command yt-frames` / `which yt-frames`** — сначала прогнать резолв (см. Prerequisites → Locating binaries).
## Prerequisites
@@ -30,12 +36,12 @@ Iterative-watching YouTube для агента: clean-markdown транскри
Скил ничего не предполагает про активный PATH. **Step 0 каждого flow** — резолв путей для `yt-frames`/`yt-transcript` и `ffmpeg` (+ `yt-dlp`, поставляется в том же venv). Если резолвится через fallback — используй PATH-prepend в каждом вызове (см. Invoke pattern ниже). Abort'ить **только** если бинаря нет ни на PATH, ни в известных install-локациях.
**yt-tools CLI** (любая из локаций даёт все четыре: `yt-frames`, `yt-transcript`, `yt-watch`, `yt-tools` + бонусом `yt-dlp`):
**yt-tools CLI** (любая из локаций даёт все пять: `yt-frames`, `yt-transcript`, `yt-listen`, `yt-watch`, `yt-tools` + бонусом `yt-dlp`):
1. **PATH**: `Get-Command yt-frames` (pwsh) / `command -v yt-frames` (bash)
1. **PATH**: `Get-Command yt-frames` (pwsh) / `command -v yt-frames` (bash). Для Flow C — probe также `yt-listen` (присутствует с pyproject 0.2.0+; если только `yt-frames` находится, а `yt-listen` нет — машина на старом 0.1.x, нужен `pipx reinstall yt-tools` / pull + reinstall).
2. **pipx-shim** (recommended install — см. install-hint ниже):
- Windows: `~/.local/bin/yt-frames.exe`
- Linux/macOS: `~/.local/bin/yt-frames`
- Windows: `~/.local/bin/yt-frames.exe` (+ `yt-listen.exe`)
- Linux/macOS: `~/.local/bin/yt-frames` (+ `yt-listen`)
3. **Legacy project-venv** (для машин до миграции на pipx):
- Windows: `~/projects/.common/lib/yt-tools/.venv/Scripts/yt-frames.exe`
- Linux/macOS: `~/projects/.common/lib/yt-tools/.venv/bin/yt-frames`
@@ -85,8 +91,9 @@ yt-frames <url> --timestamps 1:23,4:56
|---|---|---|
| A — iterative-watch | YouTube URL или bare 11-char video id | `--lang ru,en` для non-English subs; `--out PATH` |
| B — targeted-frames | YouTube URL + timestamps (`mm:ss`, `h:mm:ss`, или bare seconds: `123` → 2:03) | `--no-cache-source` (stream вместо кеша source.mp4); `--out DIR` |
| C — audio-analysis | YouTube URL + timestamps (как у B) | `--duration 30s` (default 30s, lower bound для beat-tracking); `--mode interval --interval 60s` (bulk sampling); `--no-wav` / `--no-spectrogram` (default ON); `--linear` (STFT вместо mel); `--chroma` (bonus chromagram PNG); `--sample-rate 22050`; `--no-cache-source`; `--out DIR` |
Оба flow пишут в `<cwd>/yt-cache/<video-id>/` по умолчанию.
Все три flow пишут в `<cwd>/yt-cache/<video-id>/` по умолчанию (Flow C — в `audio/` поддиректорию).
## Steps
@@ -120,6 +127,20 @@ Warnings и errors уходят в stderr (`warning: …`, `error: …`); stdout
Без transcript fetch. Если потом юзер спросит «что говорилось в тот момент?», переключайся на Flow A на том же URL — `source.mp4` cache переиспользуется, повторного download нет.
### Flow C — audio-analysis
```
0. Резолв yt-listen + ffmpeg per Prerequisites → Locating binaries; собрать PATH-prepend если резолв через fallback
1. Parse timestamps (mm:ss / h:mm:ss / bare seconds — как у Flow B)
2. yt-listen <url> --timestamps T1,T2,... → ./yt-cache/<vid>/audio/{clip,spectrum,features}_TTTT.{wav,png,md}
3. Read **обоих** per timestamp: features_TTTT.md (числа — BPM, key, chord progression, spectral features, peak frequencies, harmonic/percussive split) + spectrum_TTTT.png (vision)
4. Reasoning по BPM/key/chord/spectral. Цитируй конкретные числа из features.md; spectrum-PNG — supplementary signal, не основной (см. What NOT to do)
```
Stdout-контракт `yt-listen` — одна строка `Wrote: <abs path>` per artifact (3 на каждый таймкод: wav, png, md), как у `yt-frames`.
`source.mp4` cache переиспользуется между Flow A/B/C на одном URL — никаких повторных downloads. Default duration 30s (lower bound для beat-tracking); `--duration` override доступен. Для bulk-sampling музыкального ролика — `--mode interval --interval 60s` вместо явных таймкодов.
## Failure modes
Все failures abort cleanly; никогда не оставляй наполовину готовое состояние.
@@ -157,3 +178,5 @@ Cache hygiene: `yt-tools cache list` показывает usage, `yt-tools cache
- **Не пересказывай / не переводи transcript в свой ответ молча.** Артефакт — для твоего reasoning; цитируй с `[mm:ss]`-якорем когда приводишь пассаж.
- **Не вызывай скил на non-YouTube URL.** Vimeo / Twitch / TikTok / local mp4 — out of scope. Бери другие тулзы (или yt-dlp напрямую).
- **Не пиши результаты в произвольные пути.** По умолчанию `<cwd>/yt-cache/<vid>/`; явный `--out` только если юзер просил.
- **Не вызывай Whisper на смешанной музыке.** Юзер просит lyrics из музыкального ролика → это **не** `yt-listen`. Whisper на mixed music без source-separation = мусор (подтверждено arXiv 2506.15514). Скажи юзеру, что lyrics из music — отдельный pipeline (Demucs/Spleeter source-separation + Whisper поверх isolated vocals), out of scope текущего `yt-tools`. Не пытайся подсунуть `yt-transcript` как замену — YouTube auto-subs для музыки обычно нет, и `yt-listen` НЕ имеет Whisper-флага даже опционально.
- **Не интерпретируй `spectrum_*.png` без `features_*.md` в паре.** VLM-сигнал на audio спектрограммах ограничен (~50-60% accuracy на ESC-10, vs 72.5% human; Dixit et al. arXiv 2411.12058). Числовой digest из features.md — primary канал; spectrum-PNG — supplementary visual cue. Когда читаешь PNG — всегда читай и .md того же таймкода; цитируй BPM/key/chord/spectral из текстовых полей, не из «как выглядит картинка».