скрипты: запись настроек перестала уходить мимо и молчать

Найдено ревью, каждое воспроизведено на фикстуре.

Граница репозитория. find_root обходил всех предков в поисках .av-dev.toml и
только потом смотрел на .git: вложенный проект объявлялся здоровым по конфигу
соседа, а init дописывал секцию в чужой репозиторий. Подъём останавливается на
первом .git. Туда же ключ [tasks] dir: он не судился как путь, и «../соседний»
уводил запись за пределы репозитория с зелёным кодом.

Запись TOML. Значения склеивались в кавычки без экранирования — имя индекса с
кавычкой ломало файл целиком, и оба скрипта после этого отвечали кодом 3 на
любую команду. Заголовок секции искался точным сравнением строки, так что
«[tasks]  # комментарий» — ровно та возможность, ради которой взят формат, — не
находился, и дописывалась вторая таблица. set_version правил version внутри
секции и ломал файл, если версия в кавычках.

Молчание вместо отказа. Неизвестные ключи не отвергались: migrations, положенный
верхним уровнем (а именно так его перенесут руками из .docs.json), давал
«проверка неприменима» и зелёный итог. Неверный dir проваливался в поиск вверх, и
скрипт работал в другом каталоге, не сказав о ключе. Прежние файлы узнавались
только когда нового нет — половина переезда проходила молча.

Честность доклада. Версию не двигал никто: set_version был написан и не подключён,
init печатал версию, которой не записал, adopt писал dir мимо --target. Теперь
повышение — команда docs.py bump, а init и adopt зовут общий write_config, который
пишет версию, дописывает ключи и вслух называет разошедшиеся. Отсутствие
shared/config.py давало трейсбек и код 1 вместо 3. Число зовётся LAYOUT_VERSION в
обоих скриптах вместо CANON_VERSION и FORMAT_VERSION.
This commit is contained in:
av
2026-08-13 10:56:49 +03:00
parent 7d559e60ec
commit 423f9798ef
4 changed files with 314 additions and 88 deletions
@@ -4,7 +4,7 @@
Каталог `openspec/` — предпосылка **конвейера**, а не канона документов: без него
не работают ни `opsx:propose`, ни ревью дизайна, ни сверка требований. Поэтому и
проверка формы живёт здесь, рядом со скиллом, который каталог заводит. Раньше она
жила в `docs.py` плагина канона, и у файла было два владельца: один заводит,
жила в `docs.py`, у скилла канона, и у файла было два владельца: один заводит,
другой проверяет.
Проверяется то, что **молчит при поломке**. Файл из коробки хуже отсутствующего:
@@ -219,8 +219,8 @@ def check_form(root: Path, rep: Report) -> None:
if not (root / where).exists():
rep.skip(
f"{where} в проекте нет — ссылка на него в context не "
f"требуется. Документы канона ведёт отдельный плагин "
f"(av-dev-docs), и без него конвейер работает вслепую"
f"требуется. Документы канона проект не завёл, и без них "
f"конвейер работает вслепую: заводит их av-dev:doc-canon"
)
continue
if pointer not in live:
@@ -289,8 +289,8 @@ def report(rep: Report) -> int:
"документов проекта и ключи rules против артефактов схемы. Чего она не\n"
"видит — **пересказ вместо ссылки**: утверждение, которое можно\n"
"опровергнуть, открыв другой файл проекта, от строки «открой такой-то\n"
"файл» она не отличает. Это суждение агента `doc-consistency` из плагина\n"
"канона документов; нет плагина — нет и этой проверки, и так и скажи."
"файл» она не отличает. Это суждение агента `doc-consistency`; документов\n"
"канона в проекте нет — сверять пересказ не с чем, и так и скажи."
)
if rep.errors:
print(f"\nИтог: дрейф, {len(rep.errors)} пунктов.")