150 lines
10 KiB
Markdown
150 lines
10 KiB
Markdown
---
|
||
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]] — техническая часть той же задачи
|