Commit Graph
100 Commits
Author SHA1 Message Date
Nick ShirokovandClaude Opus 5 1bc943e846 feat(skills): новая группа вида встаёт в канонический порядок, а не в конец блока
Навыки-создатели дописывали первый объект нового вида перед </ChildObjects>. Собранная
ими конфигурация выходила в неканоническом порядке видов, и первая же загрузка-выгрузка
давала диф: на стенде подали Language -> Catalog -> Document -> CommandGroup ->
CommonCommand, платформа вернула Language -> CommonCommand -> CommandGroup -> Catalog ->
Document. При этом cf-edit add-childObject и cfe-borrow канонический порядок уже держали —
очередная «одна работа, разные реализации».

Логика перенесена из cf-edit в семью Register-InChildObjects и в вариант nested-parent:
запись встаёт перед первой группой вида старше по CHILD_OBJECT_TYPES. Для этого список
канонического порядка добавлен в meta-compile, role-compile, xdto-compile и
subsystem-compile (оба порта) и заведён в check-type-maps — копий стало 9 вместо 5, зато
все под гардом.

У подсистем нашлось больше, чем планировалось: вставка шла в конец ВСЕГО блока, поэтому
подсистема покидала и собственную группу, если ниже были другие виды. Одно правило
закрывает оба случая. Во вложенном Subsystem.xml порядок видов неприменим — потомок там
всегда один, поведение не изменилось.

Сдвиг эталонов широкий, но проверяемый: 42 файла в 12 навыках, 53 добавленные строки
против 53 удалённых с совпадающим мультимножеством — чистая перестановка, все изменённые
строки вида <Тег>Имя</Тег>. Из 425 эталонов с ChildObjects неканоничными осталось 15: 11
фикстур сортировки (неканоничны по замыслу) и 4 статические фикстуры на сохранение EOL и
разбор форм, которые мы намеренно не переупорядочиваем.

Версии подняты в обоих портах и за эту правку, и за предыдущую (карты типов).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QoAJmoNbgWKobA7JGgN5S3
2026-08-29 21:53:52 +03:00
Nick ShirokovandClaude Opus 5 bdd2ed63f9 fix(spec): позиция Bot в порядке типов ChildObjects, добавлен PaletteColor
Спецификация держала Bot на позиции 11 (после CommonModule), а платформа ставит его
сразу за DefinedType. Замерено на стенде: конфигурацию с намеренно дописанными в конец
Bot и PaletteColor загрузили и выгрузили обратно —

  8.5.1.1302:  DefinedType -> Bot -> PaletteColor -> CommonCommand
  8.3.27.1859: DefinedType -> Bot -> CommonCommand

то есть позиция не версионная, а взаимный порядок Bot и PaletteColor, которого не было
ни в одной выгрузке корпуса, теперь известен.

Последствия неверного эталона были не только косметические: cf-edit add-childObject уже
сегодня ставил новую группу Bot не туда, а планируемое выравнивание порядка групп по
такому списку передвинуло бы корректно лежащий Bot в конфигурации вроде УТ.

PaletteColor (8.5) в спецификации отсутствовал вовсе. Добавлен рядом с Bot; §7.4
поправлен: порядок типов между версиями не меняется, а набор растёт.

Синхронизированы все карты под гардом check-type-maps: порядок в cf-edit, cf-info,
cf-validate, cfe-validate, cfe-borrow и каталоги в тех же плюс cfe-diff. Проверка
эталона: последовательность групп в ut_8.3.27 и unf_8.5 теперь монотонна по списку —
раньше УТ давала False.

Кейс add-bot позицию не проверял: в фикстуре были только язык и бот, тест прошёл бы и с
неверным порядком. Добавлен add-childobject-bot-position с CommonModule, DefinedType и
CommandGroup, где место видно однозначно.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QoAJmoNbgWKobA7JGgN5S3
2026-08-29 21:21:46 +03:00
Nick ShirokovandClaude Opus 5 1046e6018c feat(cf-edit): виды с осмысленным порядком не сортируются автоматически
Правило «Subsystem не упорядочиваем» распространено на четыре вида и вынесено в
семью is_order_sensitive_type / Test-OrderSensitiveType: раньше литерал "Subsystem"
был размазан по четырём точкам — регистрация в Register-InChildObjects,
add-childObject, заимствование в расширение и сортировка.

CommonAttribute — исключение самого стандарта #std467: у общих реквизитов-разделителей
порядок в дереве задаёт порядок установки параметров сеанса
(https://github.com/1C-Company/v8-code-style/issues/78). Единственный из четырёх, где
сортировка ломает поведение, а не только диф.

CommandGroup и Language добавлены по замерам. В ACC 15 живых групп команд из 39 не
перечислены ни в одном GroupsOrder — как и подсистемы, они падают на порядок дерева.
Порядок языков задаёт порядок <v8:item> в мультиязычных строках по всей выгрузке.

Прежний вывод «сортировать их безопасно» опирался на стендовый прогон, где не было
ни подсистем (а значит и CommandInterface.xml), ни второго языка у объектов: ломаться
там было нечему, и отсутствие изменений ничего не доказывало.

PaletteColor сортируется — это переменные цветов.

Второе послабление стандарта (объекты «Удалить*» можно держать в конце ветки) не
реализуем: типовые им не пользуются — в ACC 360 таких объектов из 373 стоят по
алфавиту, а сортировка стандарту не противоречит. Оговорено в гайде.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QoAJmoNbgWKobA7JGgN5S3
2026-08-29 20:21:55 +03:00
Nick ShirokovandClaude Opus 5 ecde0d1c41 fix(skills): единый порядок поиска .v8-project.json для newObjectPosition
Резолвер настройки искал файл от каталога конфигурации и лишь потом от cwd —
единственное место в репозитории с таким отсчётом. Остальные ищут от рабочего
каталога: support-guard (18 навыков) с фолбэком на каталог конфигурации, группа
db-* (5 навыков) вообще без фолбэка, потому что каталога конфигурации у них нет.

Обоснование прежнего порядка («cwd почти всегда чужой проект») опиралось на нашу
стендовую раскладку — вызов из репозитория навыков по выгрузке в cfsrc. У
пользователя cwd и есть каталог проекта с .v8-project.json, а скрипт навыка
зовётся по абсолютному пути и cwd не меняет, так что оба порядка эквивалентны.

Кейс cfe-borrow/newobjectposition-byname клал настройку внутрь каталога расширения
и тем закреплял несуществующую раскладку — конфиг проекта лежит в корне проекта,
расширение внутри него. Поправлен на реальную.

Из гайда убран абзац, объяснявший особый порядок: объяснять больше нечего.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QoAJmoNbgWKobA7JGgN5S3
2026-08-29 19:14:27 +03:00
Nick ShirokovandClaude Opus 5 1ccd56606b fix(cfe-borrow): вернуть вставку заимствованных реквизитов в свой ChildObjects
При переводе сохранения объекта на семью Detect-XmlStyle/Finalize-XmlText из блока
выпал вызов Insert-IntoOwnChildObjects: заимствованные реквизиты собираются текстом
и вставляются в собственный ChildObjects объекта, а не через InnerXml (тот ломает
пространства имён). Без вызова слияние реквизитов при заимствовании по глубокому
пути молча теряло их.

Вызов стоит до Finalize-XmlText, чтобы схлопывание пустых тегов накрывало и
вставленные реквизиты — как было в исходном порядке.

Тестами не ловится: путь Merge-AttributesIntoObject не покрыт ни одним кейсом,
поэтому удаление прошло 29/29 и верификацию платформой. Проверено изолированным
прогоном связки: реквизит вставлен, самозакрытый ChildObjects раскрыт, пустые теги
плотные, XML валиден. Покрытие этого пути — отдельная задача.

Попутно: кейс type-name-russian расширен на add-childObject — русское имя вида
проверялось только в sort и remove.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QoAJmoNbgWKobA7JGgN5S3
2026-08-29 18:24:21 +03:00
c7b0dce151 feat(skills): порядок объектов метаданных в ChildObjects
Навыки-создатели дописывали новый объект в конец группы своего вида, а стандарт
требует порядка по имени (АПК:1108). Замеры на 8 боевых выгрузках: 103 385 объектов
лежат по алфавиту, нарушают его ровно дописки в хвост. Стенд подтвердил, что
беспорядок вечен: платформа нормализует порядок ВИДОВ (возвращает канонический),
но порядок имён внутри вида не трогает.

Настройка newObjectPosition в .v8-project.json (end по умолчанию | byName) —
решение проекта, а не вызова: читают её meta-compile, role-compile, xdto-compile,
cfe-borrow и cf-edit add-childObject. Компаратор при этом константа, моделирующая
дерево Конфигуратора: ключ «ранг+символ» без культурных таблиц, одинаковый в обоих
портах на любой ОС. На корпусе он даёт 4 нарушения на 125 088 пар против 2866 у
ordinal-сравнения, которым cf-edit и cfe-borrow сортировали до сих пор.

Subsystem не упорядочивается автоматически нигде: пока подсистемы не перечислены
в <SubsystemsOrder>, порядок дерева задаёт порядок разделов в панели, а платформа
этот список сама не заводит.

Настройка не чинит накопленное, поэтому cf-edit получил операцию sort-childObjects
(вся конфигурация или названные виды). Она переставляет значения узлов, а не узлы,
поэтому диф — чистая перестановка строк. Имя вида принимается в любом регистре,
во множественном числе и по-русски; неизвестный вид — отказ со списком допустимых.

Попутно:
- PS-порты создателей переведены с DOM-сериализации на текстовую вставку: на
  выгрузке не в каноне Конфигуратора они переписывали заголовок без просьбы;
- PS-сторона семьи detect_xml_style/finalize_xml_bytes закрыта в cf-edit и
  cfe-borrow — правка чужого файла наследует его BOM/EOL/заголовок;
- xdto-compile присоединён к семье Register-InChildObjects вместо инлайн-копии;
- закрыт ложный успех в py subsystem-compile: <ChildObjects /> с пробелом ET
  разбирает, а текстовые ветки не находили — файл писался без вставки;
- cfe-borrow py не импортировал json, из-за чего резолвер молча возвращал end;
- cf-edit add-childObject ставил объект в конец блока, за пределы своей группы,
  когда видов старше в файле не было.

Проверки: 906/906 на PowerShell и 903/906 на Python, 10 гардов, верификация
эталонов платформой без падений, раундтрип отсортированной конфигурации на
8.3.24 и 8.3.27, сортировка боевой выгрузки ACC (1,4 МБ) и расширения из корпуса.

Задачу принёс PR #85; часть кода взята оттуда.

Co-Authored-By: Sergei Pleshanov <72200277+Abacadabras@users.noreply.github.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QoAJmoNbgWKobA7JGgN5S3
2026-08-29 17:34:09 +03:00
Nick ShirokovandClaude Opus 5 7409bacd47 docs(db-repo): шаг 2 цитирует фактический текст предупреждения
В инструкции стояло «локальная конфигурация изменена: из хранилища
получено N», скрипт печатает «…изменена, получено объектов из
хранилища: N». Модель искала в выводе фразу, которой там нет.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 11:00:34 +03:00
Nick ShirokovandClaude Opus 5 a812d145bd test(db-repo): захват с получением версий и без него — две ветки закреплены
Различение было реализовано, но не покрыто: ни один фейковый лог не
содержал строки «Объект получен из хранилища», поэтому подсказка о
перевыгрузке могла начать печататься всегда — и тесты бы это пропустили.

Логи кейсов сняты с живого стенда (FINDINGS #14).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 11:00:34 +03:00
Nick ShirokovandClaude Opus 5 789047a975 test(integration): перечисления создаются до объектов, которые на них ссылаются
Справочник Номенклатура объявлял реквизиты типа EnumRef.* до того,
как сами перечисления попадали в конфигурацию. Валидатор, ужесточённый
в 7b40eb4c, проверяет разрешимость ссылочных типов и отвергал такое
промежуточное состояние.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 20:41:07 +03:00
Nick ShirokovandClaude Opus 5 3adffd1537 chore: выровнять версии PS-портов с py
Правило требует синхронного бампа обоих портов, а я поднял только py — дважды
за сегодня, в правке кавычек на POSIX и в выравнивании потоков. Версия
описывает состояние навыка, а не отдельного файла, поэтому пара обязана
двигаться вместе.

Двадцать один PS-порт выровнен по своему py.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 19:05:02 +03:00
Nick ShirokovandClaude Opus 5 4f61ef77ca fix(py-порты): ошибки печатать в тот же поток, что и PS
Двадцать один py-порт печатал ошибки в stderr, тогда как их PS-мастера пишут
через Write-Host в stdout. Счётчики совпадали один в один (14↔14, 19↔19,
13↔13) — сообщения были те же, разъехался только поток. Это нарушало
соответствие из docs/python-porting-guide.md, где Write-Host сопоставлен
обычному print.

Это не косметика. Харнесс не чередует потоки, а группирует: сначала весь
stderr, потом весь stdout. Из-за этого в py-порте вердикт «Error dumping
configuration (code: 1)» печатался ПЕРЕД строками, которые его объясняют, а
причина из лога платформы оказывалась в самом низу — причинный порядок вывода
переворачивался. Порт, работающий на macOS, читался хуже того, что работает на
Windows.

Тесты этого не ловили по построению: текст ошибки сверяют только кейсы со
строковым expectError, а он смотрит в stderr — потому такие кейсы есть лишь у
семейства, где потоки сходятся, а в db-* их ноль.

Добавлен гард check-error-streams.mjs: нет записи в stderr в PS-порте — не
должно быть и в py, и симметрично. Он сразу нашёл пять навыков сверх тех, что
я насчитал вручную, и отсеял два ложных срабатывания (в meta-remove слово
Write-Error стоит в комментарии «почему НЕ Write-Error»).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 19:03:34 +03:00
Nick ShirokovandClaude Opus 5 fdeae5c87f test(db-dump-xml): не сверять текст ошибки — потоки портов различаются
Кейс проверял текст через stdoutContains и был зелёным на PowerShell, красным на
python: Write-Host уходит в stdout, а py-порт печатает ошибки в stderr. Это
общее для группы расхождение, поэтому давний error-partial-no-objects тоже
проверяет только код возврата. Новый кейс приведён к тому же виду.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 17:58:25 +03:00
Nick ShirokovandClaude Opus 5 989f4c5526 fix(db-dump-xml,db-load-xml): список объектов задаёт частичную операцию
Список без режима давал молчаливо неверный результат, причём разный. У выгрузки
умолчание Changes игнорировало -Objects и отдавало «изменённое с прошлой
выгрузки». У загрузки на движке 1cv8 умолчание Full игнорировало -Files и
заменяло всю конфигурацию базы, тогда как на ibcmd та же команда выполнялась
частично — одни и те же аргументы вели себя по-разному.

Теперь перечисленные объекты или файлы сами задают частичную операцию. Режим
разрешается до ветвления на движки, поэтому расхождение исчезает по построению;
проверено на стенде: обе ветки дают одинаковый результат.

Заданный вместе со списком -Mode Full или Changes не отбрасывается молча — о нём
сообщает [note]. Список с -Mode UpdateInfo отвергается: это другая операция,
обновление ConfigDumpInfo без выгрузки файлов, и вывод подменил бы её.

Инструкции намеренно не менялись: -Mode Partial остаётся каноничной формой,
а вывод режима — прощающим вводом, который мы не документируем.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 17:43:39 +03:00
Nick ShirokovandClaude Opus 5 7c37fe2ac2 docs(db-repo): disconnect не необратим — мы сами подключали базу обратно
В каскаде стояло «Операция необратима». Это неверно: переподключение работает,
просто требует обоих флагов connect, о чём сказано абзацем выше. Ложная
безвозвратность отпугивала бы от законного действия.

Убрано и «пропускает диалог аутентификации»: в пакетном режиме с реквизитами
диалога нет — это теория из документации, а не действие.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 17:16:10 +03:00
Nick ShirokovandClaude Opus 5 1eb565b1d1 fix(db-repo): новый объект конфигурации — захватывается только корень
Инструкция велела захватывать «объект и корень». Объекта ещё нет ни в базе, ни
в хранилище, поэтому список отвергается целиком — «Загруженный список объектов
пуст» — и корень тоже не достаётся: модель осталась бы вовсе без захвата.

Верный порядок такой же, как у новой формы или макета: захватываем то, что
существует (корень), частичная загрузка создаёт объект, и при помещении он
называется вместе с корнем. Пример в SKILL.md разделён на два шага.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 17:10:42 +03:00
Nick ShirokovandClaude Opus 5 e26f122895 docs(tests): описать fakePlatform в формате кейса
Раннер получил новое поле — формат кейса обязан его описывать, иначе следующий
автор снова напишет .cmd руками и пометит кейс osOnly.

Отдельно сказано, что таким кейсам osOnly не нужен, и почему: именно забытый
-posix двойник давал дыру, из-за которой на маке выполнялся один кейс из
двенадцати.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 16:27:43 +03:00
Nick ShirokovandClaude Opus 5 0704c0ef12 test(runner): фейковая платформа — забота раннера, а не кейса
Кейс описывал механику: сам клал fake.cmd с batch-кодом, прописывал путь к нему
и помечался osOnly win32, потому что .cmd на маке не исполнится. Тот же сценарий
на POSIX требовал второго файла с fake.sh — отсюда двойники, а забытый двойник
означал дыру: у db-repo на маке выполнялся один кейс из двенадцати, и заметили
это случайно.

Теперь кейс объявляет намерение — лог и код возврата в поле fakePlatform, — а
раннер сам кладёт .cmd или .sh под текущую ОС, пишет лог и заглушку базы и
подставляет {fakePlatform} в аргументы. Механизм включается только по объявлению
поля; остальные кейсы не затронуты.

Мигрированы db-repo (11), db-update (4), db-load-xml (4); 19 двойников удалены.
Пропусков на Windows стало 0 вместо 19 — это и были двойники.

Кейсы db-create, db-dump-cf, db-run, epf-build оставлены как есть: часть из них
однопортовая по существу (cp866 в выводе, гейт «ложного успеха» на runtimeOnly),
и каждый требует отдельного решения.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 16:19:09 +03:00
Nick ShirokovandClaude Opus 5 84e908304e fix(db-*): на POSIX снимать и кавычки склеенных ключей
Проверка прямым вызовом платформы на darwin: /ConfigurationRepositoryF"путь с
пробелом" отвергается, тот же ключ без кавычек проходит. Значит на POSIX лишние
и обрамляющие кавычки значений, и кавычки внутри склеенных ключей — они нужны
только для склейки команды на Windows.

File="…" не трогаем: там кавычки часть синтаксиса строки соединения, и с ними
на POSIX всё работает (проверено db-create).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 15:45:51 +03:00
Nick ShirokovandClaude Opus 5 b7837c86ce fix(db-*): на POSIX снимать обрамляющие кавычки с аргументов платформы
Воспроизведено на darwin: db-load-xml с путём, содержащим пробел, падает с
«Неопределена информационная база». На POSIX аргументы уходят списком, и
кавычки, нужные для склейки команды на Windows, становятся частью значения.
Тот же механизм ранее молча терял многословный -comment в db-repo.

Правка в общей run_v8 (семья platform: run_v8) — чинит все двенадцать
потребителей разом. Снимается ОДИН слой обрамляющих кавычек, поэтому склеенные
ключи (/N"user", File="…", /ConfigurationRepositoryF"путь") не задеты: у них
кавычки внутри токена, там их ждёт разборщик 1С.

PS1 не затронут: там команда всегда склеивается в строку. Точечный arg_value(),
добавленный в db-repo при разборе дефекта, откачен — семейная правка делает его
лишним.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 15:42:46 +03:00
Nick ShirokovandClaude Opus 5 ab5c1fd7c1 fix(db-repo): бамп версии после правки кавычек на POSIX
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 14:56:41 +03:00
Nick ShirokovandClaude Opus 5 5e11cf2912 fix(db-repo): на POSIX не добавлять свои кавычки к значениям аргументов
Живой прогон на darwin показал: многословный -comment теряется целиком, а
однословный доходит. На POSIX аргументы уходят списком, и наши кавычки
становятся частью значения; склейка в одну строку нужна только на Windows,
там же нужны и кавычки.

Ключи вида /F"путь" и /N"имя" не трогаем: там кавычки внутри токена требует
сам разборщик 1С, и они нужны на обеих ОС.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 14:55:11 +03:00
Nick ShirokovandClaude Opus 5 72a4777192 test(db-repo): posix-двойники кейсов — на маке прогонялся один из двенадцати
Кейсы используют фейковую платформу в виде .cmd и потому помечены osOnly win32.
На Mac mini из двенадцати выполнялся один, то есть py-порт там почти не
проверялся — ровно та ловушка, о которой предупреждает памятка по стенду.

Добавлены двойники на fake.sh для всех кейсов, кроме уже имевшегося.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 14:21:09 +03:00
Nick ShirokovandClaude Opus 5 b653ca74d9 docs(db-repo): убрать навязанный рецепт сравнения версий
В history.md лежала цепочка «выгрузить CF версии → создать временную базу →
загрузить → выгрузить XML → сравнить». Это один из способов, поданный как
единственный: сравнить можно и средствами конфигуратора, а по задаче #76
источником сравнения может быть прямо версия хранилища, без временной базы —
тогда рецепт станет ещё и неверным.

Работа навыка заканчивается на dump-cfg; что делать с полученным CF, решает
пользователь.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 14:16:01 +03:00
Nick ShirokovandClaude Opus 5 24579c2580 docs(db-repo): в инструкциях только применение, http отделён от tcp
Перечитал изменённые инструкции свежим взглядом и убрал всё, что описывает
поведение навыка вместо способа им пользоваться: образец вывода отчёта (модель
увидит настоящий), фразу «навык подсказывает оба флага», объяснение, почему
длинный отчёт не печатается, и заверение, что неверная пара путь+расширение
ничего не ломает. Осталось действие: как назвать, что указать, чего ждать от
кода возврата.

Столбец «Что теряется» переименован в «Почему»: у lock -All не теряется ничего,
и строка «Ничего, но…» в таком столбце читалась криво.

Подсказка о недоступном хранилище разделена по схеме адреса: tcp ведёт к
серверу хранилища и порту, http — к веб-серверу и публикации. Прежний общий
текст советовал бы проверять сервер хранилища и порт 1542 для http-адреса, где
это неверно.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 14:14:18 +03:00
Nick ShirokovandClaude Opus 5 8edacf39c8 docs(db-list): убрать из инструкции историю проверок
В SKILL.md место только тому, как навык применять. Оговорка о том, что форма
адреса не описана в известной документации и проверена на стенде, — это история
работы, модели она ничего не даёт. Осталось применимое: вид адреса, порт по
умолчанию и то, что недоступный сервер отвечает тем же сообщением, что и
отсутствие реквизитов.

В справочнике проекта оговорка тоже переписана на факты: требование совпадения
версий сервера и платформы вместо рассказа о том, где мы это выясняли.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 14:08:30 +03:00
Nick ShirokovandClaude Opus 5 1c74843c05 feat(db-repo): сетевое хранилище проверено, различаем недоступный сервер
Установлен crserver 8.3.23, поднят на порту 1542 — сетевое хранилище перестало
быть непроверенным местом. Адрес tcp://<хост>[:<порт>]/<имя>, порт по умолчанию
1542; хранилище создаётся сервером по требованию. Весь цикл, файл списка
объектов и администрирование работают так же, как на файловом, и одинаково в
обоих портах. Отдельного поведения у сетевого хранилища не обнаружено.

Нашлась двусмысленность: недоступный сервер даёт «Соединение с хранилищем
конфигурации не установлено» — ровно то же сообщение, что и отсутствие
реквизитов. Прежняя подсказка «добавьте repository в реестр» в сетевом случае
уводила не туда. Теперь db-repo различает по схеме адреса и советует проверить
сервер и порт, а подсказка db-load-xml и db-load-git называет обе причины.

Документация больше не оговаривается «не проверено»: форма адреса подтверждена
на стенде.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 14:07:10 +03:00
Nick ShirokovandClaude Opus 5 3cdba1f70c docs: не выдавать синтаксис сетевого хранилища за документированный
Форма tcp://srv01/MyApp пришла из примера в ишью #77, а мы повторили её в
справочнике реестра и в db-list как факт. Прицельный поиск показал, что в
доступной документации её нет: глава 7.4.15 описывает только каталог хранилища,
а в обоих «Приложение 4» (8.3.24 и 8.3.27) не встречаются ни crserver, ни
tcp:// — совпадения по слову «хранилище» относятся к лицензированию.

Теперь сказано «каталог хранилища или адрес сервера хранилища» и добавлена
оговорка: путь передаётся платформе как есть, конкретный синтаксис в известной
нам документации не описан, на серверном хранилище навык не проверялся.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 13:54:33 +03:00
Nick ShirokovandClaude Opus 5 b2ee1216bf feat(db-load-xml,db-dump-xml): каталог расширения из extensions[].src
Пункт плана, остававшийся открытым: каталог исходников расширения модель
указывала руками, хотя он объявлен в записи базы. Каталог выбирает модель, а не
скрипт, поэтому это строка в инструкции — рядом с такой же про configSrc.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 13:50:54 +03:00
Nick ShirokovandClaude Opus 5 4f6bfd75eb fix(db-repo): длинный отчёт по версиям не печатать вовсе, а не обрезать
Печать отчёта целиком стоит модели контекста, но опаснее другое: обрезанный
отчёт по версиям читается как полный ответ. По куску легко заключить, что
объект не менялся, — в отличие от списка объектов, где неполнота очевидна.

Короткий отчёт (до ста строк) печатается целиком, длинный не печатается
совсем: называется путь к файлу и способы сузить выборку. Порог не
теоретический — даже стенд из девяти версий даёт 124 строки.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 13:47:39 +03:00
Nick ShirokovandClaude Opus 5 ac20d95fea feat(db-repo): снять с модели рутину, которую видно по прогону каскада
Три места, где модель делала работу за навык.

report требовал -OutputFile, хотя отчёт всё равно печатается в вывод: путь
приходилось придумывать ради файла, который никто не читает. Теперь без
параметра отчёт уходит во временный файл, а путь называется — сохранить
осознанно по-прежнему можно.

create и connect с явным путём хранилища теперь печатают готовый блок
repository для .v8-project.json. Без записи в реестре реквизиты придётся
передавать в каждом вызове, а update откажется работать вовсе — вспомнить
об этом модель не может, а подставить готовое мы можем.

Подсказка про -ForceReplaceCfg сразу называет и второй флаг: при
переподключении платформа отвергает дважды подряд, сообщая о причинах по
одной, и модель упиралась бы два раза.

Проверено, что печатаемый блок — валидный JSON: строка из вывода разбирается
парсером, путь читается обратно. В PS обратный слэш в строке замены -replace
не спецсимвол, из-за чего слэши сперва удвоились дважды.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 13:43:04 +03:00
Nick ShirokovandClaude Opus 5 8faa1f77c0 feat(db-repo): подсказки по отказам подключения, смоук каскадных операций
Административные и сервисные команды собирались по документации, а вживую
гонялись сырыми вызовами платформы — сборка аргументов навыком не проверялась.
Прогон через навык показал, что работают dump-cfg (включая -Version), set-label,
add-user, clear-cache во всех областях, optimize, copy-users, create -NoBind.

Нашлись два тупика подряд при переподключении базы: платформа сначала отвергает
непустую конфигурацию, а после -ForceReplaceCfg — то, что за пользователем
хранилища уже числится эта база. Оба раза действие не называется. Добавлены
подсказки на оба сообщения, сценарий описан в references/connect.md.

Заодно постусловие update сработало на настоящей аварии: после неудачного
переподключения база осталась отключённой, и команда отказалась вместо тихой
замены конфигурации.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 13:36:42 +03:00
Nick ShirokovandClaude Opus 5 e64c48cdfd fix(db-repo): полная форма вызова в каскаде — короткая ломала переключение рантайма
Файлы references/ несли сокращённую запись «... db-repo.ps1 -Command …», и
scripts/switch.py её не видел: его регулярное выражение требует полной формы
powershell.exe -NoProfile -File <path>.ps1. В python-дистрибуции каскад остался
бы с .ps1, то есть на macOS указывал бы на неисполнимый файл, а гард
переносимости этого не ловит — он следит за плейсхолдером ${CLAUDE_SKILL_DIR},
которого в сокращённой записи тоже не было.

Проверено прогоном switch_runtime_content по всем четырём файлам: до правки
переключатель находил 0 вызовов при трёх упоминаниях .ps1, после — все
конвертируются, .ps1 не остаётся.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 13:33:00 +03:00
Nick ShirokovandClaude Opus 5 19d62afb29 docs(db-list): схема хранилища конфигурации в реестре баз
db-list/SKILL.md — фактическая спека .v8-project.json, которую читает модель,
и без repository она давала неполную картину: база под хранилищем не принимает
ни одной операции конфигуратора без реквизитов доступа, причём это касается не
только db-repo, но и всей группы загрузки-выгрузки.

Добавлены repository и extensions[] в пример и таблицу полей, раздел с
описанием обоих (включая то, что у расширения своё хранилище со своим путём,
а пользователь хранилища не наследуется от пользователя базы), пункт в
интерактивный сценарий добавления базы и форма ключей доступа в разделе про
строку подключения.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 13:29:55 +03:00
Nick ShirokovandClaude Opus 5 4a49eea422 feat(db-repo): порог на длину вывода и функциональные тесты
Операция над всей конфигурацией перечисляет тысячи объектов, и весь список
уезжал в вывод модели. Списки обрезаются на двадцати позициях, полный уходит
в файл, путь к которому назван. Сырой лог платформы при отказе показывается
хвостом в 200 строк: итог и причину платформа пишет в конце.

Тесты на фейковой платформе — 12 кейсов, разбор лога проверяется без 1С на
логах, снятых со стенда. Ключевой кейс закрывает главное правило навыка:
платформа вернула 1, часть объектов захвачена, навык возвращает 0 с поимённым
предупреждением. Обрезка списка проверена на логе из тридцати объектов —
кейс требует и строку «и ещё 10», и отсутствие двадцать первого объекта
в выводе.

Проверено негативным контролем: намеренно сломанное ожидание роняет кейс,
то есть тесты действительно сверяют вывод, а не проходят вхолостую.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 13:25:11 +03:00
Nick ShirokovandClaude Opus 5 67b8ad5b47 fix(db-repo): порядок вывода и формулировки подсказок
Прогон всех веток вывода подряд показал, что длинный совет про перевыгрузку
вставал МЕЖДУ фактами и вердиктом: строка «захват не выполнен» терялась в
середине. Совет теперь печатается последним во всех ветках — сначала факты,
затем вердикт.

Убрана мёртвая подсказка про -Yes: флага больше нет. Снято утверждение «по
убыванию частоты» у причин отказа commit — проверить его нечем. Сняты повтор
слова «проверьте» и склонение «объект(ов)». Заголовок «Не были захвачены»
дополнен пояснением, что снимать нечего.

Примеры в шапке скрипта приведены к именованной подкоманде.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 13:19:39 +03:00
Nick ShirokovandClaude Opus 5 1327b4fc5a feat(db-repo): py-порт, реестр семей копий и подсказки в db-load-git
Порт .py зеркалит .ps1 по порядку функций, именам и комментариям — расхождения
только там, где их диктует рантайм. Общие функции взяты из соседних портов
дословно.

Копии зарегистрированы в check-inline-drift.mjs: db-repo присоединён к шести
платформенным семьям, четыре функции блока реквизитов хранилища заведены новыми
семьями, разбор сообщений хранилища — семьёй с эталоном db-load-xml. Гард сразу
нашёл настоящее расхождение: копии Get-RepositoryArgs в четырёх соседях остались
со старым comma-return, эталон правился позже. Ресинхронизировано.

Разбор сообщений хранилища добавлен и в db-load-git: он тоже грузит в базу
частично и получает те же три отказа платформы.

Подкоманда стала именованной (-Command): в группе скрипты принимают только
именованные параметры, слэш-форму в них переводит модель. Позиционный параметр
у пишущего навыка запрещён гардом check-positional-binding. Mandatory при этом
не ставим — обязательный параметр PowerShell запрашивает интерактивно, а в
пакетном запуске это зависание.

Все девять гардов и функциональные тесты db-* зелёные в обоих рантаймах.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 13:08:52 +03:00
Nick ShirokovandClaude Opus 5 012f5c3c30 feat(db-repo): операции над всей конфигурацией — только по явному -All
Захват всей конфигурации на большой базе идёт долго и блокирует работу всей
команде, а получался он от одного забытого -Objects: «вся конфигурация» была
умолчанием. Теперь для lock, unlock и commit это отдельный флаг -All, который
нельзя совместить с -Objects. Захват корня с -WithChildren, означающий то же
самое, отсылает к нему же.

update без -Objects остаётся обычным вызовом: получение всех изменений — это
нормальный ежедневный сценарий, а не тяжёлая операция.

Флаг отдельный, а не -Force: у -Force уже есть платформенное значение, разное
по подкомандам, и третье значение сделало бы его нечитаемым.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 12:51:05 +03:00
Nick ShirokovandClaude Opus 5 33633aaf0e feat(db-repo): подменять часть объекта владельцем вместо отказа платформы
Модель может попросить захватить реквизит, табличную часть, измерение или
ресурс. Платформа на это отвечает «Загруженный список объектов пуст» — без
имени объекта и без причины, отладить такой ответ нечем.

Навык распознаёт вложенные виды подчинённых и подменяет их объектом-владельцем,
сообщая о подмене. Это не меняет смысл запроса: владелец — минимально возможная
единица захвата для того, что просили, потому что своей сущности у части объекта
нет. Отказ стоил бы лишнего круга без пользы.

Заодно убран comma-return у двух функций: в связке с @() у вызывающего он даёт
вложенный массив, из-за чего список объектов склеивался в один fullName.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 12:45:36 +03:00
Nick ShirokovandClaude Opus 5 3e816d3e12 feat(db-repo): правила захвата, нулевой шаг цикла и диагностика по отчёту субагента
Проверка навыка субагентом на сквозном сценарии вскрыла фактическую ошибку
в инструкции: реквизиты и табличные части перечислялись наравне с формами как
захватываемые объекты. Платформа их объектами не считает — список объектов
получается пустым, причём без секции «отсутствующие в конфигурации». Правишь
реквизит, табличную часть, измерение, ресурс или модуль — захватывай владельца;
формы, макеты и команды захватываются отдельно.

Сообщение «объект не найден» имеет три разные причины: опечатка, отставание
базы от хранилища и попытка захватить то, что объектом не является. Теперь
перечислены все три.

В цикл добавлен нулевой шаг — получение актуального состояния перед началом
работы: правки должны опираться на актуальные версии в том числе тех объектов,
которые не меняются, но используются. Загрузка и обновление БД слиты в один
шаг через -UpdateDB, поэтому цикл не удлинился.

Захват корня конфигурации с -WithChildren отклоняется: это захват всей
конфигурации, а выглядит как захват корня. Для всей конфигурации есть
однозначная форма — вызов без -Objects.

Текстовый отчёт печатается, а не только сохраняется в файл.

Схема repository и extensions[] описана в docs/v8-project-guide.md,
цикл под хранилищем — в docs/db-guide.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 12:40:44 +03:00
Nick ShirokovandClaude Opus 5 478e7489f9 feat(db-repo): навык работы с хранилищем конфигурации 1С
Новый навык /db-repo с подкомандами: рабочий цикл (lock, unlock, commit,
update), подключение базы (connect, disconnect), история (report, dump-cfg),
администрирование (create, add-user, copy-users) и сервис (set-label,
optimize, clear-cache). Каскад инструкций в references/.

Ядро навыка — разбор вывода платформы: код возврата о фактическом результате
не говорит. Захват и помещение не атомарны (код 1 при реально захваченном
объекте), а все no-op'ы дают код 0. Вердикт строится по строкам лога и
отражает достижение запрошенного состояния, а не факт изменения.

Захват, обновление и unlock -Force молча подтягивают свежие версии в локальную
конфигурацию. Навык называет полученные объекты и печатает готовую команду
перевыгрузки: без неё частичная загрузка старых исходников молча откатывает
чужие изменения.

Соседние навыки группы получили общий блок разрешения реквизитов хранилища из
.v8-project.json — база под хранилищем не принимает без них ни одной операции
конфигуратора. db-load-xml дополнительно подсказывает действие по трём
сообщениям платформы, db-dump-xml получил -ObjectsFile для передачи списка
объектов между навыками без перепечатывания.

Порты .py следуют отдельно.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 12:31:39 +03:00
0442aa015a fix(tests,web-test): не отдавать rmSync/cpSync пути с не-ASCII символами
На Windows fs.rmSync и fs.cpSync молча не делают ничего, когда не-ASCII символы
есть в самом аргументе пути (nodejs/node#61067). Для аудитории проекта это боевой
сценарий: кириллическое имя пользователя даёт кириллический %TEMP%, где раннер
создаёт воркспейсы, плюс кириллические имена объектов 1С в путях внутри кейсов.

Замерено на восьми сборках. Затронуты 22.18.0–22.23.2 (последняя LTS Jod) и
24.12.0–24.14.0; исправны 22.15.1, 24.15.0+, 25.x, 26.x. Фикс приехал в fs-слой
Node 24.15 и в ветку 22.x не бэкпортирован, поэтому «обновить Node» вопрос не
закрывает. Зависимость не монотонна по версиям (24.14 чинит rmSync, но не
cpSync) — гард по номеру версии невозможен, решает только сам путь.

Симптомы: rmSync молча ничего не удаляет; cpSync с не-ASCII приёмником молча
ничего не копирует; cpSync с не-ASCII источником валит процесс нативно
(0xC0000409) мимо try/catch. Не затронуты mkdirSync, readdirSync, lstatSync,
copyFileSync (включая перезапись), unlinkSync, rmdirSync — на них стоит обход.

Что ломалось: под кириллическим %TEMP% фикстуры не доезжали до воркспейсов
(meta-info — 15 ложных падений из 26, один кейс ложно-зелёный на пустом
воркспейсе); cfe-validate/module-state-flag-without-file был красным даже при
ASCII %TEMP% (кириллица в самом deletePath); --update-snapshots молча не сносил
старый эталон, то есть портил коммитимые артефакты.

Реализация — единый fsutil в двух побайтно одинаковых копиях (tests/common/ и
внутри автономного навыка web-test), раскатанный на все 42 точки вызова в 11
файлах: tests/skills/*, tests/web-test/*, hooks/test/run.mjs, движок web-test.
Предикат судит по resolve(p), а не по строке аргумента: относительный
ASCII-аргумент при не-ASCII cwd платформа роняет так же молча. Ретраи доживают
до ручного обхода, перезапись как у cpSync с force: true, настоящие ошибки не
глотаются, выживший после удаления путь — громкая ошибка.

Гард tests/skills/check-nonascii-fs.mjs в check-all.mjs проверяет хелпер, а не
платформу (поэтому зелёный и на исправной Node), сверяет хеши обеих копий и
печатает справкой состояние текущей сборки.

Co-Authored-By: androman.pro <5669019+andromanpro@users.noreply.github.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 19:27:02 +03:00
Nick ShirokovandClaude Opus 5 36bae190b2 test(role-compile): объявлен пропуск платформенной проверки для намеренно невалидных фикстур
Фикстуры childobjects-missing и childobjects-selfclosed невалидны by design: первая вовсе без
<ChildObjects>, вторая с пустым. Кейсы проверяют реакцию навыка на такую конфигурацию, а платформа
её не принимает — «читаемое свойство не соответствует ожидаемому. Текущее Configuration, ожидаемое
ChildObjects» и «Неизвестный объект метаданных - Language.Русский» соответственно.

Причина пропуска записана по факту отказа платформы, а не по предположению.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 18:46:15 +03:00
Nick ShirokovandClaude Opus 5 66a11d1dba fix(tests): verify-snapshots понимает inputRaw, inputEncoding и версию формата EPF/ERF
Полный прогон верификации давал шесть падений; четыре из них — дефекты самого инструмента.

Вход кейса. Файл писался только из caseData.input и только UTF-8, поэтому кейсы, задающие вход
через inputRaw (сырая строка) и inputEncoding (UTF-16, cp1251), оставляли навык без входного
файла: meta-compile/encoding-utf16-bom и interface-edit/bad-definition-file падали на «Cannot bind
argument to parameter». Хуже того, кейс, у которого нет ни input, ни preRun, ни params, отбрасывался
в discoverCases как «ничего не подающий навыку» — inputRaw там тоже не учитывался, и такие кейсы
в верификацию не попадали вовсе (meta-compile/bad-encoding-cp1251,
subsystem-compile/bad-definition-file). Теперь оба ключа поддержаны, encodeInput перенесён копией
из runner.mjs — общего модуля у раннеров нет, и README предписывает добавлять ключ в оба.

Это ровно та «тихая дыра», о которой README предупреждает: ключ, известный лишь одному раннеру,
даёт кейс, зелёный в функциональном прогоне и не доезжающий до платформы в верификации.

Версия формата. Гейт вызывался только при наличии каталога конфигурации, поэтому кейсы
epf-init/format-221 и erf-init/format-221 доходили до сборки и падали на стенде без 8.5 — красным,
хотя это свойство стенда. Ядро гейта выделено и читает версию из шапки любого MetaDataObject,
включая исходник обработки и отчёта; в EPF-ветке проверка выполняется до epf-build.

Проверено точечно: два кейса с inputRaw проходят, два format-221 честно пропускаются по версии
платформы, два ранее невидимых кейса берутся в прогон и проходят. Функциональный раннер не задет:
role-compile 26/26, meta-compile 85/85.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 18:46:15 +03:00
Nick ShirokovandClaude Opus 5 9c66e89372 fix(tests): verify-snapshots грузит фикстурные кейсы и показывает долю непроверенного
configDir приходил только из скилл-уровневого setup (empty-config) и из ветки external, поэтому
у навыков с setup: none кейс на фикстуре до платформы не доезжал — и всё равно получал PASS.
Таких кейсов девять: пять support-edit, три meta-validate и один skd-decompile. В наборе по
умолчанию дефект не проявлялся: там у всех навыков setup: empty-config, и загрузка шла.

Теперь фикстура-конфигурация грузится как external-выгрузка, а путь «грузить нечего и спец-маршрут
не подошёл» вместо молчаливого PASS требует объявить skipPlatformVerify с причиной. skd-decompile
отнесён к standalone-навыкам: его выход — JSON-описание, грузить нечего.

Отдельно — видимость: «прошло» и «проверено платформой» больше не одно и то же число. В консоли и
в отчёте печатается, сколько кейсов реально обратилось к платформе, а сколько прошло мимо и почему
(external, standalone, ожидаемый отказ навыка, недоступная платформа); в таблице отчёта появилась
колонка «Платформа». Падения в эту долю не попадают: у них обращение было и не удалось.

Девяти кейсам проставлен пропуск с проверенной причиной. Для support-edit причина выяснялась
опытом: фикстуры содержат рукотворный Ext/ParentConfigurations.bin, и платформа отвечает «Ошибка
формата потока», тогда как настоящий bin из типовой грузится, а выгрузка без bin — тоже. Попытка
пересобрать фикстуры полноценными объектами вылечила XML, но не bin, и была откачена.

Документация: в README добавлена таблица маршрутов платформенной проверки и правила появления
configDir — раньше это знание жило только в коде, и «зелёный» отчёт не отличался от «проверенного».
В docs/1c-support-state-spec.md записано, что по описанной грамматике bin можно читать, но нельзя
собрать файл, который примет платформа.

Полный прогон verify-snapshots до и после правки даёт одинаковый состав: 461 passed, 6 failed,
30 skipped из 497 — новых падений нет. Тесты: 871/879, интеграционные 13/13.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 18:04:55 +03:00
Nick ShirokovandClaude Opus 5 333bcad938 fix(meta-validate): batch выполняется в одном процессе вместо процесса на объект
py-порт в batch-режиме запускал сам себя через subprocess на каждый объект, и на разбор в единицы
миллисекунд приходилось ~146 мс старта интерпретатора с импортом lxml. На корпусном прогоне это
давало 92 мс на объект против 40 мс у PS — при том, что обычно py быстрее в разы. Причина
асимметрии: PS вызывает себя через & в том же процессе, платя свой дорогой старт один раз на батч.

Теперь py делает то же самое: файл компилируется один раз и выполняется в свежем globals() на
каждый объект. Изоляция состояния получается той же, что у & в PowerShell (script-переменные не
переживают вызов), а старт и компиляция платятся однократно.

Прогон по acc_8.3.24 и erp_8.3.24 (33 895 объектов): 274 c против ~2900 c, вывод побайтово тот же
и совпадает с PS-портом строка в строку. Батч с битым объектом в середине по-прежнему доходит до
конца и возвращает ненулевой код, -OutFile пишет те же файлы на объект.

Заодно уточнён комментарий про кэш состава конфигурации: «порождает процесс на объект» было верно
только для py, тогда как в PS кэш не переживает объект из-за новой области видимости.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 15:59:57 +03:00
Nick Shirokov 18510d65d5 merge: meta-validate — устранение ложных срабатываний на типовых конфигурациях (246 ERROR и 1662 WARN → 0 и 1) 2026-08-22 14:14:11 +03:00
Nick ShirokovandClaude Opus 5 d6f3b69810 fix(tests): причины исключений в реестре check-type-maps
Оба исключения по IntegrationService стояли с пустой причиной, а такое гард лишь помечает как долг
и продолжает. Именно так вид метаданных и выпал незамеченным: meta-validate не знал его вовсе и
отвергал штатный объект типовой конфигурации.

Причины проставлены по смыслу своих записей: meta-compile такие объекты не создаёт, meta-validate
проверяет их структурно. Долг исключений без причины закрыт.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 14:00:37 +03:00
Nick ShirokovandClaude Opus 5 f694c48835 fix(meta-validate): ложные срабатывания на типовых конфигурациях
Прогон по acc_8.3.24 и erp_8.3.24 (33 895 объектов) давал 246 ERROR и 1662 WARN на выгрузках,
которые платформа принимает без единой ошибки, — то есть ложным был весь вывод валидатора.
Две группы при этом роняли exit=1 на типовой конфигурации: то же, из-за чего заводились #79 и #84,
только зеркально.

Разобрано платформой на стенде 8.3.24.1691, каждый случай — копия эталона с одной мутацией:

- пустой <Type/> у реквизита — это тип «Произвольный»; загрузка проходит, round-trip сохраняет
  конструкцию без изменений. Было ERROR (245 раз), стало молчание;
- блока Type нет вовсе — загрузка тоже проходит, но платформа молча подставляет тип нового
  реквизита, Строка(10). Замысел теряется, загрузка нет: ERROR заменён на WARN;
- IntegrationService и Bot отсутствовали в списках видов метаданных (в спеке 45 типов, навык знал
  43) — штатный объект типовой давал «Unrecognized metadata type» и exit=1;
- HierarchyType платформа пишет всегда и при выключенной иерархии игнорирует: из 1254 срабатываний
  1253 приходились на её собственный дефолт. Предупреждаем только о явно заданном другом типе;
- источник подписки задают и наборами типов (<v8:TypeSet>), а состав журнала — элементами
  <xr:Item>; проверки искали только <v8:Type> и потому не видели ни одной такой подписки и ни
  одного журнала. Зато реально пустой источник и пустой состав платформа отвергает
  («Источник событий должен быть задан», «Для журнала не заданы регистрируемые документы») —
  там уровень поднят с WARN до ERROR;
- сигнатура процедуры переносится на несколько строк, и Экспорт оказывается не на строке с именем;
  регулярка требовала одну строку. Модуль поставщика в двоичном виде (Module.bin) больше не даёт
  предупреждения «файл не найден» — текста там нет by design;
- ExchangeDate — легаси-реквизит планов обмена, поддержан в meta-compile и meta-decompile:
  добавлен как допустимый, но не обязательный.

После правки корпус даёт 0 ERROR и 1 WARN — тот самый единственный недефолтный тип иерархии.
Объём проверки не потерян: объектов провалидировано 33 895 против 33 894.

Восемь новых кейсов держат обе стороны каждой правки: валидная конструкция молчит, настоящая
ошибка по-прежнему ловится.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 14:00:22 +03:00
Nick Shirokov 12aecbf50b merge: meta-validate #84 — состав конфигурации решает уровень ссылки; form-add — нотация и приём обиходных написаний 2026-08-22 13:03:59 +03:00
Nick ShirokovandClaude Opus 5 aafc680ad5 fix(form-add): нотация параметров в документации и приём обиходных написаний
Таблица параметров и примеры в SKILL.md описывали слэш-нотацию (--set-default, --purpose),
а скрипт принимает -SetDefault и -Purpose. Модель, читающая таблицу, получала отказ биндинга
на первом же вызове. Документация приведена к исполняемой форме — она теперь одна.

Заодно навык принимает обиходные написания молча: назначение формы русским названием вида
формы или английским с суффиксом Form (регистр, пробелы и разделители не значимы) и флаг
основной формы с дефисом внутри имени. Допустимость назначения для вида объекта по-прежнему
проверяется — неизвестное значение отвергается со списком доступных.

Порты выровнены: PS принимает это через алиас параметра, py — через перечисление опций;
проверено девятью написаниями на обоих, результат совпадает.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 12:52:31 +03:00
Nick ShirokovandClaude Opus 5 9efa336fc1 test(meta-validate): кейсы valid-constant и valid-info-register ссылались на несуществующий справочник
Оба назывались «корректными», но объект создавался со ссылкой на CatalogRef.Валюты, которого
в конфигурации нет — платформа такую выгрузку не принимает. Пока уровень был WARN, кейсы
проходили и закрепляли неверное: «валидный» объект с висячей ссылкой.

Справочник добавлен в preRun, эталоны пересняты и проверены платформой.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 12:34:56 +03:00
Nick ShirokovandClaude Opus 5 7b40eb4c0b fix(meta-validate): состав конфигурации, а не наличие файла, решает уровень ссылки (#84)
Проверки 16 и 20 выбирали уровень по наличию файла, тогда как платформа судит по составу
конфигурации. Из-за этого валидатор ошибался в обе стороны: пропускал ссылку на форму
объекта, которого в конфигурации нет (WARN, exit=0 — платформа такую выгрузку не грузит),
и ложно отказывал на фрагменте частичной выгрузки, где файла формы нет намеренно.

Единое правило: ChildObjects отвечает за существование объекта, наличие файла — только за
полноту выгрузки. Нет в составе -> ERROR, в составе есть, а файла нет -> WARN. Уровень для
фрагмента остаётся мягким сознательно: один и тот же фрагмент грузится или нет в зависимости
от состояния конфигурации БД, которого валидатору не видно.

Матрица снята на платформе 8.3.24.1691 (полная и частичная загрузка, семь ситуаций).
Прогноз про загрузку убран из текста WARN — там он не гарантирован.

Состав читается поиском подстроки в границах секции ChildObjects, без разбора XML:
batch-режим порождает процесс на объект, и полная карта оплачивалась бы каждым объектом.

Корпусный прогон по acc_8.3.24 и erp_8.3.24 (33 895 объектов): расхождений до/после нет.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-22 12:34:45 +03:00
Nick ShirokovandClaude Opus 5 0a0322faab fix(skills): пишущие навыки — запрет позиционного связывания параметров
Значение с пробелом без кавычек разрывалось на токены, и лишние молча растекались по свободным
позиционным параметрам. На документированной форме вызова (powershell -NoProfile -File):

  form-compile.ps1 -JsonPath a.json -OutputPath b.xml -Purpose Форма списка
  → Purpose=[Форма] ObjectPath=[списка], ошибки НЕТ

Навык отрабатывал успешно с усечённым значением; реальный триггер — путь с пробелом в -EmitDsl
или -ObjectPath, результат уезжал не туда молча. У навыков с парой -DefinitionFile/-Operation
осколок уезжал в -DefinitionFile, и навык жаловался на параметр, которого в команде не было.

Расхождение портов: py на том же вызове отвечает «unrecognized arguments: списка» — там все
аргументы объявлены опциями. Правка выравнивает PS по py, python не менялся.

41 навык получает [CmdletBinding(PositionalBinding=$false)] и ни одного позиционного параметра.
Без Position=0 (в отличие от read-only навыков): «главный» параметр механически не выводится —
у meta-edit первым объявлен -DefinitionFile, а путь к объекту вторым, — а ошибка выбора тестами
не ловится, раннер передаёт только именованные флаги. Позиционной формы вызова нет ни в одной из
140 строк SKILL.md, внутренние вызовы навыков друг из друга тоже именованные.

check-positional-binding.mjs расширен на пишущие навыки статической проверкой. Поведенческая
(канареечный файл) остаётся только у read-only: у web-stop и db-run все параметры
Mandatory=$false, связывание прошло бы и тело выполнилось — гард остановил бы Apache и запустил 1С.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 18:24:58 +03:00
Nick ShirokovandClaude Opus 5 77ee8f1047 fix(skills): «файл не найден» на входном JSON — одинаково во всех навыках
Часть навыков проверяла путь ко входному JSON сама и отвечала внятной строкой, часть — нет и
роняла дамп: FileNotFoundError с traceback в py, MethodInvocationException с CategoryInfo в PS1.
Один и тот же промах давал разный ответ в зависимости от навыка, а у form-edit — ещё и от порта.

Проверка перенесена в тело семьи read_json_file / Read-JsonInputFile: пол одинаков везде по
построению, и новые навыки получают его вместе с функцией. Навыки со своей проверкой срабатывают
раньше и сохраняют прежний текст, поэтому существующие кейсы не двигаются.

Заодно каталог вместо файла: раньше IsADirectoryError / отказ доступа, теперь
«Expected a JSON file, got a directory».

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 17:46:20 +03:00
Nick ShirokovandClaude Opus 5 229e66b907 fix(skills): чтение входного JSON — кодировка из BOM, эхо только для inline-значения (#80)
Этажом ниже разбора, на чтении файла, жил тот же класс дефектов в худшей форме. Файл в cp1251
с кириллицей: PS1 `Get-Content -Encoding UTF8` менял имя на 12 символов U+FFFD, JSON после этого
разбирался УСПЕШНО, и навык создавал объект с именем из «замен» — молча. Py-порт на том же файле
падал traceback-ом. Файл в UTF-16 давал ту же пару: traceback против ложного «JSON must have
'type' field».

Новая семья read_json_file / Read-JsonInputFile: кодировка берётся из BOM (UTF-8, UTF-16 LE/BE),
без BOM — строгий UTF-8, при провале сообщение называет файл и байт. Кодовую страницу не
подбираем: угаданное имя уехало бы в метаданные так же молча.

Эхо полученного значения печатается теперь только для inline-входа. Для файла оно показывало
первые 60 символов первой строки независимо от того, что ошибка на 120-й, и спорило с позицией
от парсера.

Заодно уравнен пустой вход: PS 5.1 на пустой строке отдаёт $null, а не ошибку, и навык уходил
дальше с $null, тогда как py-порт падал.

Раннеру добавлен ключ inputEncoding (utf-16le / utf-16be / cp1251) — иначе кейс про кодировку
не выразить, writeFileSync пишет только UTF-8.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 16:41:54 +03:00
Nick ShirokovandClaude Opus 5 0770889e47 fix(skills): усечение эха в сообщении о разборе JSON не маскируется под данные
Эхо полученного значения обрезалось многоточием, и обрыв читался как обрыв самих данных:
агент шёл дописывать «неполный» файл, хотя ошибка была на 57-й строке. Плюс усечение спорило
с позицией от парсера — два сигнала об одном месте, которые не сходятся.

Теперь усечение называет себя: got (first 60 chars, whitespace collapsed). Число символов
не печатаем — после схлопывания пробелов оно не сходилось со смещением из сообщения парсера.
На коротком входе, ради которого эхо и заводилось (съеденные оболочкой кавычки дают
{group:X,commands:[Y]} — 22 символа), усечения нет вовсе.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 15:40:40 +03:00
Nick ShirokovandClaude Opus 5 a44a29a7d8 fix(skills): внятная диагностика разбора JSON вместо стектрейса (#80)
Неверный входной JSON ронял скрипты необработанным исключением: PS1 отдавал дамп
ConvertFrom-Json с CategoryInfo, py-порт — traceback с внутренностями json/decoder.py.
Имя файла в сообщении не фигурировало, а для полиморфного -Value не было видно, какую
форму ждёт операция.

Общий хелпер ConvertFrom-JsonInput / parse_json_input в 12 навыках × 2 порта (24 места),
зарегистрирован семьёй в check-inline-drift.mjs — копии держит гард. Сообщение в одну
строку: ожидаемая форма, полученное значение, текст парсера в скобках.

Эхо полученного значения нужно потому, что съеденные оболочкой кавычки дают почти тот же
JSON ({group:X} вместо {"group":"X"}), и без него агент считает свой вызов верным. PS 5.1
печатает лишь огрызок и локализованно, Python — только номер колонки.

Возврат в PS1 через Write-Output -NoEnumerate: вынос разбора в функцию добавляет второй
анруллинг, и одноэлементный JSON-массив стал бы скаляром. Импорты внутри тела py-хелпера —
skd-decompile импортирует json локально как _json, а тело семьи обязано быть одинаковым.

В раннер добавлен inputRaw (запись входного файла дословно): через case.input битый JSON
невыразим, JSON.stringify всегда даёт валидный документ.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 15:25:54 +03:00
Nick ShirokovandClaude Opus 5 53fc16d2f1 fix(form-add): вид объекта берём из структуры документа, а не поиском по имени
Вид искался как первое совпадение //md:<Вид> по всему файлу и потому зависел
от порядка перебора видов. У бизнес-процесса есть свойство <Task>, и объект
определялся как задача, после чего имя не находилось вовсе: «Не удалось
определить имя объекта из Properties/Name». Прежний упорядоченный список видов
это маскировал — в нём BusinessProcess стоял раньше Task.

Теперь вид — первый элемент-потомок MetaDataObject, и неподдерживаемый вид
отвергается по имени, а не проваливается в поиск имени объекта.

Нашлось платформенной проверкой всех видов разом: собрана конфигурация с
формой каждого поддержанного вида (15 видов, 43 формы) и загружена в 1С.

Заодно покрытие назначений доведено до полного (добавились FolderChoice, Load,
Record), а мелкие кейсы на один объект объединены: несколько назначений в одном
кейсе, эталон показывает все слоты сразу.

Гард дополнен инвариантом «ровно одна основная форма на вид» и его паритетом
между портами — именно им пользуется умолчание -Purpose.

Инструкция: в таблице параметров умолчание Purpose исправлено с Object на
основную форму вида.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 12:55:30 +03:00
Nick ShirokovandClaude Opus 5 0f7745af4b feat(tests): verify-snapshots --strict — пропуск считается падением
Пропуск по версии платформы гасит проверку: кривую фикстуру он тоже спрячет,
если её формат объявлен выше платформы стенда. Обычный прогон теперь прямо
говорит, что пропуски не проверены, а на стенде с нужными платформами
--strict не оставляет непроверенных кейсов.

Опечатка в версии формата при этом не маскируется в любом режиме: версии вне
лестницы в таблице соответствия нет, пропуск не срабатывает, и кейс доезжает
до платформы.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 12:15:55 +03:00
Nick ShirokovandClaude Opus 5 64dd135882 fix(tests): verify-snapshots — гейт по версии формата, а не только по совместимости
Пропуск кейса решался только режимом совместимости. Кейс на формате 2.21 с
совместимостью 8.3.27 на платформе 8.3.24 пропускался, а на 8.3.27 доходил до
загрузки и падал: формат 2.21 требует 8.5. На стендах с промежуточной
платформой (в том числе macOS с 8.3.27) это давало постоянный ложный красный.

Эталон соответствия «формат → платформа» — таблица «Лестница версий» из
docs/1c-configuration-spec.md, как и в check-format-versions.mjs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 12:09:52 +03:00
Nick ShirokovandClaude Opus 5 179f5334d9 test(skills): кейсы form-add по видам и назначениям форм
Десять кейсов на то, что раньше не проверялось вовсе: журнал документов
(список и отказ для формы объекта), перечисление, план видов расчёта, наборы
записей регистров, форма группы справочника, произвольная форма, формы
хранилища настроек и отказ для константы. Дыра в покрытии и позволила дефекту
дожить: журналов и регистров среди кейсов не было.

Пять чужих кейсов (form-compile-from-object, cfe-validate) звали form-add без
-Purpose для регистров и полагались на умолчание Object — назначение им
проставлено явно. Их эталоны пересняты: слот основной формы, который раньше
молча оставался пустым, теперь заполняется. Эталоны проверены загрузкой в 1С.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 10:51:24 +03:00
Nick ShirokovandClaude Opus 5 399e02148d fix(tests): verify-snapshots понимает шаг deletePath
Шаг preRun deletePath поддерживал только runner.mjs. В платформенной проверке
такой шаг проваливался в ветку запуска скрипта и падал на step.script.split,
то есть кейс не проверялся вовсе, а выглядел упавшим инструментом.

Шаг без script/writeFile/deletePath теперь отвергается с внятным текстом, а не
падает на undefined.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 10:51:07 +03:00
Nick ShirokovandClaude Opus 5 aad9956e12 test(skills): гард на таблицу назначений форм
Держит три инварианта form-add: состав видов сходится со спецификацией,
свойство «основная форма» у назначения есть у этого вида по спецификации,
таблицы PS и PY совпадают между собой.

Проверено на регрессии: если вернуть журналу DefaultListForm, гард падает
дважды — по спецификации и по паритету портов.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 10:51:06 +03:00
Nick ShirokovandClaude Opus 5 45353afc51 fix(form-add): таблица видов вместо четырёх разошедшихся списков
Для журнала документов навык писал в форму битый главный реквизит —
cfg:.Журнал с пустым префиксом. Платформа такую выгрузку не принимает
(«Исключение XDTO при чтении файла»), а навык рапортовал успех.

Причина не в забытой строке: знание о видах было размазано по четырём
независимым спискам (поддерживаемые типы, объектные, обработко-подобные,
карта типов реквизита) плюс switch по свойствам DefaultForm. DocumentJournal
попал в поддерживаемые, но не в карту типов — и подстановка $null дала точку
без типа. Теперь одна плоская таблица: строка на (вид, назначение), в ней тип
главного реквизита и свойство «основная форма».

Того же корня и починено заодно:
- Purpose=Object принимался для видов, у которых формы объекта не бывает
  (журнал, перечисление, регистры накопления и бухгалтерии);
- свойство «основная форма» выбиралось без учёта вида: журналу писался
  DefaultListForm, которого у него нет, и слот молча не находился;
- AccumulationRegister описывался как RecordSet, хотя у регистра такого
  главного реквизита не бывает, а слота под него нет вовсе;
- ChartOfCalculationTypes отвергался, хотя формы объекта у него обычные.

Новое: назначения Folder и FolderChoice (формы группы), RecordSet для всех
регистров, Save/Load для хранилища настроек и Custom — произвольная форма без
главного реквизита, самая частая в типовых после объектной. Умолчание Purpose
теперь основная форма вида, а не жёсткий Object: для регистра сведений это
форма записи, для журнала — форма списка.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 10:50:51 +03:00
Nick ShirokovandClaude Opus 5 e987eeece2 docs(form-spec): формы по видам объектов — полные таблицы
Таблица «Свойства DefaultForm по типам объектов» была неполной: в ней не было
журнала документов, перечисления, регистров накопления/бухгалтерии/расчёта,
плана видов расчёта, критерия отбора, хранилища настроек и внешних объектов.
Сверять код было не с чем, и код оказался неполон ровно так же.

Добавлена вторая таблица — главный реквизит формы по назначению. Она разводит
случаи, которые легко перепутать: форма группы это форма ОБЪЕКТА группы, а
форма выбора группы — динамический список; у формы набора записей свойства,
чтобы назначить её основной, нет вовсе; форма без главного реквизита
(«произвольная») — обычное дело, а не полуфабрикат.

Источник — выгрузки типовых: слоты и типы главного реквизита сняты по
конфигурациям acc, erp, ut, unf.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-21 10:49:31 +03:00
Nick ShirokovandClaude Opus 5 bfef1b8a0c test(skills): кейсы на висячие ссылки на форму
form-remove: удаление заблокировано ссылками; -Force чистит слот реквизита,
ChoiceForm внутри формы и элемент начальной страницы; регресс на матч
ссылки — удаление своей формы не трогает ссылку на чужую форму с тем же
именем.

meta-remove: общая форма, на которую смотрит Configuration.xml, — ссылка
попадает в отчёт, -Force обнуляет слот.

meta-validate: незарегистрированная форма и отсутствующий файл формы дают
ошибку, GUID в слоте — нет.

Эталоны проверены платформой: verify-snapshots поднимает базу и грузит
результат.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 20:12:36 +03:00
Nick ShirokovandClaude Opus 5 da166845cb fix(tests): кейсы на fixture-фикстурах попадали мимо платформенной проверки
verify-snapshots отбирал кейсы по правилу «нет input, preRun и params →
read-only, пропускаем». Кейс на `setup: fixture:` под него подходил, хотя
навык правит скопированную фикстуру, — и молча выпадал из проверки, ради
которой инструмент и существует. Отчёт при этом оставался зелёным.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 20:11:18 +03:00
Nick ShirokovandClaude Opus 5 d791a00419 feat(meta-validate): ссылка на несуществующую форму — ошибка
Непустое Default*Form / Auxiliary*Form / ChoiceForm могло указывать на
форму, которой нет, а валидатор отвечал OK. Платформа такую выгрузку не
принимает: «Неизвестный объект метаданных».

Новая проверка разбирает обе формы записи. Для своего объекта проверяется
регистрация формы в ChildObjects — это работает и на одиночном файле, без
конфигурации; для общей формы и чужого объекта (форма журнала документов в
слоте документа) нужна конфигурация, и проверяется наличие файла формы.
Отдельно ловится случай, когда форма зарегистрирована, а файла нет.

Значением слота бывает GUID — такие пропускаются, как и в cf-validate.
Заимствованные объекты расширения пропускаются целиком: их формы живут в
основной конфигурации.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 20:11:18 +03:00
Nick ShirokovandClaude Opus 5 cb46348c8b fix(meta-remove): Configuration.xml перестал быть слепой зоной скана
Файл целиком исключался из проверки ссылок как «чистится автоматически»,
хотя автоматически в нём чистится только ChildObjects. Ссылки уровня
конфигурации (DefaultReportForm и соседи) после удаления общей формы
оставались висеть и в отчёте не упоминались.

Теперь такие слоты видны в списке ссылок, а с -Force очищаются — наравне со
слотами других объектов и элементами начальной страницы. Ссылки на типы и
вызовы в .bsl по-прежнему не трогаем: чем их заменить, неизвестно.

Два полных обхода конфигурации свёрнуты в один, Get-ChildItem -Recurse
заменён на Directory.EnumerateFiles: на большой конфигурации проход занимал
180 с против 47 с, а проходов было два.

Общие паттерны ищутся в тексте без form-слотов — иначе файл со слотом
попадал в список дважды, а пропуск файла целиком спрятал бы настоящую
ссылку рядом со слотом.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 20:11:02 +03:00
Nick ShirokovandClaude Opus 5 93d9511a46 fix(form-remove): проверка ссылок перед удалением формы
Форма могла удаляться, оставляя ссылки на себя в других объектах: платформа
затем отвергала загрузку с «Неизвестный объект метаданных». Навык правил
только файл своего объекта и про остальную конфигурацию не знал.

Теперь перед удалением конфигурация сканируется на ссылки на эту форму
(слоты Default*/Auxiliary*Form и ChoiceForm, ChoiceForm и SettingsStorage
внутри форм, элементы начальной страницы). Есть ссылки — удаление
останавливается со списком мест; с -Force форма удаляется, а ссылки
очищаются: слот пустеет, элемент начальной страницы вырезается.

Заодно починен матч ссылки: сравнение шло по хвосту «Form.<Имя>» без вида и
объекта, поэтому при удалении своей ФормаСписка обнулялась и ссылка на
DocumentJournal.Ж.Form.ФормаСписка в том же файле.

Сравнение путей переведено на длинную форму: Resolve-Path отдаёт короткое имя
8.3, перечисление файлов — длинное, из-за чего собственный файл объекта не
исключался из скана.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 20:10:50 +03:00
Nick ShirokovandClaude Opus 5 97f1a0d4e1 fix(switch): навыки без привязки к Claude — порт на другие платформы не уносит хвосты
Три файла из 77 навыков нарушали соглашение о переносимости, и сборка
Codex-порта уносила их as is (#75).

Функциональный из трёх один: json-dsl.md писал путь к скрипту литералом
.claude/skills/meta-edit/..., то есть на любой платформе кроме claude-code
команда указывала в несуществующий каталог и молча не находила скрипт.
Заменено на ${CLAUDE_SKILL_DIR}/ — дальше путь разворачивает switch.py.

Ещё два — тексты: проза с названием агента в mxl-compile (заодно избыточная:
у соседних *-compile это одна строка «принимает X → генерирует Y») и
.claude-путь в комментарии state.mjs.

От регресса — check-agent-portability.mjs: вырезает разрешённый
${CLAUDE_SKILL_DIR}/ и падает на любом оставшемся claude. Ловит и плейсхолдер
без завершающего слеша: switch.py разворачивает только форму со слешем,
остальные проехали бы насквозь. Сторонний node_modules исключён — playwright
содержит ClaudeGenerator и .claude/agents, переписывать его нельзя.

switch.py не менялся: ни одного плейсхолдера и ни одного вызова скрипта вне
*.md верхнего уровня навыка нет, рекурсия не нужна.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 16:35:07 +03:00
Nick ShirokovandClaude Opus 5 9475aa6171 fix(skd-edit): не срезать значимое локальное объявление xmlns при пересборке поля
При пересборке поля навык сохранял <valueType> через OuterXml/tostring и срезал
из фрагмента ВСЕ объявления namespace. Для префиксов, объявленных в корне схемы,
это верно — DOM переобъявляет их на фрагменте избыточно. Но объявление, которого
в корне нет, значимо: xmlns:d5p1 живёт только локально на <v8:Type> и связывает
префикс, которым квалифицировано значение узла (d5p1:CatalogRef.X). Срез оставлял
висячий префикс — XML остаётся well-formed (префикс в тексте парсер не проверяет),
а 1С отвергает тип. Радиус — modify-field.

Слепой срез заменён на Strip-InheritedXmlns / strip_inherited_xmlns: объявление
выбрасывается, только если корень объявляет тот же префикс с тем же URI. Карта
корня снимается с уже имевшегося RawRootOpening; разделитель \s+, поскольку
корневой тег бывает разложен по строкам. Префикс и URI сравниваются ординально —
в PS -eq и @{} регистронезависимы. Подставлено во все пять точек среза каждого
порта; для корневых префиксов поведение не меняется.

Кейс modify-field-ref-type: modify-field по ref-полю не был покрыт. Проверено,
что кейс краснеет до правки — и по снэпшоту, и по skd-validate.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 14:27:03 +03:00
Nick ShirokovandClaude Opus 5 b6857c9406 docs(tests): check-positional-binding в таблице гардов
Гард добавлен в check-all.mjs прошлой волной, а в таблицу README не попал.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 11:52:40 +03:00
Nick ShirokovandClaude Opus 5 1c63d9ddcc refactor(skills): регистрация в ChildObjects — именованная функция под анти-дрейфом
Три навыка делали одну работу тремя инлайновыми копиями в main(), и одна копия
молча разъехалась: role-compile.py рапортовал об успехе, не тронув файл. Это
ровно тот класс, от которого заведён check-inline-drift.mjs, но в реестр
регистрацию было не записать — он работает по именованным функциям.

Регистрация вынесена в Register-InChildObjects / register_in_childobjects
с контрактом (родительский XML, тег родителя, тег потомка, имя) и исходом
added | already | no-childobj | no-config. Печать сообщений осталась на
вызывающей стороне: тексты у навыков разные, и сведение их меняло бы вывод.

Эталон — meta-compile, форма функции задана на .ps1. role-compile берёт его
тело копией, и гард это подтверждает. subsystem-compile идёт отдельным
вариантом с обоснованием: родителем бывает вложенный Subsystem.xml
произвольной глубины, поэтому отступ он берёт из документа, а запись
дописывает в конец блока — фиксированные три табуляции там неверны,
и группировать по типу нечего.

Вынос поведенчески нейтрален: 837 кейсов зелёные на обоих рантаймах,
снэпшоты не дрейфовали, матрица «навык × порт × изломанная форма
Configuration.xml» даёт те же исходы, а порты — байт-в-байт одинаковый файл.
Попутно из subsystem-compile.py убран мёртвый ET.SubElement перед pass:
дерево мутировалось и выбрасывалось, правка идёт по сырому тексту.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 11:52:40 +03:00
2b0ac3ea75 fix(role-compile): py-порт рапортовал об успешной регистрации, не тронув Configuration.xml
Configuration.xml без <ChildObjects> и с самозакрытым <ChildObjects/> — в обеих
формах py печатал «<Role>X</Role> added to ChildObjects», а файл оставался
байт-в-байт прежним. Модель считала роль включённой в состав конфигурации,
хотя её там не было. PS-порт обе формы обрабатывал верно: самозакрытый тег
раскрывал, при отсутствующем честно предупреждал.

Ветвление перенесено из meta-compile.py, где та же задача давно решена: есть
блок ChildObjects — вставляем внутрь; есть самозакрытый тег — раскрываем первой
записью; нет ни того ни другого — исход no-childobj и файл не трогаем. Ветка
вывода no-childobj в py уже была и до сих пор оставалась недостижимой.

Порядок вставки и способ правки файла не менялись: проверка на боевых выгрузках
(acc/erp, CRLF и LF) показала, что действующая реализация даёт дельту ровно
в одну вставленную строку, а платформа порядок ChildObjects не нормализует.

Кейсы на обе формы. На неисправленном коде оба падают на python и проходят
на powershell. Проверка «успех не объявлен» опирается на stdoutNotContains
и снапшот: Write-Warning в PS 5.1 идёт в stdout с локализованным префиксом,
а py пишет в stderr, поэтому портируемого stderrContains тут быть не может.

Co-Authored-By: Sergei Pleshanov <2357qwr@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 11:32:46 +03:00
Nick ShirokovandClaude Opus 5 1cfc76da2e fix(tests): гард позиционного связывания не зеленеет на незапустившемся интерпретаторе
Поведенческая секция считала успехом любой ненулевой код возврата, а не
запустившийся интерпретатор даёт status: null — проверка проходила вакуумно,
ничего не проверив. Теперь spawn-ошибка сама является нарушением.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 15:13:24 +03:00
Nick ShirokovandClaude Opus 5 9e883d7f4f fix(skills): в read-only навыках позиционным остаётся только путь ко входу
Тот же класс дефекта, что закрыт в role-validate, был возможен во всей семье
*-info / *-validate / cfe-diff: лишний позиционный аргумент связывается со
следующим параметром по порядку объявления. Остальные скрипты спасала
случайность типов — на втором слоте оказался [int]MaxErrors/Limit или строка
с [ValidateSet], которые падают на конвертации. Любая перестановка параметров
в param() открывала дыру заново, а снапшот-тесты сверяют вывод, а не связывание.

Во всех 21 скрипте объявлен [CmdletBinding(PositionalBinding=$false)], входной
путь помечен Position=0. Инвариант: анализирующий навык не пишет в файл, который
ему не назвали по имени. Документированные вызовы в репозитории все именованные,
поведение навыков не меняется — 835 кейсов зелёные на обоих рантаймах без дрейфа
снэпшотов.

Гард check-positional-binding.mjs держит инвариант статически (объявление и
единственный Position=0; в py-порте все add_argument именованные) и поведенчески
(лишний позиционный аргумент роняет вызов, канареечный файл остаётся цел).
Семья определяется по имени навыка, поэтому новый *-info/*-validate попадает под
гард сам. На состоянии до правки гард падает на role-validate обеими проверками.

Версии обоих портов подняты синхронно; из шапок убраны чужие хвосты.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 14:59:52 +03:00
83d63fac47 fix(role-validate): лишний позиционный аргумент затирал файл валидационным отчётом
Вызов role-validate.ps1 с двумя позиционными аргументами — естественная ошибка
по документации role-compile/SKILL.md, где значилось
`/role-validate <RightsPath> [MetadataPath]`, хотя параметра MetadataPath у
навыка нет. Второй аргумент связывался с -OutFile, и скрипт перезаписывал
указанный файл (например, Roles/Имя.xml) текстом отчёта — исходник роли терялся.

PositionalBinding=$false оставляет позиционным только путь ко входу; -OutFile
передаётся строго по имени. Справка приведена к факту. Python-порт уже иммунен:
argparse принимает только именованные параметры.

Co-Authored-By: Sergei Pleshanov <2357qwr@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 14:59:37 +03:00
Nick ShirokovandClaude Opus 5 77cef101f7 test(cfe-borrow): причина пропуска на 8.3.27.1688 — с механизмом, а не с наблюдением
Разбор довели до механизма. Сборка не разрешает поля динамического списка заимствованной
формы в авто-режиме (ManualQuery=false + MainTable). Проверено и отброшено: порт скрипта,
ОС, имена латиницей, заимствованность таблицы, порядок загрузки, BaseForm/UseAlways/Settings,
полный эталонный вид с ListSettings и Список.Ref. Проходит только ручной запрос, что меняет
семантику списка.

Решающий факт: ту же базу, где расширение валидно загружено платформой 8.3.24, сборка 1688
выгружает с битыми путями (~Список.Склад) — то есть не разрешает их даже из собственного
хранилища. Наш эмит при этом совпадает с эталоном Конфигуратора (upload/cfe/ut/ФормаСписка2).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-18 11:22:07 +03:00
Nick ShirokovandClaude Opus 5 9dcaa9b6b1 fix(tests): пропуск платформенной верификации, привязанный к сборке платформы
Кейсы form-main-attr-dynamic-list и form-main-attr-list-query падали на маке. Диагностика:
дело не в порте (Windows+python проходит) и не в ОС (мак на 8.3.24.1691 проходит) — отвергает
конкретная сборка 8.3.27.1688. Решающий опыт: расширение, загруженное на 8.3.24 и выгруженное
самой платформой, сборка 1688 тоже отвергает с теми же «Неверный путь к данным» — то есть она
не принимает собственный артефакт Конфигуратора, а не наш эмит.

Глухой skipPlatformVerify снял бы проверку и на стендах, где она работает, поэтому он теперь
принимает объект {reason, platforms:[…]} — пропуск только на перечисленных сборках.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 23:13:44 +03:00
Nick ShirokovandClaude Opus 5 e9a40a1499 fix(tests): verify-snapshots помечает пропуски в REPORT.md, а не считает их падениями
Консольная сводка пропуски различала, а файл отчёта нет: статус считался как
`passed ? OK : FAIL`, поэтому пропущенный кейс печатался как FAIL, попадал в счётчик
падений и в раздел Findings. Отчёт читают глазами и по нему решают, есть ли проблема, —
расхождение с консолью здесь дороже всего.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 22:55:17 +03:00
Nick ShirokovandClaude Opus 5 eda24ae1e7 fix(tests): verify-snapshots читает оба потока при падении навыка
ps1 печатает строку ошибки в stdout, py — в stderr, а лог платформы оба кладут в stdout.
Верификатор брал `stderr || stdout`, поэтому на python-порте лог терялся целиком: падение
выглядело как «Error loading configuration (code: 1)» без причины, и распознать по нему
неподдерживаемую версию формата было нечем — кейсы format-221 на маке падали вместо пропуска.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 22:35:17 +03:00
Nick ShirokovandClaude Opus 5 54ee6efe93 fix(tests): каталог расширения в кейсах cfe-* — cfe вместо ext (closes #74)
Кейсы клали расширение в `ext`, а cf-init создаёт в корне конфигурации платформенный `Ext/`.
На регистронезависимой ФС (Windows, APFS) это один каталог: эталоны годами фиксировали
слипшееся дерево — в snapshots/<case>/ext/ лежали вперемешку ClientApplicationInterface.xml
конфигурации и Configuration.xml расширения, а имя каталога в коммите зависело от того, кто
создал его первым (ext у одних кейсов, Ext у других при одинаковом входе). На Linux те же
кейсы дали бы два каталога и другой снапшот.

Переименование механическое: 64 кейса пяти навыков, 349 файлов эталонов переехали без
единой правки содержимого. Заодно переписан регистр 21 пути в индексе — git с core.ignorecase
не показывал, что файл конфигурации числится под ext/, а лежит в Ext/.

Гард в runner.mjs: имя каталога расширения из params, совпавшее без учёта регистра с тем, что
уже есть в фикстуре, валит кейс с объяснением. Проверено, что старое имя теперь не проходит.

verify-snapshots.mjs чинится тем же заходом — там нашлись две дыры:
- каталог расширения был захардкожен как 'ext', поэтому кейсы с другим именем МОЛЧА теряли
  вторую половину проверки: блок загрузки расширения обходился по existsSync и в отчёте это
  выглядело успехом;
- setup брался только из _skill.json, и кейсы с гейтом по версии формата (218/220/221)
  проверялись на конфигурации 2.17 — то есть платформа не видела ровно того поведения, ради
  которого кейс написан.
Плюс два honest-skip вместо ложных падений: режим совместимости выше платформы и формат
выгрузки новее платформы (второе читаем из ответа платформы, а не дублируем лестницу версий).

Проверено: регресс 827/0/8 (ps1) и 824/0/11 (py), гарды 5/5. Платформенная верификация:
cfe-borrow 26/28 на 8.3.27, cfe-patch-method 22/23, cfe-init 6/7, все 14 кейсов format-221
на 8.5 — остальное осознанные пропуски.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 22:20:28 +03:00
Nick ShirokovandClaude Opus 5 113dc9e928 fix(cfe-validate): проверка модулей затирала имя расширения в итоговой строке
Новая проверка (сверка «файл модуля ↔ пометка») переиспользовала $objName — ту же
переменную, из которой собирается финальная строка отчёта. В расширении с заимствованным
объектом ps1 печатал «Validation OK: Extension.Цены» — имя последнего проверенного объекта
вместо имени расширения. py-порт был прав, то есть порты разошлись молча.

Расхождение видно только на непустом расширении с чистым результатом, поэтому ни один
существующий кейс его не ловил: тесты сверяют снапшот, а итоговую строку — нет. Добавлены
утверждения на неё в кейсы valid, with-borrowed-object и module-state-file-without-flag;
проверено, что на старой версии скрипта они падают.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 21:20:26 +03:00
Nick ShirokovandClaude Opus 5 8fe727e32f feat(cfe-borrow,cfe-patch-method,cfe-validate): модули заимствованных объектов и пометка расширенного свойства
Объект заимствуют, чтобы дописать в него модуль, — теперь пустой файл модуля создаётся
навыком, а не руками. Тип с единственным модулем (CommonModule, HTTPService, WebService)
получает его без указаний, остальные — по -Module; -Module None отменяет. Существующий
файл не перезаписывается никогда.

Вместе с файлом проставляется <xr:PropertyState> со State=Extended. Замер по лестнице
платформ 8.3.20…8.5.1: элемент появился в формате 2.19 (8.3.26), ниже платформа молча
выбрасывает его при загрузке — отсюда гейт по версии формата. Имя свойства равно базовому
имени файла модуля, у заимствованной формы — Form. Правило владения одно: пометку ставит
тот, кто создал файл, поэтому её ставит и cfe-patch-method.

Заодно закрыта перезапись при повторном заимствовании: прямая ветка писала XML уже
заимствованного объекта начисто, унося собственные реквизиты расширения и регистрацию
формы, — молча, с успешным отчётом. Повторный вызов теперь безопасен и служит способом
дозаимствовать модуль.

cfe-validate сверяет «файл модуля ↔ пометка» в обе стороны (предупреждение: перекос
платформа принимает, но выгрузка Конфигуратора так не выглядит).

Проверено: полный регресс 827/0/8 (ps1) и 824/0/11 (py), гарды 5/5, сквозной раундтрип
на 8.3.26 и 8.3.27 — InternalInfo совпадает с выгрузкой платформы.

Закрывает #70. Разбор и эталоны выгрузки — Romandredan.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 20:59:39 +03:00
Nick ShirokovandClaude Opus 5 89f65ca6a6 fix(tests): чинит фикстуру role-info/multiple-types и иконку скипа в verify
Кейс собирал через meta-compile регистр сведений без измерений и ресурсов —
платформа такую конфигурацию не принимает («Регистр без измерений, ресурсов и
реквизитов»), и verify-snapshots падал на нём на обеих ОС. Регистру добавлены
измерение и ресурс, эталон переснят: дельта только в XML регистра, права роли
не изменились.

Заодно построчный вывод verify рисовал скипы как ✗ (иконка выбиралась по
result.passed, который скип не выставляет): десяток «✗» под «0 failed» читался
как сломанный набор.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 18:44:14 +03:00
Nick ShirokovandClaude Opus 5 da51d8ab7d fix(tests): verify-snapshots скипает недоступную внешнюю выгрузку, а не падает
runner.mjs при отсутствии external-пути возвращает SKIP, verify-snapshots писал
ошибку. Путь к дампу ERP/БП машинозависим: на Mac mini корпуса нет, и два кейса
role-validate краснели при полностью исправном навыке. README предупреждает ровно
об этом: раннеры читают один DSL каждый своей реализацией.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 17:15:26 +03:00
Nick ShirokovandClaude Opus 5 261ea8c96d test: раскрытие сервисов в role-compile, права на корень и заимствованные объекты
Кейсы: раскрытие HTTP- и веб-сервиса, раскрытие сервиса интеграции, раскрытие
заимствованного сервиса, дедупликация раскрытого листа с явным, отказ без
метаданных, отказ основной роли расширения, право на корень сервиса в
role-validate и в cfe-validate.

Фикстуры HTTP- и веб-сервиса строит preRun через meta-compile — рукописный XML
платформа отвергала (у Operation обязателен замыкающий ChildObjects, у сервиса
нужен Ext/Module.bsl). Оба кейса проходят verify-snapshots: 1С принимает
конфигурацию с раскрытыми правами при полной загрузке.

Вручную написаны только фикстуры, которые навыками не собрать, — сервис
интеграции (meta-compile его не поддерживает) и расширение с заимствованным
сервисом (выгрузка Конфигуратора); оба с skipPlatformVerify и причиной.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 16:54:08 +03:00
Nick ShirokovandClaude Opus 5 9702a4c485 fix(cfe-validate): основные роли не дают прав на заимствованные объекты
Платформа: «Назначение прав доступа на заимствованные объекты основными ролями
в расширениях недопустимо». Роль вне DefaultRoles так делать вправе, поэтому
проверяются только основные. Ловится статически, а по симптому — отказ загрузки
расширения — причина не читается.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 16:19:55 +03:00
Nick ShirokovandClaude Opus 5 4be10e3d7f fix(role-validate): право на сервис целиком — ошибка
Роль с правом на `HTTPService.Имя` формально валидна и молча не работает:
платформа проверяет вложенный объект (метод шаблона URL, операцию, канал),
маршруты отвечают 403, и по симптому причина не читается. В Конфигураторе
галки на корне сервиса нет вовсе — это недостижимое состояние, а не стилистика.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 16:19:55 +03:00
Nick ShirokovandClaude Opus 5 756bd857dd fix(role-compile): права на сервис — на вложенных объектах, а не на корне
Короткая запись `HTTPService.X: Use` писала узел, которого платформа не видит:
роль проходила role-validate и cfe-validate, а маршруты отвечали 403. В корпусе
(acc/erp/ut/unf, 907 записей прав на сервисы) корневого узла нет ни разу — право
живёт на методе шаблона URL, операции, канале; в Конфигураторе галки на корне нет.

Короткая запись теперь раскрывается во все вложенные объекты сервиса по его
метаданным в OutputDir. Заимствованный сервис несёт заимствованные шаблоны и
методы, поэтому раскрытие даёт ровно тот набор, на который права дать можно.
Нет метаданных или вложенных объектов — отказ с подсказкой формата.

Плюс отказ, когда основная роль расширения (из DefaultRoles) даёт права на
заимствованный сервис: платформа такое расширение не принимает.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 16:19:43 +03:00
Nick ShirokovandClaude Opus 5 4967b752d7 docs(spec): тело XDTO-пакета — Ext/Package.bin, а не Package.xdto
Выгрузка конфигуратора кладёт модель пакета в Ext/Package.bin
(текстовый XML в UTF-8 с BOM). Файла Package.xdto не существует.

Closes #69

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 15:03:37 +03:00
Nick Shirokov f4768e245a docs(mxl-decompile): свернуть каталог раундтрип-форм до одного абзаца
Таблица из пяти записей читалась как меню возможностей и звала писать
apply руками — ровно та развилка, которой мы избегаем в прощающем вводе.
Осталась одна мысль, ради которой это вообще упомянуто: незнакомый ключ
в разобранном макете нельзя удалять «для чистоты» — он несёт состояние
исходного файла.
2026-08-17 14:26:15 +03:00
Nick Shirokov 2a16395951 docs(mxl-compile,mxl-decompile): закрыть дыры каскада, найденные обходом по задачам
Перечитал каскад как модель, идущая от задачи, и нашёл три места, где задача
упиралась в пустоту:

- ключ pictureParameter стоял в схеме DSL, но не был описан нигде, а задача
  «картинка в ячейке» из индекса вела в drawings.md, где её не было. Добавлен
  раздел: picIndex считается с единицы по порядку объявления в pictures,
  выравнивания и положение текста — ключи стиля, pictureParameter — ключ ячейки;
- объектная форма rowStyle с модификатором apply нигде не описана, хотя её
  пишет декомпилятор: модель, разобравшая чужой макет, встречала непонятный
  ключ. Такие формы собраны в mxl-decompile отдельной таблицей — вместе с
  controlType "none", пустым valueType, пустой привязкой к раскладке и записью
  палитры без картинки;
- область печати и повторение шапки при печати DSL не выражает — теперь это
  сказано прямо в print.md, а не выясняется опытным путём.

Индекс задач дополнен колонкой ключей: модель, увидевшая незнакомый ключ
в схеме, сразу находит нужный файл.
2026-08-17 14:23:53 +03:00
Nick Shirokov e10421d18e docs(mxl-compile): выправить примеры каскада по проверке компилятором
Прогнал каждый json-блок инструкций и спецификации через компилятор
и валидатор, фрагменты достраивая до минимального макета. Нашлось:

- пример колоночных раскладок показывал области с "rows": [] — макет
  с именованными областями поверх нуля строк, валидатор на нём ругается;
- шапка таблицы в примере SKILL.md была записана пятью объектами с col
  там, где хватает позиционного списка строк.

Оба поправлены; раундтрип примера подтверждает, что форма каноническая —
декомпилятор возвращает её же.
2026-08-17 13:01:50 +03:00
Nick Shirokov 13d100dff8 docs(mxl-compile): позиционный список ячеек работает и с rowStyle
Утверждение «короткой формой не задать height и rowStyle» неверно: проверено
компилятором, позиционный список кладётся в cells и уживается со свойствами
строки, включая маркеры > и |. Это форма записи ЯЧЕЕК, а не строки — так
и сформулировано теперь в инструкции и спецификации.

Строка итога в примере переписана короче, заодно показывает эту форму.
2026-08-17 12:56:05 +03:00
Nick Shirokov 93ced4b239 docs(mxl-compile,mxl-decompile): каскад инструкций — файл на задачу
Каскад повторял спецификацию: reference/dsl-spec.md был её копией, а styles
и format-properties — копиями двух других файлов документации. Модель, чтобы
что-то сделать, читала спеку целиком.

Теперь в SKILL.md лежит то, что нужно почти всегда: компактный пример
печатной формы, структура DSL, области, строки, ячейки с короткой формой,
rowStyle и оформление из десяти частых ключей. Остальное — по файлу на
задачу: layout, print, drawings, input-cells, groups, notes,
style-properties, каждый 26-81 строка; читать нужно только свой.

Состав ядра выбран по частоте в обычных макетах (корпус без регламентированной
отчётности, которая перекашивала статистику): шрифт 93%, выравнивание 88/87%,
размещение текста 86%, рамки 57-72%, формат 52%; поля ввода и группы, наоборот,
оказались редкими — 7%.

В mxl-decompile переехало то, что относится к чтению чужого макета, а не
к авторингу: пересборка — полная перегенерация, побайтового совпадения ждать
не всегда стоит, и перечень конструкций, которые цикл не переживают.
2026-08-17 12:51:53 +03:00
Nick Shirokov da76bb6f01 docs(mxl): свести спецификацию DSL в один файл
Оформление и полный перечень свойств стиля жили отдельными файлами, хотя
описывают тот же DSL. Теперь docs/mxl-dsl-spec.md — единственная полная
документация формата, как у остальных семейств навыков: структура,
оформление, колоночные раскладки, перечень свойств.

Ссылки в README и указателе спецификаций сведены к одной строке.
2026-08-17 11:15:43 +03:00
Nick Shirokov e048fcff70 fix(mxl-compile,mxl-decompile): пустой текст ячейки и расхождение портов на нём
Пустой текст платформа хранит только самозакрывающимся тегом: в корпусе ERP
таких 1 224 460, а из 780 934 непустых блоков нет ни одного, где пусты все
языки. Компилятор же на `text: ""` писал блок с пустым элементом языка —
запись, которой в выгрузках не существует. Теперь любая пустая форма
(строка, пустой объект, все языки пустыми) даёт один и тот же тег, а
декомпилятор возвращает её канонической пустой строкой.

Отсюда же росло расхождение портов. Свёртка текста на пустой карте языков
возвращала null, если ДРУГОГО текста в макете нет вовсе: набор языков
документа при этом пуст. Дальше py писал пропуск колонки, а ps1 наступал
на разворачивание одноэлементного массива — список из одного $null
превращался в $null, и строка целиком уходила в пустые.

Проверено на 12 корпусных макетах: 1411 пустых текстов из 1411 вернулись
на место, JSON портов совпадает.
2026-08-16 12:30:19 +03:00
Nick Shirokov fef55043cf feat(mxl-compile,mxl-decompile): параметр картинки у ячейки, прозрачность одним ключом
Ячейка получила третий параметр — pictureParameter, имя параметра, которым
подставляют картинку. Сама картинка задаётся оформлением (picIndex), а этот
тег живёт у ячейки, последним из её параметров, и уживается с текстом.
В корпусе таких ячеек 21 в 9 макетах; теперь все 21 возвращаются обратно.

Прозрачность картинки сведена к одному ключу transparent: false — фона нет,
{ x, y } — прозрачен цвет пикселя с этими координатами. Два способа записи
у платформы исключают друг друга (t принимает только false, включённую
прозрачность выражают tx/ty), так что двум ключам DSL соответствовал один
флажок диалога.

Заодно выровнен порядок ключей в проверке «объект описывает ячейку»: в
py-порте не хватало note.
2026-08-15 21:03:01 +03:00