--- title: Чистка устаревших разделов в доках vault — метод created: 2026-09-15T00:00:00.000Z updated: '2026-09-15T23:59:00.000Z' type: tech namespace: personal tags: - obsidian - docs - vault - methodology - pitfalls - unicode confidence: high status: done related: - '[[family/how-to/truenas-infrastructure]]' - '[[personal/tech/vless-space-subscription-egress]]' - '[[personal/tech/xray-outbound-subscription-3xui]]' --- # Чистка устаревших разделов в доках vault — метод > ## 🎯 Когда применять > > Док накопил **летописи** — разделы вида «ПОПЫТКА 1…7», «ПРЕДЫДУЩИЙ ФИНАЛ: ЗАДАЧА НЕ ВЫПОЛНЕНА», «Волна 1 правок». Они описывают состояние, которое **уже противоречит факту**, и читаются как текущий статус. Alex: «Там никаких летописей не осталось точно?! Только статус и инструкции??» > > **Целевая форма дока:** статус → что хотели → как работает сейчас → процедура → питфоллы → артефакты. Грабли — **двумя строками**, не разделом. --- ## 🔴 Главное правило: вырезать блок ≠ задача выполнена **Ошибка этой сессии:** вырезал блок-летопись из 163 строк и отчитался «готово». Через ход Alex спросил про остатки — grep нашёл **ещё 6 устаревших мест** в том же доке, включая простыню «`vless-space` НЕ СОЗДАН, БД ОТКАЧЕНА» на строке 101. **Почему так вышло:** фокус на одном блоке, ноль проверки остального файла. В доке 990 строк — устаревшие утверждения живут не только в летописях, но и в шапках, таблицах, «Открытых вопросах». **Обязательный шаг после любого вырезания — grep по СМЫСЛУ, а не по имени блока:** ```bash grep -rn "НЕ СОЗДАН\|НЕ ВЫПОЛНЕН\|откачен\|НЕ ЗАЛИТ\|следующий шаг\|не начат" \ --include="*.md" family/ personal/ | grep -v "\.bak" ``` Плюс пройти по **всем связанным** докам (в этой задаче их было 5), а не только по тому, где нашёл проблему. --- ## 🔴 ПИТФОЛЛ 1: маркер не находится из-за нормализации Unicode (NFC/NFD) **Симптом:** скрипт падает на `assert end marker not found`, хотя строка в файле **визуально** точно такая, как в коде. **Причина:** macOS HFS+/APFS отдаёт имена/строки в **NFD**, Obsidian и git обычно пишут **NFC**. Русские буквы с диакритикой (`й`, `ё`) и эмодзи (`⚠️`) в двух формах — это **разные байты**. **Диагностика — сравнить длину:** ```bash awk 'NR==550' file.md | tail -c 200 | cat -v # покажет \M-^M и прочие артефакты ``` Если в конце видно `\M-^...` — строка не в той нормализации, что литерал в скрипте. **Лечение — не искать по точной строке целиком, а матчить по НАЧАЛУ или ХВОСТУ:** ```python # ❌ хрупко: ломается на нормализации и на любом изменении хвоста END_MARK = "отдельный кусок, не начат." if END_MARK in ln: ... # ✅ устойчиво: хвост без диакритики, проверяем через endswith по rstripped строке END_TAIL = "не начат." if ln.rstrip().endswith(END_TAIL): ... ``` Ещё надёжнее — маркер **без** диакритики/эмодзи (ASCII или простые кириллические буквы без `й`/`ё`). **Проверка нормализации файла:** ```bash python3 -c "import unicodedata,sys; s=open(sys.argv[1]).read(); print('NFC' if unicodedata.is_normalized('NFC',s) else 'NFD-или-смешанное')" file.md ``` --- ## 🔴 ПИТФОЛЛ 2: вырезал блок → украл заголовок секции **Симптом:** после вырезания раздела в доке **пропал подзаголовок** `### xray-admin — 3x-ui панель`, и секция стала сиротой в оглавлении. **Причина:** заголовок жил **внутри** вырезанного диапазона (он был частью летописи), а следующий за ним текст — уже живая часть дока. **Ошибка усугубилась:** я «восстановил» заголовок **вслепую**, не проверив, есть ли он ниже. В результате заголовок оказался **дважды** (строки 333 и 399), и настоящая секция с таблицей параметров шла второй. **Правило:** перед вырезанием — `grep -n "^#\{1,4\} " file.md` и записать, какие заголовки внутри диапазона. После вырезания — **проверить дубли**: ```bash grep "^### " file.md | sort | uniq -d # пусто = дублей нет ``` Проверка стыка глазами — обязательно (читаем 10 строк до и 10 после места реза). --- ## 🧱 Процедура (по шагам) 1. **Снять границы.** `read_file` с `offset`/`limit` вокруг блока; `grep -n "^#"` по файлу — полная карта заголовков. 2. **Классифицировать каждый раздел.** «Это статус/инструкция» → оставить. «Это рассказ о том, как я ошибался» → вырезать, **извлекая грабли**. 3. **Извлечь грабли в 1–2 строки** перед вырезанием (не после — иначе потеряются). 4. **Написать скрипт-резак** в `~/tmp-xray-space/`, не sed. Бэкап — первой операцией: ```python shutil.copy2(SRC, SRC.with_suffix(".md.bak-before-<что-делаем>")) ``` 5. **`assert` на каждый маркер** + на порядок маркеров (`start < end`). Скрипт обязан упасть, если разметка не та. 6. **Прогнать, проверить стык**, grep на остатки + дубли заголовков. 7. **Пройти по связанным докам** — тем же grep'ом на смысл. **Почему не `sed`:** прямое указание Alex — «NEVER use in-place scripts or SED for editing». Плюс sed не даёт `assert` и молча «съест» не тот диапазон. --- ## ✅ Чек-лист приёмки - [ ] Летописей нет: `grep -rn "ИСТОРИЯ\|ПОПЫТКА\|ПРЕДЫДУЩИЙ\|Волна" <файлы>` → только не относящиеся к теме - [ ] Дублей заголовков нет: `grep "^##\+ " f.md | sort | uniq -d` → пусто - [ ] Стыки читаются (10 строк до/после) - [ ] Wiki-ссылки живы - [ ] Бэкап `.bak-before-*` на месте - [ ] Связанные доки проверены, не только целевой > ⚠️ **Проверка wiki-ссылок — только относительным путём.** `[[obsidian-sync]]` — это `family/how-to/obsidian-sync.md`, не `obsidian-sync.md` в корне. Проверка «файл существует по литеральному имени» даёт **ложные срабатывания**: > ```bash > for n in $(grep -o "\[\[[^]]*\]\]" f.md | sed 's/.*\[\[//;s/\]\]//;s/|.*//'); do > find . -name "$n.md" -not -path "./.git/*" | head -1 > done > ``` > Найдено «5 битых ссылок» → все 5 существовали в других папках. **Не паниковать до `find` по всему vault.** --- ## 📉 Эффект (факт, 2026-09-15) | Файл | Было | Стало | Что убрано | |---|---|---|---| | `personal/tech/vless-space-subscription-egress.md` | 854 | 552 | 8 разделов-летописей → 2 строки граблей | | `family/how-to/truenas-infrastructure.md` | 990 | 844 | блок 163 стр. + чейнджлог моих правок + дубль заголовка | --- ## 🧱 ГРАБЛИ — 3 пункта (все поймал на себе) 1. **Вырезал один блок — проверь весь файл и все связанные доки.** Летописи живут не только в очевидных местах; устаревшее утверждение может сидеть в шапке или в таблице. 2. **Маркер для вырезания — без диакритики и эмодзи, матч по `startswith`/`endswith`.** Точное совпадение строки ломается на NFC/NFD (macOS отдаёт NFD). 3. **Заголовки внутри вырезаемого диапазона — зафиксировать ДО реза.** Восстановив заголовок вслепую, получишь дубль; проверять `sort | uniq -d`. ## Связанные заметки - [[family/how-to/truenas-infrastructure]] — где применялся метод (блок `xray-admin`) - [[personal/tech/vless-space-subscription-egress]] — где применялся метод (летописи попыток) - [[personal/tech/xray-outbound-subscription-3xui]] — техническая часть той же задачи