test(skills): реестр и гард общих inline-реализаций

Навыки автономны, общие утилиты копируются в каждый .ps1/.py, но нигде не было
зафиксировано, какая копия эталонная и какие расхождения законны. Ревизия по #60
показала два класса семей: одни держатся побайтово (support-guard — 16 копий,
db-обвязка — 12), другие разъехались без эталона (resolve_type_str — 6 копий и
6 вариантов, get_ml_text — 7 и 7).

check-inline-drift.mjs держит реестр семей внутри себя: вариант → эталон → копии.
Копия обязана совпадать с эталоном своего варианта; отклоняющийся вариант обязан
иметь обоснование, иначе печатается как долг. Часть расхождений законна (esc_xml
без " в form-* ради раундтрипа), поэтому модель хранит варианты, а не одно
эталонное тело. Разъехавшиеся целиком семьи стоят на храповике maxVariants.

Извлечение тел: PS1 — до строки ровно `}` (балансировка скобок даёт ложные
18 вариантов из 18 копий Assert-EditAllowed); PY — с обязательным снятием
docstring-ов (иначе одинаковый код с разным описанием читается как расхождение).

check-all.mjs — единая точка входа: check-enum-drift и check-uuid-invariant были
рабочими, но не упоминались в README и никем не запускались.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-08-08 17:22:33 +03:00
co-authored by Claude Opus 5
parent fc2d460b57
commit c8144a01e5
4 changed files with 530 additions and 11 deletions
+22 -11
View File
@@ -222,16 +222,27 @@ PS1 молча возвращает `$null` при обращении к нес
При доработке `.ps1`:
1. Применить аналогичные изменения в `.py`
2. Если затронуты inline-утилиты — обновить во всех скриптах: `grep -r "def esc_xml" .claude/skills/`
2. Если затронуты inline-утилиты — править **эталон** и копировать во всех потребителей варианта
(см. ниже), затем прогнать `node tests/skills/check-inline-drift.mjs`
## Inline-утилиты — полный список
## Inline-утилиты — реестр
| Функция | Где используется |
|---------|-----------------|
| `esc_xml()` | compile, init, edit, add скрипты |
| `emit_mltext()` | compile, init, add скрипты |
| `new_uuid()` | init, add, compile скрипты |
| `read_utf8()` | все скрипты |
| `write_utf8_bom()` | все скрипты с записью |
| `paginate()` | info скрипты |
| `split_camelcase()` | info скрипты |
Списка «на глаз» здесь нет: он протухает молча. Источник истины — реестр внутри гарда:
```bash
node tests/skills/check-inline-drift.mjs --list # семья → эталон → список копий
node tests/skills/check-inline-drift.mjs # проверить, что копии не разошлись
node debug/inline-utils/scan-dupes.mjs # найти кандидатов, которых в реестре ещё нет
```
Модель: у семьи есть один или несколько **вариантов**, у варианта — навык-**эталон** и список
копий. Копия обязана совпадать с эталоном своего варианта побайтово (с точностью до комментариев,
docstring-ов и пробелов).
Несколько вариантов — нормально, если различие содержательное, и тогда у варианта обязано быть
поле `why`. Пример: `esc_xml` в form-* не экранирует кавычки (платформа их в тексте элемента не
экранирует, `&quot;` ломает раундтрип), а в init-навыках экранирует — там он применяется к
значениям атрибутов. Вариант без `why` считается долгом и печатается как WARN.
Добавил новую общую утилиту — заведи семью в реестре, иначе гард сообщит, что навык содержит
функцию, но в реестре не объявлен.