[2026-09-26] eagle: family/how-to/vault-git-sync.md personal/plans/balda-mm-pr-plan.md personal/tech/balda-go-fx-pitfalls.md personal/tech/music-tags-repair-truenas.md
This commit is contained in:
1 parent
e200596c45
commit
036e7a7091
4 files changed
+272
-30
No files matched your search
@@ -12,6 +12,35 @@ tags:
|
||||
|
||||
# Vault Git Sync
|
||||
|
||||
> **🔴 STATE 2026-09-26 — РАСХОЖДЕНИЕ РАСПИСАНИЯ: Taiga-сервер синкается РАЗ В ЧАС, не 5 минут.**
|
||||
> Job `sync-vault` внутри контейнера `hermes-taiga` (Hermes cron, id `29851c413903`) расписан
|
||||
> **`0 * * * *`** — то есть раз в час, **хотя таблица ниже (и `family/how-to/obsidian-sync`)
|
||||
> заявляет «*/5»**. Обнаружено при проверке «доехал ли док до телефона»: ноты, залитые с Mac
|
||||
> в 14:32, в клоне `obsidian-syncthing` отсутствовали.
|
||||
>
|
||||
> **Следствие:** изменения с Eagle/Kraken доезжают до телефона с задержкой **до 60 минут**.
|
||||
> Цепочка `bare repo → git-клон obsidian-syncthing → Syncthing → телефон` рвётся на **втором**
|
||||
> шаге (`Syncthing здоров` ≠ `док доехал`).
|
||||
>
|
||||
> **Ручной триггер (проверено):**
|
||||
> ```bash
|
||||
> docker exec hermes-taiga sh -c 'cd /opt/data && HERMES_HOME=/opt/data \
|
||||
> /opt/hermes/.venv/bin/hermes cron run 29851c413903'
|
||||
> # → Triggered job: sync-vault … Ran now: succeeded.
|
||||
> ```
|
||||
> После триггера: клон обновлён, Syncthing `needFiles: 0`, телефон `completion: 100`.
|
||||
>
|
||||
> **Открытый вопрос к Alex: менять расписание на `*/5 * * * *` или оставить раз в час?**
|
||||
>
|
||||
> ⚠️ **Диагностика синка — только изнутри контейнера Syncthing:** порт `8384` слушает лишь
|
||||
> в контейнере, `curl` с хоста TrueNAS даёт пустоту.
|
||||
> ```bash
|
||||
> docker exec syncthing sh -c 'K=$(grep -oE "<apikey>[^<]+" /var/syncthing/config/config.xml | sed "s|<apikey>||"); \
|
||||
> curl -s -H "X-API-Key: $K" "http://localhost:8384/rest/db/status?folder=k5vyy-gzdgj"'
|
||||
> ```
|
||||
> Папка Syncthing: id `k5vyy-gzdgj` «Obsidian Vault», устройства `SM-S931B` (телефон) и `syncthing-taiga`.
|
||||
> Готовый скрипт: `~/Developer/personal-os/tools/music-tags/check_syncthing2.sh`.
|
||||
|
||||
> **✅ STATE 2026-09-22 — рецидив ACL закрыт перманентно.** Дефект вернулся (35 root-каталогов),
|
||||
> push с Mac падал. Починено: **236/236 каталогов `INHERIT` + `WRITE`** через
|
||||
> `midclt filesystem.setacl` (read-modify-write + ожидание job). **Наследование подтверждено
|
||||
@@ -76,7 +105,7 @@ No stash. No GIT_DIR/GIT_WORK_TREE workarounds.
|
||||
|--------|-------------------------------|---------------|----------------------|
|
||||
| Eagle | `~/scripts/sync-vault.sh` | full clone | **launchd** `com.sync-vault` (⚠️ НЕ Hermes cron) `StartInterval=300` |
|
||||
| Kraken | `~/scripts/sync-vault.sh` | sparse clone | host crontab */5 |
|
||||
| Taiga | `/opt/data/sync-vault.sh` | 3-phase orch. | Hermes cron */5 |
|
||||
| Taiga | `/opt/data/sync-vault.sh` | 3-phase orch. | 🔴 **Hermes cron `0 * * * *` (РАЗ В ЧАС!)** — job `29851c413903`, см. STATE 2026-09-26 выше. Не `*/5`, как считалось ранее |
|
||||
|
||||
> **⭐ УТОЧНЕНО 2026-09-02 (Eagle/мак sync-механизм):** автосинка на маке запускается **НЕ Hermes cron, а launchd-агентом** `~/Library/LaunchAgents/com.sync-vault.plist`:
|
||||
> - Программа: `/Users/admin/scripts/sync-vault.sh`, `StartInterval = 300` сек (5 мин)
|
||||
|
||||
@@ -7,7 +7,85 @@
|
||||
> Со всеми поддерживаемыми фичами и тестами. Вперёд! Без вопросов и требующих апрува команд.»
|
||||
> Часть [[personal/projects/balda/balda-mm-delegation-plan]] (Фаза 2 → PR в апстрим).
|
||||
|
||||
## Итог
|
||||
## ✅ Итог: ветка PR-clean (2026-09-26, финал)
|
||||
|
||||
**7 коммитов**, рабочее дерево **чистое**, `tools/` **исключён** из ветки.
|
||||
|
||||
```
|
||||
9f1e6c9c chore: ignore local project-editing tooling
|
||||
58f27120 docs(mattermost): document the transport setup and behavior
|
||||
80d4f41d test(mattermost): cover the transport, the ingress and the handler
|
||||
c7f18654 fix(mattermost): send a monotonic seq on every client frame
|
||||
6dc3077a feat(mattermost): add the inbound handler and wire it into DI
|
||||
fa1a2855 feat(auth): support the Mattermost channel subject
|
||||
004fda2f feat(mattermost): add Mattermost transport adapter ← база прошлой сессии
|
||||
```
|
||||
|
||||
### 🔴 Уборка `tools/` — важный урок про PR
|
||||
|
||||
База `004fda2f` (прошлая сессия, **не мои 5 коммитов**) затащила в ветку **16 файлов
|
||||
`tools/`**: `apply-project-deltas.py`, `run-project-deltas.sh`, `deltas-mattermost*.json`,
|
||||
`.deltas-state.json`. **В апстриме каталога `tools/` нет вообще** — проверено
|
||||
`git ls-tree upstream/main tools/` → пусто.
|
||||
|
||||
**Как найдено:** `git diff --stat upstream/main...HEAD` показал `tools/` в списке файлов PR.
|
||||
|
||||
**Фикс (коммит `9f1e6c9c`):**
|
||||
1. `/tools/` добавлен в `.gitignore`;
|
||||
2. `git rm -r --cached tools/` — файлы **остались на диске** (39 файлов), ушли только из git.
|
||||
|
||||
⚠️ **`gitignore` НЕ отвязывает уже отслеживаемые файлы** — нужен именно `git rm --cached`.
|
||||
⚠️ **Проверять состав PR через `git diff upstream/main...HEAD`, а не по своим коммитам.**
|
||||
Свои 5 коммитов были чистыми (`git log upstream..HEAD -- tools/` → пусто), но ветка в
|
||||
целом — нет, из-за базового коммита.
|
||||
|
||||
**Результат:** в PR-diff **41 файл**, все — файлы проекта, ни одного `tools/`.
|
||||
|
||||
### Финальные гейты — все зелёные на закоммиченном дереве
|
||||
|
||||
```
|
||||
go build ./... → exit 0
|
||||
go vet ./... → exit 0
|
||||
go tool golangci-lint run → 0 issues
|
||||
go tool go-arch-lint check --project-path . → OK - No warnings found
|
||||
go test -race (затронутые пакеты) → все ok
|
||||
тесты mattermost + handlersfx (verbose) → 126 PASS, 0 FAIL, 0 SKIP
|
||||
```
|
||||
|
||||
**Тестов добавлено:** **103** (`grep -c '^func Test'`), по файлам:
|
||||
|
||||
| Файл | Тестов |
|
||||
|---|---|
|
||||
| `channel/mattermost/ingress_test.go` | 23 |
|
||||
| `channel/mattermost/inbound_test.go` | 18 |
|
||||
| `channel/mattermost/adapter_test.go` | 17 |
|
||||
| `handlersfx/mattermost_ingress_test.go` | 17 |
|
||||
| `channel/mattermost/locator_codec_test.go` | 15 |
|
||||
| `channel/mattermost/client_test.go` | 13 |
|
||||
|
||||
### Предсуществующие падения — НЕ наши (проверено `git stash`)
|
||||
|
||||
5 пакетов: `cmd/balda`, `apps/balda`, `mcpruntime`, `session`, `state`. Те же 4 теста
|
||||
падают **без** моих правок:
|
||||
`TestInitCommand_NonInteractiveAutoSelectsRootAndGeneratesDetectedAgents`,
|
||||
`TestCanonicalSessionMemoryRuntimeComposesPortableCapabilities`,
|
||||
`TestCanonicalSessionMemoryRuntimeMigratesAndReopensGroupedStore`,
|
||||
`TestResolveWorkspaceBaseBranch_ConfigPreferredWhenValid`.
|
||||
|
||||
### Лишнее, что я НЕ отправляю в PR
|
||||
|
||||
`tools/mm-*.sh` (9 ops-скриптов), `tools/deltas-*.json` (16 файлов дельта-групп),
|
||||
`tools/apply-project-deltas.py`, `tools/run-project-deltas.sh`, `tools/.deltas-state.json`
|
||||
— локальный инструментарий, остаётся на диске, в git не идёт.
|
||||
|
||||
### Осталось (по желанию Alex)
|
||||
|
||||
1. `git push -u origin feat/mattermost-channel` → открыть PR в `baldaworks/balda`.
|
||||
2. Фаза 3/4 — [[personal/projects/balda/balda-kraken-mm-deploy]].
|
||||
|
||||
---
|
||||
|
||||
## Итог (промежуточный, до уборки tools/)
|
||||
|
||||
Ветка `feat/mattermost-channel`, **6 коммитов** (базовый `004fda2f` + 5 новых). Готово к `git push` и PR.
|
||||
|
||||
|
||||
@@ -1192,6 +1192,37 @@ git log --oneline --name-only <mой_первый_коммит>^..HEAD -- '<dir>
|
||||
Альтернатива без «удаления»: оставить файлы untracked, а из индекса убрать одним
|
||||
явным коммитом по команде Alex.
|
||||
|
||||
## 🔴 PR-гигиена: проверять состав ветки, а не свои коммиты
|
||||
|
||||
База ветки `004fda2f` (коммит **прошлой сессии**) затащила в PR **16 файлов `tools/`**
|
||||
(`apply-project-deltas.py`, `run-project-deltas.sh`, `deltas-mattermost*.json`,
|
||||
`.deltas-state.json`). В апстриме каталога `tools/` **нет вообще** — проверка:
|
||||
`git ls-tree upstream/main tools/` → пусто.
|
||||
|
||||
🔴 **Свои коммиты были чистыми**, но ветка — нет:
|
||||
```bash
|
||||
git log --oneline --name-only upstream/main..HEAD -- 'tools/' # → показал базу, не мои коммиты
|
||||
```
|
||||
Поэтому смотреть надо **состав PR**, а не свои коммиты:
|
||||
```bash
|
||||
git diff --name-only upstream/main...HEAD # все файлы PR
|
||||
git diff --name-only upstream/main...HEAD | grep -v '^tools/' # что должно остаться
|
||||
```
|
||||
|
||||
**Фикс — два шага, оба обязательны:**
|
||||
```bash
|
||||
echo '/tools/' >> .gitignore
|
||||
git rm -r --cached tools/ # 🔴 файлы ОСТАЮТСЯ на диске, уходят только из git
|
||||
```
|
||||
|
||||
⚠️ **`gitignore` НЕ отвязывает уже отслеживаемые файлы.** Без `git rm --cached` файлы
|
||||
останутся в индексе навсегда, сколько ни игнорь их.
|
||||
⚠️ `git rm -r --cached` уходит на **апрув** (рекурсивное удаление) — если апрув истёк,
|
||||
команда **не выполнилась**, состояние целое; проверить `git diff --cached --name-only`
|
||||
до повтора.
|
||||
⚠️ Артефакты деплоя (`mm-*.sh` ops-скрипты) в PR не шлём — только код транспорта,
|
||||
wiring, тесты, доки.
|
||||
|
||||
## Связанные заметки
|
||||
|
||||
- [[personal/projects/balda/balda-mattermost-connector]] — Фаза 2, где всё это найдено
|
||||
|
||||
@@ -18,8 +18,81 @@ related:
|
||||
---
|
||||
# Music Tags Repair — TrueNAS (7906 файлов)
|
||||
|
||||
> **Статус: ЭТАПЫ 1–2 ЗАВЕРШЕНЫ (2026-09-26), затем ПЕРЕДЕЛАНЫ после замечаний Alex. CSV v2 передан. Документы ревью залиты в vault и синкнуты. Правки файлов НЕ начаты.**
|
||||
> Следующий шаг — апрув Alex по CSV, затем бэкап + применение.
|
||||
> **Статус: ЭТАПЫ 1–2 ЗАВЕРШЕНЫ (2026-09-26). CSV v3 + ноты ревью отданы Alex. Правки файлов НЕ начаты.**
|
||||
> Следующий шаг — апрув Alex по нотам ревью, затем бэкап + применение.
|
||||
> Перед выдачей проведён **полный аудит всех 4774 строк** (`audit_plan.py` + `final_gate.py`) — найдено и исправлено 6 багов.
|
||||
|
||||
## 🔴 ВТОРАЯ ВОЛНА ИСПРАВЛЕНИЙ (2026-09-26, «Сам проверил весь файл на адекватность?»)
|
||||
|
||||
Alex: **«Сам проверил весь файл на адекватность? Чтобы я в очевидные вещи тебя не тыкал.»**
|
||||
Честный ответ был — **нет, полностью не проверил**: до этого гонялись только выборочные проверки
|
||||
(`find_bad_titles.py` — 5 метрик). Полный аудит по всем 4774 строкам нашёл **6 реальных багов**.
|
||||
|
||||
| # | Баг | Файлов | Как проявлялся | Фикс |
|
||||
|---|---|---:|---|---|
|
||||
| 1 | `artist = Сказки` | **238** | Папка-контейнер `!Детские/Сказки/` попадала в artist → Jellyfin показал бы 238 треков одним «исполнителем» | `NON_ARTIST` применяется к контейнеру; под деревом сказок одиночная папка идёт в **album**, artist пустой |
|
||||
| 2 | Mojibake выживал рядом с латиницей | **127** | `Èâàíóøêè International`, `Ìóìèé Òðîëëü vs DJ Scratchin` — guard по доле кириллицы **на всю строку** отвергал конверсию | `fix_encoding` переписан **пословно** (`re.split(r"(\s+)")`): битые слова чинятся, латиница не трогается |
|
||||
| 3 | Имя радиовыпуска как артист | **31** | `!Radio/y OOF/`, `v 2009-03 mIr.vEsNЫ` → artist | `is_release_folder()`: `!`-префикс, год, `RELEASE_WORDS`; плюс fallback на имя файла |
|
||||
| 4 | Номер склеен с названием | **17** | `11 Track 11`, `04 Yves Larock - Rise Up` | Срезание ведущего номера + `title<-filename(denumbered)` |
|
||||
| 5 | `album = '+'` | **10** | Папка `старые/+/` — только символы | `clean_folder()`: компонент без букв/цифр отбрасывается |
|
||||
| 6 | `©` как title | **1** | Значение из одних символов | `clean_value()` отвергает значение без `isalnum()` |
|
||||
|
||||
**Дополнительно поймано и исправлено:**
|
||||
- `Пилот/1999 Жывой Концерррт` — фильтр релизов убивал **легитимный альбом** с годом. Разделено: строгая проверка только для **слота артиста** (`dirs[-2]`), альбом может содержать год.
|
||||
- 18 правок без `source` → теперь помечаются `encoding/cleanup only`.
|
||||
- Ложные тревоги, разобранные вручную (НЕ баги): `Би-2/2001 Би-2`, `Рубль/Рубль`, `Сектор Газа/1989 - Сектор Газа` — **одноимённые альбомы**; `NTL`, `Eagles`, `Carl Orff` — реальная латиница; `NTL ft. MC Молодой`, `Flёur` — нормальные смешанные имена.
|
||||
|
||||
### 📊 Цифры после второй волны
|
||||
|
||||
| Метрика | v1 (отклонён) | v2 | **v3 (финал)** |
|
||||
|---|---:|---:|---:|
|
||||
| `fix` | 4601 | 4961 | **4774** |
|
||||
| `no_change` | 2946 | 2945 | 3132 |
|
||||
| `probe_failed` | 359 | 0 | **0** |
|
||||
| Ноты: «требует решения» | 9 | 9 | **296** |
|
||||
| Ноты: средняя | 2611 | 2611 | 2031 |
|
||||
| Ноты: высокая | 2341 | 2341 | 2447 |
|
||||
|
||||
> 📌 **«Требует решения» вырос 9 → 296 — это честнее.** Раньше вместо признания «источника нет»
|
||||
> подставлялся мусор (`Сказки`, `y OOF`). Теперь такие файлы явно идут в блок 1.
|
||||
|
||||
**Итоговые гейты (все PASS):**
|
||||
|
||||
| Проверка | Скрипт | Результат |
|
||||
|---|---|---|
|
||||
| Полный аудит по классам ошибок | `audit_plan.py` | mojibake = **0**, `generic_artist` = **0**, `numeric_title` — законные |
|
||||
| Полнота и отсутствие заглушек | `final_gate.py` | **PASS** — нет `probe_failed`, пустых title, U+FFFD, правок без источника |
|
||||
| Регресс-тесты | `test_plan2.py` | **65/65 PASS** |
|
||||
|
||||
**Оставшиеся флаги аудита — все проверены вручную и законны:** `album == artist` (49, одноимённые альбомы),
|
||||
`translit_kept` (83, реальная латиница), `mixed_script` (19, `NTL ft. MC Молодой`), `numeric_title` (15, `007`/`48`).
|
||||
|
||||
## 📡 SYNCTHING: ноты доехали, но найдено расхождение в расписании
|
||||
|
||||
Проверка по просьбе Alex («Проверь что syncthing всосал доку»). Первая проверка показала:
|
||||
`obsidian-syncthing` **отстал на 3 коммита**, нот в нём нет.
|
||||
|
||||
**Причина — не Syncthing.** Цепочка «bare repo → git-клон `obsidian-syncthing` → Syncthing → телефон»
|
||||
рвётся на **втором** шаге: job `sync-vault` в контейнере `hermes-taiga` расписан **`0 * * * *` — раз в час**,
|
||||
хотя в `[[family/how-to/vault-git-sync]]` записано «каждые 5 минут».
|
||||
|
||||
**Что сделано:** job триггернут вручную (`hermes cron run 29851c413903`). Проверено:
|
||||
|
||||
| Проверка | Значение |
|
||||
|---|---|
|
||||
| Клон `obsidian-syncthing` | ✅ HEAD `e200596`, 4 файла на месте |
|
||||
| Syncthing: очередь | ✅ `needFiles: 0`, `needItems: 0`, `needDeletes: 0` |
|
||||
| Синк с телефоном | ✅ `completion: 100`, `remoteState: valid` |
|
||||
| Ошибки | ✅ `errors: 0` |
|
||||
| Версия Syncthing | `v2.1.5 "Hafnium Hornet"`, папка `k5vyy-gzdgj` «Obsidian Vault» |
|
||||
|
||||
**Питфоллы синка этой сессии:**
|
||||
- 🔴 **Порт Syncthing `8384` слушает ТОЛЬКО внутри контейнера** — `curl` с хоста TrueNAS отдаёт пустоту.
|
||||
Запрос API делать через `docker exec syncthing curl -s -H "X-API-Key: …" http://localhost:8384/rest/…`.
|
||||
- ⚠️ **`obsidian-syncthing` — git-клон (remote `/vault.git`), а не Syncthing-папка.** Обновляет его
|
||||
Taiga по крону; Syncthing лишь раздаёт то, что уже в клоне. «Syncthing здоров» ≠ «док доехал».
|
||||
- ⚠️ **Скрипт диагностики синка** — `check_syncthing2.sh` (в `tools/music-tags/`): версия, статус папки,
|
||||
connections, completion для телефона. Писать в файл, а не инлайн — иначе `APIKEY=` в heredoc режется.
|
||||
|
||||
## 📋 ДОКУМЕНТЫ РЕВЬЮ В VAULT (2026-09-26)
|
||||
|
||||
@@ -30,9 +103,9 @@ CSV отдан не только файлом, но и **читаемыми но
|
||||
| Файл | Содержимое |
|
||||
|---|---|
|
||||
| `music-tags-review.md` | **Обзор** — сводка, ссылки на три блока |
|
||||
| `1-needs-decision.md` | **Требует решения** (9 файлов) — исполнителя взять неоткуда |
|
||||
| `2-medium-confidence.md` | **Средняя уверенность** (2611) — из имён файлов и папок |
|
||||
| `3-high-confidence.md` | **Высокая уверенность** (2341) — точная перекодировка |
|
||||
| `1-needs-decision.md` | **Требует решения** (296) — исполнителя взять неоткуда |
|
||||
| `2-medium-confidence.md` | **Средняя уверенность** (2031) — из имён файлов и папок |
|
||||
| `3-high-confidence.md` | **Высокая уверенность** (2447) — точная перекодировка |
|
||||
|
||||
**Формат (запрос Alex):** порядок от «требует решения» к «высокая уверенность»,
|
||||
каждый файл = чекбокс `- [ ] **имя.mp3**` + подпункты `field: старое → новое` + источник.
|
||||
@@ -40,10 +113,10 @@ CSV отдан не только файлом, но и **читаемыми но
|
||||
|
||||
**Генератор:** `render_grouped_doc.py` (Mac, `~/Developer/personal-os/tools/music-tags/`).
|
||||
|
||||
**Синк:** `bash ~/scripts/sync-vault.sh` → `Pushed ✓`, коммит `14e9f570`.
|
||||
Проверено в bare repo: `git ls-tree -r HEAD | grep music-tags` → 4 файла на месте.
|
||||
**Синк:** `bash ~/scripts/sync-vault.sh` → `Pushed ✓`. Последний коммит после второй волны
|
||||
залит, клон `obsidian-syncthing` обновлён до `e200596`, Syncthing раздал телефону (`completion: 100`).
|
||||
|
||||
> ⚠️ Блоки 2 и 3 — по 730–780 КБ, 5000+ строк. Obsidian открывает, но скролл тяжёлый.
|
||||
> ⚠️ Блоки 2 и 3 — по 580–790 КБ. Obsidian открывает, но скролл тяжёлый.
|
||||
> Облегчение — оглавление по папкам в начале каждого файла (клик → прыжок к разделу).
|
||||
> Если Alex попросит — разбить по категориям на отдельные файлы.
|
||||
|
||||
@@ -333,15 +406,19 @@ if cyr < 2: # одна буква — совпадение
|
||||
| # | Шаг | Статус |
|
||||
|---|---|---|
|
||||
| 1 | **Инвентаризация** read-only: `ffprobe` по 7906 файлам → CSV | ✅ **ГОТОВО (v2)** — `tag_inventory2.py`, `probe_failed`=0 |
|
||||
| 2 | **CSV с предлагаемым исправлением** → Alex | ✅ **ГОТОВО (v2)** — `~/Downloads/music-tags/tag-fix-plan.csv` |
|
||||
| 2a | **Ноты для ревью в Obsidian** → Alex (запрос Alex 2026-09-26) | ✅ **ГОТОВО** — `personal/plans/music-tags-review/`, синкнуто (`Pushed ✓`, коммит `14e9f570`) |
|
||||
| 3 | **Апрув Alex по CSV** → только после этого правки | ⏳ **ЖДЁМ** (v1 отклонён, v2 + ноты отданы) |
|
||||
| 2 | **CSV с предлагаемым исправлением** → Alex | ✅ **ГОТОВО (v3)** — `~/Downloads/music-tags/tag-fix-plan.csv`, 4774 `fix` |
|
||||
| 2a | **Ноты для ревью в Obsidian** → Alex (запрос Alex) | ✅ **ГОТОВО** — `personal/plans/music-tags-review/`, синкнуто + доехало до телефона |
|
||||
| 2b | **Полный аудит всех строк** (`audit_plan.py` + `final_gate.py`) → 6 багов исправлено | ✅ **ГОТОВО** (запрос Alex «Сам проверил весь файл?») |
|
||||
| 3 | **Апрув Alex по нотам ревью** → только после этого правки | ⏳ **ЖДЁМ** (v1 и v2 отклонены, v3 + ноты отданы) |
|
||||
| 4 | **Бэкап** — копировать **только перезаписываемые** файлы в `Music/backup/` | ⏳ |
|
||||
| 5 | **Ремонт** — перекодировка mojibake + восстановление из имён путей + чистка мусора | ⏳ |
|
||||
| 6 | **Приёмка** — Alex слушает выборку | ⏳ |
|
||||
|
||||
> 📌 **Что именно ждёт апрува:** 4961 `fix` из 7906. Alex проходит по трём нотам ревью,
|
||||
> 📌 **Что именно ждёт апрува:** 4774 `fix` из 7906. Alex проходит по трём нотам ревью,
|
||||
> отмечает чекбоксы. Ничего не применяется до его «go».
|
||||
>
|
||||
> 🔴 **Открытый вопрос к Alex:** расписание job `sync-vault` в Taiga — `0 * * * *` (раз в час),
|
||||
> хотя доки говорят «каждые 5 минут». Менять на `*/5 * * * *` или оставить?
|
||||
|
||||
### Ключевые решения по плану (правки Alex)
|
||||
|
||||
@@ -379,19 +456,25 @@ if cyr < 2: # одна буква — совпадение
|
||||
| `id3_direct.py` | **Прямой парсер ID3v2** из байтов файла (минуя `ffprobe`). Поддержка ID3v2.2/2.3/2.4, кодировок 0–3, extended header. Используется как fallback и как отладочный инструмент |
|
||||
| `analyse_inventory.py` | Анализ CSV инвентаря: доли вердиктов, проверка обратимости mojibake, разбор `lost`/`probe_failed` |
|
||||
| `build_fix_plan.py` | **Этап 2 (v1, устарел).** Содержал ранний выход по `probe_failed` — из-за него ДДТ отдавались пустыми |
|
||||
| `build_fix_plan2.py` | **Этап 2 (v2, актуальный).** Ранний выход убран, `prettify_translit` добавлен. Содержит `fix_encoding`, `clean_value`, `repair`, `split_artist_title`, `title_from_name`, `derive_from_path`, `prettify_translit` |
|
||||
| `build_fix_plan2.py` | **Этап 2 (v3, актуальный).** Пословный `fix_encoding`, `NON_ARTIST`-контейнеры, `is_release_folder`, `clean_folder`, `prettify_translit`, срезание ведущего номера. Функции: `_try_convert`, `has_release_in_path`, `fix_encoding`, `clean_value`, `clean_folder`, `repair`, `split_artist_title`, `title_from_name`, `derive_from_path`, `is_release_folder`, `prettify_translit` |
|
||||
| `audit_plan.py` | **ПОЛНЫЙ аудит плана по всем строкам** (не сэмплы). Классы: `damaged_output`, `mixed_script_artist`, `translit_kept`, `watermark`, `numeric_title`, `artist_equals_title`, `generic_artist`, `album_equals_artist`, `one_char_title`, `whitespace`, `no_visible_change` |
|
||||
| `final_gate.py` | **Гейт полноты перед выдачей.** Проверяет: совпадение числа строк с инвентарём, отсутствие заглушек (`probe_failed`/`investigation`), пустых title, U+FFFD, правок без `source`. Печатает PASS или точные строки |
|
||||
| `deepdive_audit.py` | Углублённый разбор крупнейших классов (`generic_artist`, `translit_kept`) — по папкам и сырым значениям |
|
||||
| `separate_moji.py` | Отделяет **настоящий mojibake** от **законной латиницы** (`NTL`, `Eagles`) по доле маркеров CP1251 |
|
||||
| `review_classes.py` | Печать контекста по каждому классу аудита, сгруппировано по папке — чтобы судить целым альбомом |
|
||||
| `check_syncthing2.sh` | **Диагностика синка**: версия Syncthing, статус папки, connections, completion для телефона. Запрос через `docker exec syncthing curl … :8384` |
|
||||
| `check_fix_plan.py` | Контроль качества плана: U+FFFD на выходе, пустые title/artist, сэмплы «до/после» по категориям |
|
||||
| `find_bad_titles.py` | Ищет недоработки: `TrackNN` в title, голые номера, пустой artist |
|
||||
| `check_ddt_album.py` | Печатает **полный набор сырых тегов** файлов по маске — для проверки «баг скрипта vs так в файле» |
|
||||
| `diagnose_probe_failures.py` | Анализ заголовков файлов, упавших в `probe_failed`: ID3-версия, `hdr_ok`, размеры |
|
||||
| `test_encoding_guard.py` | **32 регресс-теста.** Защита от порчи корректного UTF-8 (`Björk`, `Saint-Saëns`, `Noël`) + `split_artist_title` |
|
||||
| `test_plan2.py` | **Регресс-тесты v2.** Включая отказ сплита на event-именах (`НАШЕСТВИЕ 2009 - ДЕНЬ ВТОРОЙ` остаётся целиком в title) |
|
||||
| `test_encoding_guard.py` | **32 регресс-теста (v1).** Защита от порчи корректного UTF-8 (`Björk`, `Saint-Saëns`, `Noël`) + `split_artist_title` |
|
||||
| `test_plan2.py` | **65 регресс-тестов (v3, актуальный).** Пословная конверсия смешанных строк, короткие токены (`7Á`→`7Б`), контейнеры не становятся артистом, release-папки, отказ сплита на event-именах |
|
||||
| `render_grouped_doc.py` | **Генератор нот для Obsidian.** CSV → 3 блока по уверенности + обзор, группировка по папкам, чекбоксы, оглавление |
|
||||
|
||||
| Артефакт | Где | Что |
|
||||
|---|---|---|
|
||||
| CSV-инвентарь v2 | `/mnt/RED_2TB/storage/tmp/tag-inventory2.csv` (NAS) → `~/Downloads/music-tags/tag-inventory.csv` (Mac) | 7906 строк, колонка `source`, `probe_failed`=0 |
|
||||
| **CSV-план правок v2** | `~/Downloads/music-tags/tag-fix-plan.csv` (Mac, 2.2 МБ) | **Отдан Alex 2026-09-26.** 4961 `fix` / 2945 `no_change` / 0 `probe_failed` |
|
||||
| **CSV-план правок v3** | `~/Downloads/music-tags/tag-fix-plan.csv` (Mac, ~2.2 МБ) | **Отдан Alex 2026-09-26 (финал).** 4774 `fix` / 3132 `no_change` / 0 `probe_failed` |
|
||||
| Копия БД Jellyfin | `/tmp/jf-copy.db` (Mac) | Для чтения API-ключа (см. `[[family/how-to/jellyfin-config]]`) |
|
||||
| Копия плана на NAS | `/tmp/tag-fix-plan.csv` (NAS) | Для запуска `id3_direct.py`/`check_ddt_album.py` с хостовым Python |
|
||||
| **Ноты ревью в vault** | `personal/plans/music-tags-review/` (4 файла) | Обзор + 3 блока по уверенности; **вход для апрува Alex**, синкнуто |
|
||||
@@ -436,6 +519,14 @@ if cyr < 2: # одна буква — совпадение
|
||||
- 🔴 **`execute_code` требует апрува** — Alex явно сказал: **«Без аппрува команды используй! Скрипты пиши»**. Писать исполняемое в файл (`write_file`) → запускать через `terminal`. Не инлайн.
|
||||
- 🔴 **Наивная перекодировка CP1251→Latin-1 портит корректный UTF-8** (`Saint-Saëns` → `Saint-Saлns`). Обязателен guard по доле кириллицы (см. выше).
|
||||
- 🔴 **`ffprobe` молча отдаёт ПУСТЫЕ теги** при битом фрейме внутри (напр. `comment='eng'`) — при `exit=0` и валидном JSON. Нельзя считать «пусто от `ffprobe`» доказательством отсутствия тегов. Нужен fallback-парсер ID3 (`id3_direct.py`).
|
||||
- 🔴 **Guard по доле кириллицы на ВСЮ строку ломает смешанные значения.** `Èâàíóøêè International` — битая кириллица + настоящая латиница; соотношение на строку не проходит порог, и mojibake **выживает**. Конвертировать **пословно** (`re.split(r"(\s+)")`), плюс второй проход по одиночным буквам (`Василий Ê`) при условии, что соседнее слово конвертировалось.
|
||||
- 🔴 **Токены с пунктуацией вне Latin-1 (`Ãëþê'OZA`) не конвертируются целиком** — `encode("latin-1")` падает на апострофе. Нужен посимвольный проход с проверкой итоговой доли кириллицы (≥50%).
|
||||
- 🔴 **Папка-контейнер (`Сказки`, `Разное`, `Различные исполнители`) — не артист.** Прямая подстановка `d_artist` даёт 238 файлов с одним «исполнителем». Контейнерный уровень отбрасывается; под деревом сказок одиночная папка идёт в **album**, artist пустой.
|
||||
- 🔴 **Папки релизов (`!Radio`, `y OOF`, `v 2009-03 mIr.vEsNЫ`) — не артисты.** Признаки: `!`-префикс, год в имени, `RELEASE_WORDS` (`radio`, `chart`, `mix`, `окtoberfest`…).
|
||||
- ⚠️ **Фильтр релизов не должен применяться к альбомам.** `Пилот/1999 Жывой Концерррт` содержит год, но это легитимный альбом. Проверять **только слот артиста** (`dirs[-2]`), не все компоненты пути.
|
||||
- ⚠️ **Значения из одних символов (`©`, `+`, `(17)`) — мусор.** И для тегов (`clean_value`), и для папок (`clean_folder`) — отвергать, если нет ни одной буквы/цифры.
|
||||
- ⚠️ **Ведущий номер, склеенный с названием (`11 Track 11`, `04 Yves Larock - Rise Up`),** — срезать регекспом и брать имя файла.
|
||||
- ⚠️ **Проверка «без источника» может ложно срабатывать** на правках, где изменились только пробелы/водяные знаки. Помечать такие явно (`encoding/cleanup only`), а не оставлять пустым `source`.
|
||||
- 🔴 **Ранний `continue` по «нераспознанному» вердикту = потеря файлов.** Именно так 359 файлов ушли заглушками, а ДДТ — без artist/title. **Неизвестный вход обрабатывать общей логикой, а не пропускать.**
|
||||
- 🔴 **Нельзя отдавать пользователю вывод, не проверив сэмплы проблемных групп.** CSV v1 ушёл Alex без проверки `probe_failed` и `!Retro` — и был справедливо раскритикован. Проверка = прогон `check_fix_plan.py` + `find_bad_titles.py` + выборочный `check_ddt_album.py`.
|
||||
- ⚠️ **Правка скриптов только локально → `scp` заново.** `sed -i` на NAS применён быть не должен (была такая ошибка, исправлена).
|
||||
@@ -445,20 +536,30 @@ if cyr < 2: # одна буква — совпадение
|
||||
### 🧠 ГЛАВНЫЙ ПРОЦЕССНЫЙ УРОК ЭТОЙ СЕССИИ
|
||||
|
||||
Alex: **«Ты бы хоть постеснялся не проверив мне файл сувать!»**
|
||||
Alex (вторая волна): **«Сам проверил весь файл на адекватность? Чтобы я в очевидные вещи тебя не тыкал.»**
|
||||
|
||||
Порядок работы, который себя оправдал **после** исправления:
|
||||
**Ключевое различие, которое стоило двух итераций: выборочная проверка ≠ полный аудит.**
|
||||
После первой правки были «пройдены все проверки» — но это были 5 метрик на сэмплах. Полный аудит
|
||||
по всем 4774 строкам нашёл ещё **6 багов**, крупнейший — 238 файлов с `artist = Сказки`.
|
||||
|
||||
Порядок работы, который себя оправдал:
|
||||
|
||||
1. Написать скрипт **в файле** (`write_file`) — не инлайн, не `execute_code`.
|
||||
2. Прогнать **автотесты** на краевых случаях (латиница с умляутами, дефисы, номера).
|
||||
3. Прогнать **контрольные проверки** на реальном выводе: U+FFFD, пустые поля, оставшийся мусор.
|
||||
4. **Посмотреть сэмплы «до/после» глазами** по каждой проблемной группе.
|
||||
5. Проверить **сырые данные конкретного файла**, прежде чем спорить «это баг / не баг».
|
||||
6. **Только потом** отдавать пользователю.
|
||||
2. Прогнать **автотесты** на краевых случаях (латиница с умляутами, дефисы, номера, смешанные строки).
|
||||
3. **Прогнать ПОЛНЫЙ аудит по всем строкам**, а не по сэмплам (`audit_plan.py`).
|
||||
4. **Прогнать гейт полноты** (`final_gate.py`) — заглушки, пустые поля, правки без источника.
|
||||
5. Посмотреть сэмплы «до/после» глазами по каждой проблемной группе.
|
||||
6. Проверить **сырые данные конкретного файла**, прежде чем спорить «это баг / не баг».
|
||||
7. Отделить **настоящие дефекты от законных случаев** (`separate_moji.py`, `review_classes.py`):
|
||||
`NTL`/`Eagles` — латиница, `Би-2/2001 Би-2` — одноимённый альбом.
|
||||
8. **Только потом** отдавать пользователю.
|
||||
|
||||
Три бага были пойманы пунктами 3–4 **до** выдачи. Пункт 5 отличает «баг скрипта» от «так в файле» (случай ДДТ).
|
||||
**Заглушка `needs investigation` в отчёте — это не результат, а моя недоработка.**
|
||||
**Заглушка `needs investigation` в отчёте — это не результат, а недоработка.**
|
||||
**«Требует решения» — законный исход, но должен быть явным, а не замаскированным мусором.**
|
||||
|
||||
### 🐞 Три бага, пойманные ДО выдачи CSV (файлы не пострадали)
|
||||
### 🐞 Девять багов, пойманных ДО выдачи (файлы не пострадали)
|
||||
|
||||
**Первая волна (3 бага, сэмплами):**
|
||||
|
||||
| # | Баг | Как пойман | Фикс |
|
||||
|---|---|---|---|
|
||||
@@ -466,9 +567,12 @@ Alex: **«Ты бы хоть постеснялся не проверив мне
|
||||
| 2 | `Camille Saint-Saëns` → `Camille Saint-Saлns` — перекодировка ломала корректный UTF-8 | `check_fix_plan.py`, сэмплы «partial» | Guard по доле кириллицы в результате (≥50%, ≥2 буквы) |
|
||||
| 3 | Управляющие байты (`\x1d`) в ID3 «чинились» вместо пометки | Просмотр сэмплов | `CONTROL_RE` → такие значения отвергаются, берётся имя файла |
|
||||
|
||||
> 🔑 **Методический вывод:** контроль качества плана (`check_fix_plan.py`) на сэмплах
|
||||
> «до/после» поймал все три бага. Без этого CSV ушёл бы Alex с испорченными предложениями.
|
||||
> **Всегда проверять вывод скрипта на сэмплах до передачи пользователю.**
|
||||
**Вторая волна (6 багов, полным аудитом):** см. таблицу «ВТОРАЯ ВОЛНА ИСПРАВЛЕНИЙ» в начале документа —
|
||||
`artist = Сказки` (238), mojibake рядом с латиницей (127), имя радиовыпуска как артист (31),
|
||||
номер склеен с названием (17), `album = '+'` (10), `©` как title (1).
|
||||
|
||||
> 🔑 **Методический вывод:** сэмплы ловят грубые ошибки, полный аудит — системные.
|
||||
> Оба нужны. Без полного аудита CSV v2 ушёл бы Alex с 238 файлами «исполнитель Сказки».
|
||||
|
||||
## Связанные заметки
|
||||
|
||||
|
||||
Reference in new issue
Block a user