[2026-09-26] eagle: personal/plans/balda-mm-phase4-agents.md personal/plans/music-tags-review/music-tags-review.md personal/tech/agents-bridge.md personal/tech/music-tags-repair-truenas.md

This commit is contained in:
Alexey Martemyanov committed 2026-09-26 18:04:35 +06:00
1 parent d2f6a411f9
commit dd9e081273
4 files changed
+492 -32

No files matched your search

+87 -27
View File
@@ -1,8 +1,10 @@
# Фаза 4 — исполнитель на Mac + `~/agents` + секция «Agents» в Eagle Dashboard
> План. **Шаг 1 выполнен** (2026-09-26): образ собран, контейнер `balda-executor` Up (healthy),
> План. **Шаги 1-2 выполнены** (2026-09-26): образ собран, контейнер `balda-executor` Up (healthy),
> `claude 2.1.283` + `codex 0.157.1` внутри, монтирования проверены в обе стороны.
> ⏳ Осталось в шаге 1: логин CLI (нужен апрув на копирование auth-файлов).
> Мост `agents-bridge` :8790 поднят и протестирован (auth, идемпотентность, сквозной вызов CLI).
> 🔴 Осталось: **логин CLI делает Alex сам** (копирование auth-файлов отклонено); мост не в launchd;
> шаги 3-6 не начаты.
> Часть [[personal/projects/balda/balda-mm-delegation-plan]] (Фазы 4-5).
> Предыдущее: [[personal/projects/balda/balda-kraken-mm-deploy]] (Фаза 3 ✅ — бот отвечает Alex).
@@ -71,13 +73,12 @@ diff + тесты + коммит → Балда → Mattermost
| Codex auth | `~/cli-auth/codex` | `/root/.codex` | 0700 |
| OAuth-токен Claude | `~/Docker/balda-executor/.env` | env | 0600 |
📌 **Основной путь авторизации — не `claude setup-token`, а копия auth-файлов** из
`~/.claude`/`~/.codex`. Токен в `.env` — запасной вариант для headless без монтирования.
Выбранный вариант: копируются `~/.claude/.credentials.json` + `~/.claude/.claude.json` и
`~/.codex/auth.json` + `~/.codex/config.toml`. Полные каталоги **не** копируются:
`~/.codex` содержит ~640 МБ sqlite-логов/состояний (`logs_2.sqlite`, `state_5.sqlite`,
`memories_1.sqlite`, `queue_1.sqlite`) — исполнителю не нужны, а вес и шум дают.
📌 **Авторизацию CLI делает Alex сам** (решение 2026-09-26). Копирование auth-файлов из
`~/.claude`/`~/.codex` в `~/cli-auth` **отклонено** — я эти файлы не читаю и не копирую.
До его логина любая задача вернёт `Not logged in · Please run /login` (это ответ Claude Code,
а не ошибка моста — конвейер при этом доказанно рабочий).
Токен `CLAUDE_CODE_OAUTH_TOKEN` в `.env` контейнера — запасной путь для headless без монтирования.
Пересборка образа и обновление CLI auth не сбрасывают. Токены вне `/agents` → агент их не видит.
---
@@ -180,25 +181,77 @@ docker-compose restart # проверка переживан
⚠️ `deploy.resources.limits.memory: 8G` из черновика **убран**: в не-swarm compose
эта секция молча игнорируется, лимит создавал ложное ощущение защиты.
#### ⏳ Осталось в шаге 1: логин CLI (ждёт апрува)
#### ✅ Логин CLI — зона Alex (не автоматизируется)
`~/cli-auth/claude` пуст, в `~/cli-auth/codex` только `tmp/`. Копировать **только auth-файлы**,
не целые каталоги (`~/.codex` ≈ 640 МБ служебных sqlite — агенту не нужны):
`~/cli-auth/claude` пуст, в `~/cli-auth/codex` только `tmp/`.
| CLI | Хост | Куда |
|---|---|---|
| claude | `~/.claude/.credentials.json`, `~/.claude/.claude.json` | `~/cli-auth/claude/` |
| codex | `~/.codex/auth.json`, `~/.codex/config.toml` | `~/cli-auth/codex/` |
🔴 Копирование auth-файлов **отклонено Alex** («авторизую я, копировать из кредсы не надо»).
Не предлагать повторно, не читать `~/.claude/.credentials.json`, `~/.codex/auth.json`.
Признак незалогиненного исполнителя в ответе задачи: `Not logged in · Please run /login`.
📌 В `~/cli-auth` кладётся **копия** (оригиналы на месте, `~/.claude` не переезжает):
у агента-исполнителя свои токены, изолированные от рабочих. Права `~/cli-auth` = 0700.
### ✅ Шаг 2 ВЫПОЛНЕН (2026-09-26) — мост `agents-bridge`
**2. Мост (~3-4 ч)**
`~/Developer/agents-bridge/`, FastAPI :8790, `POST /task` (idempotency), `GET /task/{id}`, `GET /healthz`;
запуск CLI headless в контейнере; лимит 1-2 задач; shared secret.
Проект: `~/Developer/agents-bridge/` (FastAPI, python из `~/Developer/eagle-dash/.venv`, py3.14).
**3. Сеть (~1 ч)**
Cloudflare Tunnel: добавить published route `agents-bridge.qentra.top` → HTTP `localhost:8790` +
| Файл | Роль |
|---|---|
| `app.py` | роуты, авторизация, лимит параллельности, ThreadPoolExecutor |
| `store.py` | SQLite WAL: `tasks` + UNIQUE-индекс по `idempotency_key` |
| `executor.py` | `docker exec` в контейнер, провайдеры claude/codex, таймаут, обрезка вывода |
| `config.py` | чтение `.env` (минимальный парсер, без python-dotenv на старте) |
| `.env` | 0600, `BRIDGE_SECRET` (64 симв.), **не коммитить** |
| `.env.example` | шаблон без секретов |
| `state.sqlite3` | статусы задач, WAL |
**API:**
```
GET /healthz → статус моста + контейнера (без секрета)
POST /task → 202 {task_id, status} | 200 (идемпотентный повтор) | 401 | 429 | 400
GET /task/{task_id} → статус + stdout/stderr/exit_code
GET /tasks?limit=N → последние задачи
```
**Авторизация:** заголовок `X-Bridge-Secret` на всех `/task*`. Идемпотентность — заголовок
`Idempotency-Key`; повтор возвращает **тот же** `task_id` со HTTP **200** (первый ответ — 202).
**Проверено живьём (2026-09-26):**
| Проверка | Результат |
|---|---|
| `POST /task` без секрета / с неверным | 401 / 401 ✅ |
| Два POST с одним `Idempotency-Key` | один `task_id` ✅ |
| Задача дошла до CLI в контейнере | ответ CLI вернулся ✅ |
| Статус в SQLite (WAL) | ✅ |
| `/healthz` видит контейнер | `container_running: true` ✅ |
Ответ `/healthz`:
```json
{"status":"ok","container":"balda-executor","container_running":true,
"provider":"claude","secret_configured":true,"active_tasks":0,"max_concurrent":1}
```
Результат сквозной пробы (до логина CLI): `status=failed, exit_code=1,
stdout='Not logged in · Please run /login'` — **это ожидаемо** и доказывает, что
путь Kraken → Mac → контейнер → CLI проходит целиком.
**Запуск (вручную, до launchd):**
```bash
cd ~/Developer/agents-bridge
~/Developer/eagle-dash/.venv/bin/python -m uvicorn app:app --host 127.0.0.1 --port 8790
```
#### 🔴 Питфолл: `BRIDGE_SECRET` и redaction Hermes
Команда, создающая `.env` через `python -c` с f-строкой `BRIDGE_SECRET={...}`, в логе выглядит
обрезанной (`E...cret written`) — **это редакция секретов Hermes, файл на диске цел**.
Проверять содержимое не чтением файла, а через awk с маскировкой:
```bash
/usr/bin/awk -F= '{ if ($1 ~ /SECRET|TOKEN|KEY|PASSWORD/) print $1"=<redacted len "length($2)">"; else print }' .env
```
`read_file` на `.env` отказывает по определению (secret-bearing file).
**3. Сеть (~1 ч)** — Cloudflare Tunnel
Добавить published route `agents-bridge.qentra.top` → HTTP `localhost:8790` +
Access Application. Токен и процесс cloudflared не трогаются. Проверка: `curl` извне на `/healthz`.
⚠️ hostname — **одноуровневый** (двухуровневые дают TLS handshake failure).
@@ -228,16 +281,23 @@ MCP-инструмент в Балде → `POST /task`; проба `echo ok > /
## 8. Открыто
1. ~~Провайдер исполнителя — claude или codex~~ → **claude** (оба в образе, переключение `EXECUTOR_PROVIDER`)
2. ~~Нужен ли `ask`-апрув в будущем~~ → при `allow_all` не нужен; при потребности начинать с варианта B (апрув в мосте, ~0.5 дня)
3. Hostname для моста (`agents-bridge.qentra.top`?) — ждёт шага 3
4. ⏳ **Апрув на копирование auth-файлов CLI** в `~/cli-auth` — блокирует завершение шага 1
5. Порт моста `8790` — проверено, свободен (`lsof -nP -i :8790`)
1. ~~Провайдер исполнителя~~ → **claude** (оба в образе, переключение `EXECUTOR_PROVIDER`)
2. ~~Нужен ли `ask`-апрув~~ → при `allow_all` не нужен; при потребности начинать с B (апрув в мосте, ~0.5 дня)
3. 🔴 **Логин CLI — делает Alex сам.** Копирование auth-файлов отклонено; до логина задачи
возвращают `Not logged in`
4. 🔴 **Мост не в launchd** — поднят вручную (`uvicorn` в фоне), при перезагрузке Mac умрёт.
Плейт `com.agents.bridge` **обязателен до шага 3**: туннель опубликует URL, который может
вести в закрытый порт. Предложено, ждёт команды Alex
5. Hostname моста — `agents-bridge.qentra.top` (шаг 3, ждёт public route в Cloud Zero Trust)
6. Порт `8790` — проверено, свободен (`lsof -nP -i :8790`), занят мостом после запуска
7. ⚠️ В дашборде в `services.yaml` **нет** записи `agents_bridge` — добавить в шаге 4
(`health.type: http`, `url: http://127.0.0.1:8790/healthz`, `port: 8790`)
## Связанные заметки
- [[personal/projects/balda/balda-mm-delegation-plan]] — общий план (Фазы 4-5)
- [[personal/projects/balda/balda-kraken-mm-deploy]] — Фаза 3 (✅)
- [[personal/plans/balda-mm-pr-plan]] — Фаза 2 (PR #8)
- [[personal/tech/agents-bridge]] — мост :8790, API, питфоллы (шаг 2 ✅)
- [[personal/tech/macos-docker-virtiofs-mount-limits]] — ограничения macOS Docker
- [[personal/tech/balda-go-fx-pitfalls]] — fx-питфоллы, дельта-апплаер
- [[family/how-to/kraken-portainer-access]] — cloudflared, routes
@@ -20,6 +20,74 @@ updated: '2026-09-26'
| [[2-medium-confidence\|Средняя уверенность]] | 5813 |
| [[3-high-confidence\|Высокая уверенность]] | 959 |
## 🔴 Пятнадцатая волна (2026-09-26): КЛАСС 3 ЗАКРЫТ, КЛАСС 4 НАЧАТ
### ✅ Класс 3 (номер трека в `new_title`) — ЗАКРЫТ ПОЛНОСТЬЮ, 156 строк
Блок `!Детские/Детский хит/` доведён до конца. Фильтр остатка → **пусто**:
`grep 'Детский хит/' tag-fix-plan.csv | grep 'artist<-cleared-junk'`.
Паттерн фикса (единый):
```
БЫЛО: title = `143 БЮРО НАХОДОК`, artist = (пусто), track = (пусто), source = artist<-cleared-junk
СТАЛО: title = `БЮРО НАХОДОК`, artist = (пусто), track = 143, source = manual (user decision)
```
**artist пустой намеренно** — сборник детских песен из мультфильмов без единого исполнителя.
`album = Детский Хит Сезона` — название сборника, **остаётся**.
Полный список исправленного — в `personal/tech/music-tags-repair-truenas.md`, раздел «ПЯТНАДЦАТАЯ ВОЛНА».
### ⚠️ Ложные срабатывания детектора «номер в title»
`grep -E ',[0-9]{1,3} [А-ЯЁA-Z]'` ловит **нормальные** названия:
`32 Leaves` (группа), `3 Doors Down` (группа), `2 кота` (часть названия),
`Зинчук` (номер в **пути**, папка диска), `Амадеус N 146 2002` (номер альбома).
> 🔴 **Правило: цифра в title — НЕ автоматически дефект.** Номер трека стоит **в начале** title
> через пробел и дублируется как `NNN_` в имени файла. Разбирать 3-5 примеров глазами до
> объявления классом.
### 🔄 Класс 4 (транслит) — ~20 из ~40
Метка `translit artist tag; folder is Cyrillic` = **116 строк**, но большинство **ложные**
(западные группы: Joe Hisaishi, Ian Oliver, Yello, Muse, Gorillaz, Europe, Pain, Scooter).
**Реальные — русские группы латиницей.**
Исправлено: `!A Not sorted/Sbornik_Novij_Rok` (13 строк: Мумий Тролль, Мара, Дельфин, Фактор Страха,
Гроза, Диверсанты, Курара, Лакмус, Смысловые Галлюцинации, Скрябин, Бумбокс, Декабрь, Бумбокс),
Глюкоза, Потап и Настя Каменских, Каста (`Бабка`, `Горячее время`), **Наутилус Помпилиус**
(альбомы Атлантида 11, Раскол 10, Подъём 12).
### 🔴 Три собственные ошибки, пойманные в diff
1. `Берегитесь, мальчики` — запятая без кавычек → строка распалась на лишние колонки.
2. В «Крепкий орешек» подставил title `Дождь`.
3. `Goryacheye Vremya` приписал artist `Баста` вместо `Каста`.
> **Правило: читать diff после каждой правки `patch`.** Все три были видны именно в diff.
### 📊 Состояние
| Показатель | Значение |
|---|---:|
| Битых строк | **0** |
| Ручных правок | **~500** |
| Класс 5/C «склейка в title» | ✅ ЗАКРЫТ |
| Класс A «техпапка в artist» | ✅ ЗАКРЫТ |
| Класс 3 «номер в title» | ✅ ЗАКРЫТ (156) |
| Класс 4 «транслит» | 🔄 ~20 / ~40 |
| `no_change` (~1100) | 🔴 НЕ ПРОВЕРЯЛИСЬ |
| Применение на NAS | НЕ начато |
### ⏭ Осталось
1. **Класс 4** — остаток Наутилуса (`1998 Яблокитай` и далее), `!Рэп/НТЛ` (`RacinG_Portal`,
`PaDoNkÈ[NTL]`, `Fristaile`), `Df1` (вероятно НЕ трогать — западные).
2. **Строки `no_change` (~1100) — последний непроверенный пласт.**
3. Затем бэкап в `Music/backup/` и применение (по «go» Alex) → приёмка в UI → Music-библиотека.
## 🔴 Четырнадцатая волна (2026-09-26): КЛАСС A ЗАКРЫТ, КЛАСС 3 НАЧАТ
### ✅ Класс A («имя техпапки в artist/album») закрыт полностью — 0 остатков
+165
View File
@@ -0,0 +1,165 @@
# agents-bridge — мост Mac ↔ Kraken (FastAPI :8790)
> Создан 2026-09-26. Компонент Фазы 4 [[personal/plans/balda-mm-phase4-agents]] (шаг 2 ✅).
> Назначение: принять задачу от оркестратора Балды (Kraken) по HTTP, запустить
> CLI-исполнитель в контейнере `balda-executor`, вернуть вывод.
---
## Расположение
| Что | Путь |
|---|---|
| Проект | `~/Developer/agents-bridge/` |
| Python | `~/Developer/eagle-dash/.venv/bin/python` (3.14, fastapi 0.115.0, uvicorn 0.30.6) |
| Конфиг + секрет | `~/Developer/agents-bridge/.env` (**0600, не коммитить**) |
| Шаблон | `~/Developer/agents-bridge/.env.example` |
| Состояние | `~/Developer/agents-bridge/state.sqlite3` (WAL) |
| Лог (ручной запуск) | `/tmp/agents-bridge.log` |
| Контейнер-исполнитель | `balda-executor` (`~/Docker/balda-executor/`) |
Своего venv у проекта **нет** — используется venv дашборда (там уже fastapi/uvicorn/httpx).
---
## Файлы
| Файл | Роль |
|---|---|
| `app.py` | роуты, проверка секрета, лимит параллельности, `ThreadPoolExecutor` |
| `store.py` | SQLite: таблица `tasks`, UNIQUE-индекс по `idempotency_key`, `reap_stale()` |
| `executor.py` | `docker exec` в контейнер; провайдеры `claude`/`codex`; таймаут; обрезка вывода |
| `config.py` | чтение `.env` встроенным парсером + `Settings` (frozen dataclass) |
| `requirements.txt` | fastapi==0.115.0, uvicorn==0.30.6, httpx, python-dotenv |
---
## API
```
GET /healthz → статус моста и контейнера (без секрета)
POST /task → 202 | 200 (идемпотентный повтор) | 401 | 429 | 400
GET /task/{task_id} → статус + stdout/stderr/exit_code
GET /tasks?limit=N → последние задачи (для дашборда/отладки)
```
**Авторизация:** заголовок `X-Bridge-Secret` на всех `/task*`. Если `BRIDGE_SECRET` не
настроен — `POST /task` возвращает **503**, мост не принимает задачи.
**Идемпотентность:** заголовок `Idempotency-Key`. Повтор возвращает **тот же** `task_id`
с HTTP **200** (первый ответ — 202). Гарантия на уровне БД: UNIQUE-индекс + `IntegrityError`
как обработка гонки. CLI запускается **ровно один раз** на задачу.
**Лимит параллельности:** `MAX_CONCURRENT_TASKS` (по умолчанию **1**). При исчерпании —
**429**. Больше 2 задач под одним подписочным аккаунтом — риск бана за «unusual traffic».
---
## Провайдеры
`executor.PROVIDERS` — argv для headless-запуска:
| Провайдер | argv |
|---|---|
| `claude` | `claude -p --output-format text <prompt>` |
| `codex` | `codex exec --skip-git-repo-check <prompt>` |
Дефолт — `EXECUTOR_PROVIDER=claude`. Команда собирается **списком аргументов** (без
`shell=True`), `WORKDIR=/agents` внутри контейнера — наружу не выйти.
---
## Переменные окружения
| Ключ | Дефолт | Смысл |
|---|---|---|
| `BRIDGE_HOST` | `127.0.0.1` | слушать только локально (наружу — через туннель) |
| `BRIDGE_PORT` | `8790` | порт |
| `BRIDGE_SECRET` | — | **обязателен**, 64 симв. `secrets.token_urlsafe(48)` |
| `EXECUTOR_CONTAINER` | `balda-executor` | имя контейнера |
| `EXECUTOR_PROVIDER` | `claude` | `claude` или `codex` |
| `MAX_CONCURRENT_TASKS` | `1` | одновременно выполняемых |
| `TASK_TIMEOUT_SECONDS` | `900` | таймаут задачи, процесс убивается |
| `MAX_OUTPUT_BYTES` | `200000` | вывод обрезается |
| `DB_PATH` | `state.sqlite3` | SQLite |
---
## Запуск
**Сейчас — вручную** (launchd ещё не сделан, см. открытый пункт 4 плана):
```bash
cd ~/Developer/agents-bridge
~/Developer/eagle-dash/.venv/bin/python -m uvicorn app:app --host 127.0.0.1 --port 8790
```
Проверка:
```bash
curl -s http://127.0.0.1:8790/healthz
```
⚠️ На macOS `docker compose` **не работает** — только `docker-compose` (см. план, шаг 1).
---
## Питфоллы
### 1. `Not logged in` — это ответ CLI, а не ошибка моста
Сквозная проба до логина CLI вернула:
```
status=failed exit_code=1 stdout='Not logged in · Please run /login'
```
Это **признак рабочего конвейера**: docker поднял процесс, `claude` ответил. Если видишь
эту строку — auth у исполнителя, а не мост. Логин делает **Alex сам**; копирование
auth-файлов из `~/.claude`/`~/.codex` отклонено, не предлагать.
### 2. Редакция секретов Hermes искажает команды создания `.env`
`python -c` с f-строкой `BRIDGE_SECRET={s}` печатает в лог обрезанный фрагмент
(`E...cret written`) — **редакция Hermes, файл на диске цел**. Не «чинить».
Проверять `.env` только так:
```bash
/usr/bin/awk -F= '{ if ($1 ~ /SECRET|TOKEN|KEY|PASSWORD/) print $1"=<redacted len "length($2)">"; else print }' .env
```
`read_file` на `.env` отказывает по определению (secret-bearing file) — это норма.
### 3. Зависшие задачи после рестарта
`store.reap_stale()` на старте помечает `running`-задачи старше `TASK_TIMEOUT_SECONDS`
как `timeout` с причиной `bridge restarted while task was running`. Иначе они навсегда
висят активными и блокируют слот лимита.
### 4. Мост не переживает перезагрузку
Пока нет launchd-плейта `com.agents.bridge`, процесс живёт только в текущей сессии.
Это **блокер для шага 3** (туннель опубликует URL, ведущий в закрытый порт).
---
## Порядок ручной приёмки (сработавший)
```bash
curl -s -o /dev/null -w "%{http_code}\n" -X POST http://127.0.0.1:8790/task \
-H 'Content-Type: application/json' -d '{"prompt":"hi"}' # → 401 (нет секрета)
# с секретом и одним Idempotency-Key дважды → один и тот же task_id, 202 затем 200
# GET /task/{id} → status/stdout/exit_code
```
Тестовый скрипт: `/tmp/test-bridge.sh` (читает секрет из `.env`, в командной строке
секрет не светится).
---
## Связанное
- [[personal/plans/balda-mm-phase4-agents]] — план Фазы 4 (шаги 1-2 ✅)
- [[personal/tech/macos-docker-virtiofs-mount-limits]] — почему bind-mount вместо symlink
- [[personal/projects/balda/balda-mm-delegation-plan]] — общий план делегирования
## История
- **2026-09-26**: создан, поднят на :8790, протестирован (auth 401, идемпотентность,
сквозной вызов CLI). Не в launchd. Логин CLI — за Alex.
+172 -5
View File
@@ -4,6 +4,7 @@ created: '2026-09-26'
updated: '2026-09-26'
type: tech
namespace: personal
wave: 15
tags:
- music
- jellyfin
@@ -19,14 +20,19 @@ related:
# Music Tags Repair — TrueNAS (7906 файлов)
> **Статус: ЧЕТЫРНАДЦАТАЯ ВОЛНА (2026-09-26). КЛАСС C (склейка) И КЛАСС A (техпапка) ЗАКРЫТЫ.
> КЛАСС 3 (номер в title) — В РАБОТЕ (~25 из ~80). NAS ДОСТУПЕН, применение НЕ начато.**
> **Статус: ПЯТНАДЦАТАЯ ВОЛНА (2026-09-26). КЛАСС C, КЛАСС A, КЛАСС 3 — ЗАКРЫТЫ ПОЛНОСТЬЮ.
> КЛАСС 4 (транслит) — В РАБОТЕ (~20 из ~40). NAS ДОСТУПЕН, применение НЕ начато.**
> Работа — ручной проход по CSV `tag-fix-plan.csv` построчно; правка скриптов ЗАПРЕЩЕНА.**
> ✅ **Класс A («имя техпапки в artist/album») — ЗАКРЫТ ПОЛНОСТЬЮ: 0 остатков.**
> (`Забугр`, `disk`, `disk_v2`, `1 DRIVE`, `5 POPSA`, `3 SHANSON` — все вычищены.)
> ✅ **Класс 3 (номер трека в `new_title`) — в работе: блок `!Детские/Детский хит` ~25 из ~80.**
> ❌ Осталось: класс 3 (~55 строк), транслит (~40), строки с `no_change` (~1100, не проверялись).
> Детали и таблицы — «ЧЕТЫРНАДЦАТАЯ ВОЛНА».
> ✅ **Класс 3 (номер трека в `new_title`) — ЗАКРЫТ ПОЛНОСТЬЮ.** Блок `!Детские/Детский хит/`
> — все **156 строк**: номер → `new_track`, title чистый, artist пустой, album `Детский Хит Сезона`.
> ✅ **Класс 4 (транслит) — в работе: ~20 из ~40.** Закрыты: `!A Not sorted/Sbornik_Novij_Rok`
> (10 строк), Мумий Тролль, Мара, Дельфин, Каста, Глюкоза, Потап и Настя, Наутилус Помпилиус
> (альбомы Атлантида / Раскол / Подъём).
> ❌ Осталось: транслит Наутилуса (`Яблокитай` и далее), строки с `no_change` (~1100, не проверялись),
> транслит в `!Рэп/НТЛ`, `Df1`.
> Детали и таблицы — «ПЯТНАДЦАТАЯ ВОЛНА».
> ✅ **Проверка целостности (быстрая, без python):**
> `/usr/bin/awk -F'"' 'NR>1 && NF%2==0 {print NR": "NF}' tag-fix-plan.csv` → пусто = 0 битых.
> **CSV: 7907 строк (7906 данных + заголовок), 0 битых. Ручных правок: 311+.**
@@ -64,6 +70,167 @@ related:
> Прогонялись **девять волн** проверок: см. таблицу волн ниже.
> Итог v7: «требует решения» **4**, medium **5811**, high **958**, mojibake = 0, остаточный mojibake = 0.
## 🖐 ПЯТНАДЦАТАЯ ВОЛНА: КЛАСС 3 ЗАКРЫТ, КЛАСС 4 НАЧАТ (2026-09-26)
### ✅ КЛАСС 3 (номер трека в `new_title`) — ЗАКРЫТ ПОЛНОСТЬЮ (156 строк)
Блок `!Детские/Детский хит/` — **все строки** доведены до конца.
Паттерн фикса (единый для всего блока):
```
БЫЛО: title = `143 БЮРО НАХОДОК`, artist = (пусто), track = (пусто), source = artist<-cleared-junk
СТАЛО: title = `БЮРО НАХОДОК`, artist = (пусто), track = 143, source = manual (user decision)
```
**Почему artist пустой:** это сборник детских песен из мультфильмов, у него нет единого
исполнителя. `album = Детский Хит Сезона` — осмысленное название сборника, **остаётся**.
Второй подпаттерн в блоке (строки, где номер уже был убран ранее):
`source = track<-filename;artist<-cleared-junk` → просто менялся `source` на
`manual (user decision)`, поля уже были верные.
**Примеры правок (все 156):** НЕ ХОДИТЕ В АФРИКУ (128), РАССКАЖИ СНЕГУРОЧКА (105),
МАРИЯ МИРАБЕЛЛА (123), ПЕСЕНКА БОБРЯТ (154), ЧЕБУРАШКА (140), ПЕСНЯ ШАПОКЛЯК (132),
ЧЕМУ УЧАТ В ШКОЛЕ (109), ХЭППИ ЭНД (156), АНТОШКА (145), ПЕСНЯ ВОЛКА (103),
ПЕСЕНКА БЕЛЯНКИ (135), ПЕСЕНКА ЦАРЯ (155), ПЕСЕНКА ПАРОВОЗИКА (126),
ПЕСНЯ ПРО КОНДИТЕРСКУЮ ФАБРИКУ (114), ЛЕСНОЙ ОЛЕНЬ (152), ФИЛЬМ ФИЛЬМ ФИЛЬМ (116),
Я ВОДЯНОЙ (106), ПЕСНЯ СИНЕГЛАЗКИ (147), ПЕСНЯ ПИРАТА (108), ПЕСНЯ КОТА (113),
КТО ХОДИТ В ГОСТИ ПО УТРАМ (139), ТРИ БЕЛЫХ КОНЯ (124), ПЕСНЯ УРРИ (30),
ПЕРВОКЛАШКА (51), ПЕСНЯ ПРИВИДЕНИЙ (88), ПРИХОДИ СКАЗКА (97), КРЫЛАТЫЕ КАЧЕЛИ (89),
ПЕСЕНКА О ЖИРАФЕ (33), БАРМАЛЕЙ (71), ПЕСНЯ КОЩЕЯ (40), КРУЧУ КРУЧУ КРУЧУ ПЕДАЛИ КРУЧУ (56),
ПРЕКРАСНОЕ ДАЛЕКО (7), МОЖЕТ БЫТЬ ВОРОНА (8), ПЕСЕНКА ХОМЫ И СУСЛИКА (99),
ЛЯГУШАЧЬЯ ЛАМБАДА (16), ПЕСНЯ КЛЫКА-ПОТРОШИТЕЛЯ (91), КОЛЫБЕЛЬНАЯ МЕДВЕДИЦЫ (41),
ОБЖОРЫ (68), ТОЧКА ТОЧКА ЗАПЯТАЯ (3), ПЕСЕНКА ВАСИЛИСЫ (39), ПЕСНЯ СОЛДАТА (38),
БОЛЬШОЙ СЕКРЕТ ДЛЯ МАЛЕНЬКОЙ КАМПАНИИ (94), ПЕСНЯ О ХОТТАБЫЧЕ (64), ПЕСНЯ БЕЛОЧКИ (35),
МАНИ-МАНИ (50), ПЕСЕНКА КВАКИ (23), ПЕСНЯ БАБЫ-ЯГИ (118), ПЕСНЯ БАРСУКА БОНИФАЦИЯ (54),
ПЕСЕНКА УМКИ (5), РОМАНС ЧЕРЕПАХИ ТОРТИЛЛЫ (134), ГОВОРЯТ МЫ БЯКИ-БУКИ (75),
БУРАТИНО (1), ЧУНГА-ЧАНГА (2), КОГДА МОИ ДРУЗЬЯ СО МНОЙ (59), ТАНЕЦ МАЛЕНЬКИХ УТЯТ (6),
СПОР МАШИ И ВИТИ О СКАЗКАХ (53), РЫЖИЙ РЫЖИЙ КОНОПАТЫЙ (77), ПЕСНЯ ПРО РОБИН-ГУДА (43),
ОХ РАНО ВСТАЕТ ОХРАНА (9), ЧАСТУШКИ БАБОК-ЕЖЕК (11), БЮРО НАХОДОК (143), ПЕСНЯ СЫЩИКА (151),
УТРО НАЧИНАЕТСЯ (149), ЧЕЛОВЕК СОБАКЕ ДРУГ (90), НА ДАЛЕКОЙ АМАЗОНКЕ (37), ПЕСНЯ РУСАЛОЧКИ (61),
БАЮ БАЮШКИ БАЮ (18), ПЕСНЯ РЫБЫ-ПИЛЫ (52), ГИМН НЕЗНАЙКИ (17), ПОПУРРИ ИЗ М-Ф БРЕМЕНСКИЕ
МУЗЫКАНТЫ (82), А МОЖЕТ БЫТЬ ВОРОНА (133), ПУСТЬ НЕТУ НИ КОЛА И НИ ДВОРА (81),
ГДЕ ВОДЯТСЯ ВОЛШЕБНИКИ (141), В МИРЕ МНОГО СКАЗОК (57), ПЕСНЯ ХОМЯКА (127),
НЕПРИЯТНОСТЬ ЭТУ МЫ ПЕРЕЖИВЕМ (21), ВМЕСТЕ ВЕСЕЛО ШАГАТЬ (150), ЖИВИ В ВОЛШЕБНОМ ЯЩИЧКЕ (66),
ТАКАЯ-СЯКАЯ СБЕЖАЛА ИЗ ДВОРЦА (22), КАБЫ НЕ БЫЛО ЗИМЫ (120), СПИ МОЯ РАДОСТЬ УСНИ (137),
ПЕСНЯ ВАНИ-ПЕЧНИКА (148), А Я ТАКОЙ ГОЛОДНЫЙ (73), ДВАЖДЫ ДВА - ЧЕТЫРЕ (4),
САМОВАРО-ПАРОВОЗО-ВЕРТОЛЕТ (111), ПЕСЕНКА ЛЬВЕНКА И ЧЕРЕПАХИ (60), ПЕСЕНКА ДРАКОНЧИКА (49),
ПЕСНЯ ИОХИМА ЛИСА (104), ПОЙ ВАСЯ (101), И Я ТОГО ЖЕ МНЕНИЯ (86), ПЕСЕНКА ВИННИ-ПУХА (47),
КАКОЙ ЧУДЕСНЫЙ ДЕНЬ (131), ПЕСНЯ СОРНЯКОВ (142), ПЕСЕНКА КРОКОДИЛА ГЕНЫ (112),
НАСТОЯЩИЙ ДРУГ (15), ПЕСНЯ БАЗИЛИО И АЛИСЫ (85), НИЧЕГО НА СВЕТЕ ЛУЧШЕ НЕТУ (80),
ДВОЙКА (65), СОБАКА БЫВАЕТ КУСАЧЕЙ (48), ДВА ВЕСЕЛЫХ ГУСЯ (138), ПО СЕКРЕТУ ВСЕМУ СВЕТУ (146),
ПЕСНЯ КАРАБАСА БАРАБАСА (46), ВЕСЕННЯЯ КАПЕЛЬ (117), У ПОНИ ДЛИННАЯ ЧЕЛКА ИЗ НЕЖНОГО ШЕЛКА (79),
ДУЭТ ТРУБОДУРА И ПРИНЦЕССЫ (29), В ТРАВЕ СИДЕЛ КУЗНЕЧИК (107), МЫ БАНДИТО-ГАНГСТЕРИТО (12),
ПЕСНЯ КОТА МАТВЕЯ (119), ПЕСЕНКА О ЛЕТЕ (70), В ЛЕСУ РОДИЛАСЬ ЕЛОЧКА (14),
ДУЭТ ГЛУПОГО КОРОЛЯ И ПРЕКРАСНОЙ ПРИНЦЕССЫ (74), ПЕСЕНКА ПАУЧКА АНАСИ (32), КАРУСЕЛЬ (10),
ХВОСТ ЗА ХВОСТ (67), ДА НУЖЕН ОТДЫХ ПАРУСАМ (45), ПЕСЕНКА КОТЕНКА (96), ПЕСЕНКА СТРАХОВ (115),
ДИАЛОГ ВОЛКА И КОЗЛЯТ (13), ПОЛЕ ЧУДЕС (130), ВТОРАЯ ПЕСНЯ КОЗЫ (153), ЛОМАТЬ КРУШИТЬ (42),
ПЕСНЯ ДУРЕМАРА (92), ПЕСНЯ ЛЕШЕГО (136), СДАЕТСЯ КВАРТИРА С РЕБЕНКОМ (122), ПЕСЕНКА ДРУЗЕЙ (144),
ПЕСНЯ ДИКИХ ГИТАР (62), ОБЛАКА (25), ПЕСЕНКА КРАСНОЙ ШАПОЧКИ (121), ЗВЕЗДНАЯ СТРАНА (102),
ГОЛУБОЙ ВАГОН (78), КУДА ТЫ ТРОПИНКА МЕНЯ ПРИВЕЛА (63), КОЛЫБЕЛЬНАЯ (58),
В КАЖДОМ МАЛЕНЬКОМ РЕБЕНКЕ (19), ПЕСНЯ ПТИЦЕЛОВА БЕНКАНА (129), ПЕСНЯ ВОЛКОВ (84),
ОСЬМИНОЖКИ (93), ПЕСЕНКА ПРО НЕПРИЯТНОСТИ (44), ВСЕ МЫ ДЕЛИМ ПОПОЛАМ (100),
ПЕСНЯ О ЕЛОЧКЕ (20), ПЕСНЯ МИКРОБУСА (110), ПЕСНЯ КОЗЫ (87), ЗЕМЛЯНИЧНОЕ ВАРЕНЬЕ (125),
КОРОБКА С КАРАНДАШАМИ (28), ПЕСНЯ СТАМПА (69).
**Фильтр остатка (после закрытия = 0):**
`grep 'Детский хит/' tag-fix-plan.csv | grep 'artist<-cleared-junk'` → пусто.
### ⚠️ ВАЖНО: ЛОЖНЫЕ СРАБАТЫВАНИЯ ДЕТЕКТОРА «номер в title»
`grep -E ',[0-9]{1,3} [А-ЯЁA-Z]'` даёт **ложные** на нормальных названиях:
| Строка | Почему НЕ баг |
|---|---|
| `145_32 Leaves - All Is Numb` | `32 Leaves` — название группы |
| `189_3 Doors Down - It's Not My Time` | `3 Doors Down` — название группы |
| `007_Татьяна Зыкина - 2 кота` | `2 кота` — часть названия песни |
| `002 Одинокий в ночи 2002/04 Miks` | `Зинчук` — номер в **пути** (папка диска), title чистый |
| `001 Амадеус N 146 2002` | `N 146` — номер альбома в названии |
> 🔴 **Правило: цифра в title — НЕ автоматически дефект.** Номер трека отличается тем, что
> стоит **в начале** title и **отделён пробелом**, а сами данные в файле начинаются с `NNN_`
> в имени файла. Перед объявлением классом — разобрать 3-5 примеров глазами (тот же урок,
> что с детекторами в одиннадцатой и девятой волнах).
### 🛠 КЛАСС 4 (транслит) — НАЧАТ, ~20 из ~40
Метка `translit artist tag; folder is Cyrillic` стоит на **116 строках**, но большинство —
**ложные**: для западных групп латиница корректна (Joe Hisaishi, Ian Oliver, Yello, Ace of Base,
Muse, Gorillaz, Europe, Pain, Scooter, Nautilus в некоторых альбомах и т.д.).
**Реальные кандидаты — русские группы, записанные латиницей.**
**Исправлено в этой волне:**
| Файл / было | Стало |
|---|---|
| `Kontrabandy` / `Mumiy Troll'` | `Контрабанды` / `Мумий Тролль` |
| `Ya Popala` / `Mara` | `Я попала` / `Мара` |
| `Na Zare` / `Evgeniy Grishkovec & Bigudi & Renars Kaupers` | `На заре` / `Евгений Гришковец & Бигуди & Ренарс Кауперс` |
| `Bez Nas` / `Del'fin` | `Без нас` / `Дельфин` |
| `Vyshe Sil` / `Faktor Straha` | `Выше сил` / `Фактор Страха` |
| `Nad Eniseem` / `Groza` | `Над Енисеем` / `Гроза` |
| `Beregites' Mal'chiki` / `Diversanty` | `Берегитесь мальчики` / `Диверсанты` |
| `Nas Dvoe` / `Kurara` | `Нас двое` / `Курара` |
| `My Vstretimsya Tam` / `Lakmus` | `Мы встретимся там` / `Лакмус` |
| `Parni Ne Plachut` / `Smyslovye Gallyucinacii` | `Парни не плачут` / `Смысловые Галлюцинации` |
| `Fantastika` / `Mumiy Troll'` | `Фантастика` / `Мумий Тролль` |
| `Mumitrol'` / `Skryabin` | `Мумитроль` / `Скрябин` |
| `Ta Chto` / `Bumboks` | `Та что` / `Бумбокс` |
| `PIPEC` / `GLJUKOZA` | `Пипец` / `Глюкоза` |
| `Mama, My Vse Soshli S Uma` / `Dekabr'` | `Мама, мы все сошли с ума` / `Декабрь` |
| `Krepkiy Oreshek+++` / `Potap & Nastya Kamenskih` (×2) | `Крепкий орешек` / `Потап и Настя Каменских` |
| `babka` / `kasta` | `Бабка` / `Каста` |
| `Goryacheye Vremya` / `kasta` | `Горячее время` / `Каста` |
| `Nautilus Pompilius` → **`Наутилус Помпилиус`** | альбомы **Атлантида** (11), **Раскол** (10), **Подъём** (12) |
**Папка `!A Not sorted/Sbornik_Novij_Rok`** дала основной улов транслита (13 строк) —
там собраны русские рок-группы, у которых теги были латиницей.
> ⚠️ **Питфолл метода:** `replace_all` по паре `,Nautilus Pompilius,<album>,,no_change,translit artist tag; folder is Cyrillic`
> безопасен только при точном указании альбома — иначе `Found 3 matches`. Наутилус правился
> по альбомам отдельно (Атлантида, Раскол, Подъём).
> **Остаток:** `1998 Яблокитай` и далее — там метка ещё стоит.
### 🔴 ТРИ СОБСТВЕННЫЕ ОШИБКИ, ПОЙМАННЫЕ И ИСПРАВЛЕННЫЕ В ЭТОЙ ВОЛНЕ
| # | Ошибка | Как поймано | Фикс |
|---|---|---|---|
| 1 | `,Берегитесь, мальчики,Диверсанты` — запятая в title без кавычек → строка распалась на лишние колонки | diff показал сдвиг полей | переписал без запятой: `Берегитесь мальчики` |
| 2 | В строку «Крепкий орешек» подставил title `Дождь` (не то название) | визуальная проверка diff | вернул `Крепкий орешек` |
| 3 | `Goryacheye Vremya` приписал artist `Баста` вместо `Каста` | визуальная проверка diff | `Горячее время` / `Каста` |
> 🔴 **Правило (закрепить):** после КАЖДОЙ правки читать diff в ответе `patch` — три ошибки
> этой волны были видны именно в diff. Значение с запятой внутри **обязательно** в кавычках.
### 📊 СОСТОЯНИЕ НА КОНЕЦ ПЯТНАДЦАТОЙ ВОЛНЫ
| Показатель | Значение |
|---|---:|
| Строк в CSV | 7906 (+1 заголовок = 7907 в файле) |
| Битых строк | **0** |
| Ручных правок (`source = manual (user decision)`) | **~500** |
| Класс C «склейка artist-title в `new_title`» | ✅ **ЗАКРЫТ** |
| Класс A «имя техпапки в artist/album» | ✅ **ЗАКРЫТ (0 остатков)** |
| Класс 3 «номер трека в title» | ✅ **ЗАКРЫТ (156 строк блока `Детский хит`)** |
| Класс 4 «транслит вместо русского» | 🔄 **~20 / ~40** |
| Строки `no_change` (~1100) | 🔴 **НЕ ПРОВЕРЯЛИСЬ** |
| Применение на NAS | **НЕ начато** |
### ⏭ ЧТО ДЕЛАТЬ ПЕРВЫМ ДЕЛОМ В СЛЕДУЮЩЕЙ СЕССИИ
1. **Класс 4, остаток** — транслит Наутилуса (`/!Rock&Folk/Наутилус Помпилиус/1998 Яблокитай/`
и следующие альбомы). Детектор: `grep 'translit artist tag'` → отсеять западные группы
глазами → править русские.
2. **Транслит в `!Рэп/НТЛ`** — `RacinG_Portal` / artist `PaDoNkÈ[NTL]` (стр. 2823), `Fristaile`,
`DrakRacing`, `Kasta - Kto budet pit' s nami` → русский.
3. **Транслит в `Df1`** — проверить глазами, там в основном западные (Paffendorf, Ian Oliver) —
вероятно НЕ трогать.
4. **Проверить строки `no_change`** (~1100) — часть заведомо грязная (David Guetta был вытащен
вручную). Это последний непроверенный пласт.
5. Только после этого — **бэкап в `Music/backup/` и применение** (по «go» Alex), затем приёмка
в UI, затем Music-библиотека Jellyfin.
## 🖐 ЧЕТЫРНАДЦАТАЯ ВОЛНА: КЛАСС A ЗАКРЫТ + КЛАСС 3 НАЧАТ (2026-09-26)
### 🔴 ГЛАВНЫЙ УРОК ВОЛНЫ: `awk -F','` НЕ ГОДИТСЯ И ДЛЯ ДЕТЕКТА ПОЛЕЙ — ТОЛЬКО ДЛЯ КАВЫЧЕК