Commit Graph
145 Commits
Author SHA1 Message Date
Nick ShirokovandClaude Opus 5 4fa57c73b5 docs(skills): Language исключён из сортировки без замера — так и написать
В комментарии семьи is_order_sensitive_type и в гайде причина исключения Language была
подана как установленная: «порядок языков задаёт порядок <v8:item> в мультиязычных
строках по всей выгрузке». Это гипотеза, высказанная при обсуждении, и мы её не
проверяли. Основания у четырёх исключений разные, и теперь это видно: CommonAttribute —
исключение самого стандарта, Subsystem и CommandGroup — замер по корпусу (в ACC 15 живых
групп команд из 39 вне GroupsOrder), Language — осторожность без замера.

Правка задевает тело семьи, поэтому идёт по всем пяти навыкам в обоих портах.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QoAJmoNbgWKobA7JGgN5S3
2026-08-30 14:37:15 +03:00
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 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
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 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 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
Nick ShirokovandClaude Opus 5 8c0fbed7d6 fix(meta-compile): регистронезависимый ввод в py-порте — паритет с PS
PowerShell не различает регистр нигде, куда попадает пользовательский ввод:
свойства объекта из ConvertFrom-Json, ключи Hashtable, -eq/-contains, имена
параметров, ValidateSet. Python различает везде, поэтому один и тот же DSL
давал разный результат на разных портах — и чаще всего молча: "CodeLength"
вместо "codeLength" в py просто не находился, навык печатал [OK], а свойство
в выход не попадало.

Пилот на meta-compile. В py-порт добавлены общие обёртки: CIDict (поиск без
учёта регистра, ключи хранятся как есть — часть из них имена объектов и
попадает в XML), ci_json (рекурсивно на разобранный DSL), ci_parse_args (имена
параметров и значения choices). Ими же обёрнуты словари синонимов видов,
алиасов enum и типов.

Попутно исправлен дефект самого канона: PS принимал "type":"catalog", но
дальше использовал значение как есть — в имени тега и в регистрации в
Configuration.xml, то есть отдавал <catalog>, которую платформа не примет.
Теперь вид приводится к канону списка в обоих портах.

check-inline-drift: extractPy научен доставать class (иначе тело CIDict
невидимо), заведены три семьи с ps1: null — в PS1 этих обёрток быть не должно.

Проверка: кейс lenient-key-case зелёный на обоих портах; полный регресс
673/673 (PS) и 670+3 skipped (PY); гарды 4/4; sweep по 400 справочникам трёх
конфигураций (декомпиляция → компиляция обоими портами) — 0 расхождений.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 19:48:24 +03:00
Nick ShirokovandClaude Opus 5 4622234b52 feat(meta-compile): пары субконто бухрегистра из плана счетов
Компилятор эмитил ExtDimensionN/ExtDimensionTypeN только по ключам DSL,
поэтому регистр, созданный по неполному описанию, не совпадал с тем, что
материализует платформа: пары дописывались лишь при загрузке.

Теперь их число выводится из MaxExtDimensionCount плана счетов, на который
ссылается регистр, — файл читается из выгрузки, как версия формата из
Configuration.xml. ExtDimensionN получает LinkByType на Account с LinkItem
по номеру. Выведенный хвост дополняет DSL, а не заменяет: ключи из описания
остаются, недостающее добавляется.

План счетов не найден в выгрузке (ещё не создан, лежит вне каталога) — пары
не генерируются, в вывод идёт [HINT]: платформа допишет их сама при загрузке.

Проверка: матрица из трёх случаев (план с 3 субконто, план с 0, план
отсутствует) на обоих портах; синтетика загружена в 1С и выгружена обратно —
состав и порядок совпали; корпусный роундтрип 739 регистров без расхождений.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 18:42:00 +03:00
Nick ShirokovandClaude Opus 5 16199deb92 fix(meta-compile,meta-validate): канон стандартных реквизитов регистров
Справочник порядка StandardAttributes был неполон у трёх видов регистров, а
неизвестные имена эмитятся перед фикс-списком — платформа их вкрапляет, и
роундтрип переставал быть побайтовым.

Порядок снят с выгрузки платформы, присутствие условных реквизитов теперь
выводится из свойств объекта (periodAdjustmentLength, correspondence,
registerType), а не из наличия ключа в DSL: иначе регистр, создаваемый с нуля,
терял реквизит, который платформа обязана материализовать. Наличие ключа
осталось дополняющим условием, чтобы роундтрип не зависел от точности вывода.

- регистр расчёта: 11 реквизитов, состав безусловен (проверено матрицей
  actionPeriod × basePeriod × периодичность на 8.3.27)
- регистр бухгалтерии: PeriodAdjustment при длине периода корректировки > 0,
  RecordType при correspondence=false
- регистр накопления: RecordType только у регистра остатков

meta-validate: снято ложное предупреждение об «unexpected» RecordType у
бухрегистра, условные реквизиты исключены из проверки missing.

Проверка: 739 регистров по шести конфигурациям — 0 расхождений; снэпшоты
пересняты и верифицированы загрузкой в 1С.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 18:06:34 +03:00
Nick ShirokovandClaude Opus 5 01749ad6ee refactor(skills): свести write_xml_file и write_utf8_bom к общему эталону
У write_xml_file в docstring прямо написано «Держать копии одинаковыми —
сознательно: разошедшиеся копии сводят на нет весь смысл», и при этом копий было
три варианта. Различие оказалось не в логике нормализации, а в имени
вспомогательной функции записи байт: write_utf8_bom (11 навыков),
write_text_with_bom (form-add, help-add, template-add), save_text_bom
(cfe-borrow) — тела всех трёх побайтово эквивалентны.

Имя сведено к write_utf8_bom, тело помощника — к общему эталону (различались
только имена переменных). PS1-порт Write-XmlFile расхождений не имел.

Обе семьи переведены из храповика в реестр с эталоном: 24 семьи, 309 копий,
разъехавшихся осталось 4.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 12:29:43 +03:00
Nick ShirokovandClaude Opus 5 3c50d3caa0 fix(form,meta,skd): срезать любой сгенерированный префикс dNpM:, не только d5p1
Имя префикса для .../8.1/data/enterprise/current-config платформа выдаёт по
порядку объявления в файле: в корпусе acc_8.3.27 на этом URI встречаются d4p1,
d5p1 и d6p1. Литеральное d5p1: покрывало лишь часть случаев — тип, скопированный
из Predefined.xml, приходит с d4p1: и даёт ту же ошибку «Неизвестное имя типа».

Обобщено до ^d\d+p\d+: — так же, как это уже делает form-info. Условие «только у
ссылочных типов, с точкой» сохранено: все 11 спец-типов чужих пространств имён
(d5p1:Chart, mxl:SpreadsheetDocument, pl:Planner и др.) точки не имеют.

Регресс-кейс расширен на d4p1 и d6p1: все шесть форм ввода дают один
cfg:CatalogRef.Склады.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 20:04:06 +03:00
Nick ShirokovandClaude Opus 5 826ba3de71 fix(form,meta,skd): resolve_type_str — одно тело + срезание префикса типа
Шесть копий resolve_type_str разошлись на 6 вариантов в PY и 4 в PS1, а вместе
с формой разошлось и поведение: префикс ссылочного типа срезал только
form-compile.

Проверка на платформе (стенд 8.3.24.1691, cf-init → meta-compile → db-load-xml)
показала, что это не косметика. Префикс ломает поиск в словаре синонимов, из-за
чего русское имя типа остаётся непереведённым:
  СправочникСсылка.Склады      → cfg:CatalogRef.Склады        (верно)
  cfg:СправочникСсылка.Склады  → cfg:СправочникСсылка.Склады  (неверно)
Платформа отвечает «Неизвестное имя типа - СправочникСсылка.Склады» и
отклоняет загрузку. Именно с префиксом тип и написан в выгрузке, откуда
пользователь его копирует.

cfg: снимается всегда, d5p1: — только у ссылочных типов (с точкой): сам по себе
префикс неоднозначен, в формах d5p1:Chart, d5p1:TextDocument и ещё три типа
адресуют свои пространства имён, и там он часть канонического значения. Первая
версия правки срезала его безусловно и уронила 4 кейса form-compile.

Тело общее для всех шести навыков; словари синонимов остаются локальными, навык
объявляет на свой словарь алиас TYPE_SYNONYMS / $script:typeSynonyms.

Регресс-кейс meta-compile/type-prefix-lenient проверяет все три формы ввода.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 19:44:39 +03:00
Nick ShirokovandClaude Opus 5 d99d63288e refactor(skills): detect_format_version — один вариант на всех потребителей
Семья была разбита на «знает про автономную EPF/ERF» (form-*, mxl-compile,
template-add, help-add) и «не знает» (cfe-borrow, interface-edit, meta-compile,
role-compile, subsystem-compile, xdto-compile), плюс редакторски разошедшаяся
PS1-копия help-add.

Расширенный вариант корректен и для конфигурационных навыков: ветка срабатывает
только если <каталог>.xml — корень ExternalDataProcessor/ExternalReport, чего в
дереве конфигурации не бывает. Переключатель не нужен — раскопирован как есть.

Семья в реестре схлопнута до одного варианта: 11 копий, оба порта.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 18:18:52 +03:00
Nick ShirokovandClaude Opus 5 34b533a730 feat(meta-compile,meta-edit,cf-init,meta-validate): формат выгрузки 2.21 (8.5)
Дельта 2.20 → 2.21 снята синтетическим экспериментом: одна и та же минимальная
конфигурация загружена в две пустые базы, 8.3.27.1859 и 8.5.1.1302, и выгружена
из каждой (стенд debug/fmt221). Всё, что нашлось раундтрипом по УНФ,
воспроизвелось на синтетике — значит это ступень формата, а не особенность
конфигурации.

Версию формата задаёт ПЛАТФОРМА ВЫГРУЗКИ, а не режим совместимости:
CompatibilityMode в обеих выгрузках остался Version8_3_24, но 8.5 всё равно
написала 2.21. Поэтому нигде не появляется привязки к режиму совместимости —
только к версии из Configuration.xml, как и было устроено.

Реализовано:
- xmlns:pal (палитра) в шапках MetaDataObject и Form у meta-compile и cf-init.
  Вставка НА МЕСТО (после lf, перед style) — платформа держит объявления по
  алфавиту. В файлы с корнем extrnprops (Ext/ClientApplicationInterface.xml)
  платформа его не пишет, и мы не пишем.
- meta-compile: <Color>auto</Color> у значения перечисления, <AuxiliaryVariantForm/>
  у отчёта, <UseInInterfaceCompatibilityMode>Any</…> у общей формы. Позиции взяты
  из эталонной выгрузки, а не угаданы.
- meta-edit: <Color> при добавлении значения перечисления — навык сам эмитит
  EnumValue, и без этого добавленное значение отличалось бы от соседних.
- cf-init: 14 новых свойств корня при -FormatVersion 2.21.
- meta-validate: лестница версий (был единственным валидатором без 2.21) и три
  строки в реестре versionedProps — механизм для этого и заводился.

Проверка:
- наш вывод на 2.21 совпал с выгрузкой платформы ПОСТРОЧНО по Enums, Reports и
  CommonForms; порты идентичны modulo UUID;
- на 2.17-2.20 вывод не изменился ни в одном байте;
- юнит-тесты 648/648 ps1, 645/648 py.

Известное отклонение, НЕ относящееся к 2.21: скелет Ext/Form.xml общей формы
отличается от платформенного (dcssch в шапке, форма AutoCommandBar, Attributes) —
то же расхождение есть и на 2.20, разбирать отдельно.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 19:39:31 +03:00
Nick ShirokovandClaude Opus 5 7892c9fbfa fix(meta-compile): позиция условных стандартных реквизитов бухрегистра
У бухрегистра условные стандартные реквизиты стоят по обе стороны фикс-списка:
PeriodAdjustment — перед Account, пары субконто — после Period. Эмиттер же клал
все ключи, которых нет в списке типа, скопом вперёд, поэтому порядок расходился
с выгрузкой.

Разделено на три части: ключи вне списка по умолчанию идут ПЕРЕД ним (легаси
вроде ExchangeDate у части планов обмена), подходящие под хвостовой шаблон типа —
ПОСЛЕ, а именованные условные (PeriodAdjustment) занимают своё место в самом
списке и эмитятся при наличии в DSL.

Пары субконто заданы ШАБЛОНОМ, а не списком имён: их количество определяется
свойством MaxExtDimensionCount плана счетов. В корпусе везде 3, но это
однородность выборки, а не правило, и список из ExtDimension1..3 закрыл бы
только наблюдаемый случай. Хвост сортируется по номеру, внутри номера
ExtDimensionN идёт перед ExtDimensionTypeN.

Проверка:
- синтетика с ПЯТЬЮ парами субконто и перемешанными ключами на входе даёт
  канонический порядок с пропусками номеров; порты байт-в-байт (modulo UUID);
- раундтрип «все виды» (112 объектов, 42 вида): порядок ≠ 0, TOTAL diff 0;
- юнит-тесты 648/648 ps1, 645/648 py; 1С-сертификация accounting-register ✓.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 18:00:31 +03:00
Nick ShirokovandClaude Opus 5 0fc2d738a2 fix(meta-compile): порядок стандартных реквизитов ещё четырёх типов + Use у ПВХ
Раундтрип покрывал пять видов объектов из сорока с лишним, поэтому расхождения
порядка в остальных были не видны. Списки расширены до «по три объекта из
каждого вида» (112 объектов, 42 вида) — содержательных расхождений нет нигде,
но всплыли перестановки.

1. Порядок стандартных реквизитов, снят с корпуса acc+erp:
   - ChartOfCharacteristicTypes: PredefinedDataName, ValueType, Description,
     Code, IsFolder, Parent, Predefined, DeletionMark, Ref;
   - BusinessProcess: Started, HeadTask, Completed, Ref, DeletionMark, Date, Number;
   - Task: Executed, Description, RoutePoint, BusinessProcess, Ref, DeletionMark,
     Date, Number;
   - AccountingRegister: Account, Active, LineNumber, Recorder, Period.
   IsFolder добавлен в фикс-список ПВХ: он есть во всех 47 объектных блоках
   корпуса (5 выгрузок) и НЕ связан с иерархичностью — плоских ПВХ вдвое больше
   иерархических (36 против 15), и у них он тоже есть.

2. Новый контекст реквизита `cct` для ПВХ. Структурно реквизит ПВХ совпадает со
   справочником, но <Use> у него идёт ПОСЛЕ <Indexing>, а у справочника — перед
   (корпус: Catalog `Use,Indexing,FullTextSearch`, ПВХ
   `Indexing,Use,FullTextSearch,DataHistory`). Раньше оба шли одним контекстом.

Проверка:
- ПВХ (24 объекта): порядок ≠ 23 → 0; все виды (112): 7 → 1;
- шесть основных списков (4897 объектов) без регрессии: порядок ≠ 0,
  TOTAL diff lines 0, match не просел;
- юнит-тесты 648/648 ps1, 645/648 py;
- 1С-сертификация на 8.3.24: accounting-register, business-process, task,
  chart-of-characteristic-types — все приняты;
- дрейф эталонов 6 файлов: перестановки плюс единственное новое содержимое —
  блок IsFolder (проверено вырезанием: без него мультимножества совпадают).

Остаётся известным: у бухрегистра условные стандартные реквизиты стоят по обе
стороны фикс-списка (PeriodAdjustment перед, ExtDimension1..3 и
ExtDimensionType1..3 после), а эмиттер кладёт все «лишние» ключи вперёд. Это
правка механизма, а не таблицы — отдельной задачей.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 17:37:25 +03:00
Nick ShirokovandClaude Opus 5 0a2dba33cf fix(meta-compile): в Predefined.xml ссылочный тип остаётся локальным
Регрессия предыдущего коммита: cfg: применялся безусловно, а шапка
Ext/Predefined.xml объявляет только predef/v8/xr/xs/xsi — cfg там нет.
Получался неразрешимый префикс, и платформа отказывалась грузить конфигурацию
(1С-сертификация meta-compile/chart-of-characteristic-types: FAIL на
LoadConfigFromFiles; на коде до правки кейс проходил).

Правило платформы, снятое с корпуса, ровно то же, что уже заложено в meta-edit:
префикс из корня, если он там объявлен, иначе локальное объявление. В самом
Predefined.xml платформа пишет
`<v8:Type xmlns:d6p1="…current-config">d6p1:CatalogRef.Валюты</v8:Type>`.

Типизированные предопределённые есть только у ПВХ, а ПВХ не входит ни в один
из шести списков раундтрипа — поэтому корпус эту поломку поймать не мог, её
поймала только 1С-сертификация. Добавлен список list-cct-all.txt (24 объекта
acc+erp), чтобы пробел закрылся: content match 24/24, TOTAL diff 0.

Проверка: 1С-сертификация кейса проходит, runner 648/648 ps1 и 645/648 py.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 16:50:45 +03:00
Nick ShirokovandClaude Opus 5 683e370938 fix(meta-compile,meta-edit): ссылочный тип корневым cfg:, как пишет платформа
Эмитился локальный xmlns:d5p1 на тот же URI, что уже объявлен в шапке файла.
Формально эквивалентно (значим URI, а не префикс), платформа принимала — но
первый же цикл «загрузить в базу → выгрузить» переписывал КАЖДЫЙ ссылочный тип
в cfg:, то есть давал diff-шум при неизменной семантике. Форма пришла из СКД,
где cfg: действительно не работает; в метаданных такого ограничения нет.

Радиус оказался втрое меньше прежней оценки: form-compile в него НЕ входил —
он уже писал cfg:, а все его d5p1 относятся к чужим пространствам
(txtedt/chart/geo/graphscheme/data-analysis), которых в корне нет и где
локальное объявление законно.

meta-edit берёт префикс из объявлений КОРНЯ правимого файла, а если URI там не
объявлен — остаётся на самодостаточной локальной форме. Это не перестраховка:
ссылочный тип живёт в ТЕКСТЕ узла, поэтому XML-слой про префикс не знает и сам
объявление не добавит — проверено, .NET на документе без xmlns:cfg молча пишет
неразрешимый префикс. Та же природа объясняет удалённый костыль в meta-edit.py:
lxml выбрасывал xmlns:d5p1 как «неиспользуемый», и его возвращали регуляркой
после сериализации.

Синхронно снята нормализация ref-префикса в раундтрип-харнесе (debug/, вне git):
она сводила cfg: и d5p1: к одному REF: и делала харнес слепым ровно к тому,
ради чего затевался переход.

Проверка:
- живой цикл через базу (db-create → db-load-xml → db-dump-xml): выгрузка
  платформы совпала с нашими исходниками ПОСТРОЧНО, расхождений ноль;
- корпусный раундтрип 4897 объектов уже БЕЗ маски префикса: match не просел
  (150/1640/151/2600/314/42), порядок ≠ 0, TOTAL diff lines 0;
- юнит-тесты 648/648 ps1, 645/648 py;
- 1С-сертификация meta-edit 17/17 на платформе 8.3.24;
- дрейф эталонов 48 файлов, в диффе нет ни одной строки кроме d5p1: → cfg:;
  skd-* не затронуты ни одним файлом (там cfg: не работает).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 16:28:47 +03:00
Nick ShirokovandClaude Opus 5 a3aa8fe87c fix(meta-compile): порядок элементов по канону выгрузки
Детект порядка, добавленный в раундтрип-харнес, показал 1242 расхождения на
4897 объектах (25%). Раньше они были невидимы: дифф строился через
Compare-Object, то есть по мультимножеству строк, и позиция не проверялась.
Ложных срабатываний там нет по построению — проверка включается только когда
мультимножества уже совпали.

Три категории, все — порядок эмиссии; DSL и декомпилятор не затронуты.

1. Виды детей регистров (1140 объектов). Компилятор печатал
   Resource, Dimension, Attribute. Канон, снятый с корпуса acc+erp (разброса
   внутри типа нет): Resource, Attribute, Dimension у информационного,
   накопления и расчёта; Dimension, Resource, Attribute — у бухгалтерского.
   Команды у платформы идут последними, как и было.

2. Квалификаторы в составном типе (61 объект). Платформа пишет сначала ВСЕ
   <v8:Type>/<v8:TypeSet>, потом блоки квалификаторов, а Emit-TypeContent
   рекурсивно печатал каждую часть целиком. На одиночном типе оба порядка
   совпадают — потому и не всплывало. Порядок самих блоков тоже канонический
   и НЕ зеркалит порядок типов: Number, String, Date (при типах
   boolean,string,dateTime,decimal квалификаторы идут Number,String,Date;
   контрпримеров в корпусе нет).

3. Стандартные реквизиты плана обмена (41 объект, весь список): блок
   начинается с ThisNode, а не с Ref.

Проверка:
- корпусный раундтрип 4897 объектов: порядок ≠ 1242 → 0, match не просел
  (150/1640/151/2600/314/42), TOTAL diff lines 0;
- юнит-тесты 648/648 ps1, 645/648 py;
- 1С-сертификация на платформе 8.3.24: регистры информационный, накопления,
  бухгалтерский, расчёта и план обмена — все приняты;
- дрейф снэпшотов (10 файлов, 9 кейсов) сверен как чистая перестановка строк.
  NB: сверять нужно с нормализацией UUID-\d+ — раннер нумерует плейсхолдеры по
  порядку появления, и перестановка детей меняет, кому достанется UUID-015.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 15:22:18 +03:00
Nick ShirokovandClaude Opus 5 3ef0d74158 fix(19 навыков): единая финализация XML — стиль исходника и корректный CDATA (#57)
Три остатка волны #57, все в одной точке — блоке записи XML.

1. Порты расходились по EOL. PS гонял документ через XmlWriter с дефолтным
   NewLineHandling.Replace и принудительно переводил весь файл в CRLF, а py
   сохранял стиль исходника: role-compile на LF-ном Configuration.xml давал
   10311 байт против 10062. Ещё в пяти навыках None стоял, но финальной
   нормализации не было, и на LF-исходнике выходил смешанный EOL (meta-edit:
   40 CRLF + 136 одиночных LF) — та же форма, что у исходного дефекта #57.
   Приведено к форме cf-edit.ps1: None + канонизация к LF + целевой перевод
   строки (стиль файла-назначения, для создаваемого файла — канон CRLF).
   Replace не годится как замена: он превращает переводы строк внутри значений
   атрибутов в &#13;.

2. Гард `if (-notmatch CDATA)` не обрабатывал CDATA, а отказывался от канона
   во всём файле — то есть деградировал до «не сделал ничего». Заменён
   альтернацией: участки CDATA и комментариев возвращаются как есть, замена
   идёт только вне них. На реальных данных поведение не меняется — в корпусе
   из 476 942 XML нет ни одного CDATA и ни одного комментария.

3. py: три реализации одного правила детекта EOL сведены к одной. Мажоритарное
   правило в role-compile/subsystem-compile давало ДРУГОЙ ответ на смешанном
   входе. Дефолты _finalize_xml_bytes для нового файла приведены к канону
   (UTF-8, CRLF, без хвостового перевода); meta-edit срезает хвост в обеих
   ветках, а не только при создании файла.

Попутно, найдено байтовой сверкой портов:
- py вставлял <Role>/<Form>/<Subsystem> с пятью табами вместо трёх —
  подстановка по голому </ChildObjects> удваивала отступ строки; снэпшоты
  этого не видели, так как схлопывают пробелы между тегами;
- py-порты xdto-* писали XML-декларацию одинарными кавычками (так отдаёт
  lxml), платформа и PS пишут двойные.

Регресс: runner 647/647 ps1, 644/647 py (3 skipped), дрейфа снэпшотов нет.
Корпусный раундтрип метаданных: 4897 объектов, match 100%, совпадает с
эталонами захода #57.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 21:41:55 +03:00
Nick ShirokovandClaude Opus 5 750ba3bb13 refactor(эмиттеры): одна формулировка на повторяющийся комментарий
Код нормализации пустого тега повторён в 24 местах, а комментарий к нему за время
работы над #57 расплодился в шесть вариантов формулировки. Это то же расхождение
копий, что и в коде, только в тексте: читающий не понимает, значима разница или нет.

Сведено к одной паре формулировок — про сам тег с гардом и про запись через
MemoryStream. Комментарий теперь начинается с ПРАВИЛА, а не с причины: раньше
верхняя строка рассказывала, что here-string наследует LF от .ps1, и читалось это
как «переводы строк берём из скрипта навыка» — тогда как правило обратное:
создаём файл — пишем канон, правим существующий — наследуем его стиль.

Два пояснения в cfe-borrow унификация чуть не стёрла, они возвращены: источником
там служит OuterXml, а не XmlWriter, и нормализация стоит после вставки реквизитов,
чтобы накрыть и их. Это причина местного отличия, а не дубль.

Ссылки на ишью прорежены: #44/#46/#47 осталась по одной на файл — там, где правило
формулируется, — и убрана оттуда, где была эхом. #57 в скриптах не было вовсе.

Заодно выровнены две мелочи, найденные той же сверкой копий: interface-edit в
py-порте не обрезал хвостовой перевод там, где PS обрезает (поведение совпадало,
код — нет), а cf-edit нормализовал EOL инлайном вместо общей двухшаговой формы.

Версии подняты в ОБОИХ портах всех затронутых навыков, включая те, где текстуально
менялся только .ps1: номер версии в этом проекте читается как «одно и то же
поведение», и разводить его из-за комментария нельзя. Паритет проверен — 69 пар,
ноль расхождений.

Windows 641/641 обоими портами.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 20:06:38 +03:00
Nick ShirokovandClaude Opus 5 11e4e4301f fix(cf-init,cfe-init,meta-compile): пустой элемент вместо пустой пары (#57)
Конфигуратор пишет <Vendor/>, а не <Vendor></Vendor>: в 8 выгрузках cfsrc
(476 943 XML) пустых пар ноль. Шаблоны подставляли значение ВНУТРЬ пары, поэтому
при пустом значении получалась пара.

Собираем элемент целиком: <Vendor>/<Version> (cf-init, cfe-init),
<DefaultRoles> (cfe-init при -NoRole), <Key> и <Namespace> (meta-compile —
план обмена и веб-сервис). Образец рядом же: <Comment> и <Description> в
meta-compile так делали и раньше.

Дефект был в ОБОИХ портах одинаково — здесь дело не в сериализаторе, а в шаблоне.

Пустая пара <Namespace> в cfe-borrow была не его: он копирует свойства
заимствуемого объекта, а порождал их meta-compile.

Дрейф снэпшотов: 340 <Vendor/>, 339 <Version/>, по одному <Key/>, <Namespace/>,
<DefaultRoles/>. Содержательных изменений ноль. Тесты 641/641.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 15:55:31 +03:00
Nick ShirokovandClaude Opus 5 0b8231c467 fix(17 навыков): CRLF в разделителях строк XML (#57)
Головной дефект тикета: cfe-init выдавал Configuration.xml с 10 CR на 70 строк, а
роль и язык — вовсе без CR. Теперь 70/70, последний байт `>`.

Правило: меняем только РАЗДЕЛИТЕЛИ строк, содержимое текстовых узлов не трогаем.
Платформа его не трогает тоже — в запросах СКД из чистой выгрузки встречаются и
CRLF, и одиночный LF. Поэтому сплошная нормализация файла применяется только там,
где многострочных текстовых узлов нет по построению (скелеты), а в объектном XML
и формах разделители правятся в точке сборки.

Источников оказалось четыре, а не один:

1. Скелеты (cf-init, cfe-init, epf-init, erf-init, form-add, help-add,
   template-add) собираются here-string'ами, а .ps1/.py в репозитории хранятся с
   LF — отсюда LF и смешанный EOL.
2. py-порты склеивали документ через '\n'.join(lines); PS в тех же местах давал
   CRLF через AppendLine — порты расходились побайтово.
3. Билдеры Predefined в meta-compile собирались с явным LF и даже сворачивали
   CRLF→LF из общего эмиттера типов.
4. Чтение существующего файла в python БЕЗ newline='' молча схлопывает CRLF в LF
   (универсальные переводы строк), и запись потом кладёт LF. Так role-compile и
   subsystem-compile переписывали в LF весь Configuration.xml. meta-compile это
   уже делал правильно — там newline='' стоял с фикса #44/#46/#47.

Отдельно: XML-парсер по спецификации схлопывает CRLF при разборе, поэтому
lxml-порты (xdto-compile, xdto-edit) отдавали LF-документ там, где .NET возвращал
CRLF через NewLineHandling. Восстанавливаем EOL исходного файла.

Разделение канона и сохранения стиля:

- файл СОЗДАЁМ — канон (CRLF, без хвоста);
- существующий ПРАВИМ — наследуем его EOL, включая перевод строки вставки
  (контракт #44/#46/#47). Иначе LF-проект получал бы смешанные файлы — ровно то,
  на что заведён #57. Кейсы roundtrip-crlf-preserve остаются зелёными.

Хелпер записи скопирован в каждый навык (навыки автономны). У meta-compile он
называется Write-XmlFileKeepEol / write_xml_file_keep_eol: там нормализовать EOL
НЕЛЬЗЯ (многострочные запрос, синоним, значение заполнения), и одинаковое имя при
разном поведении было бы ловушкой.

Заодно: имя временного файла батча в meta-compile.ps1 получило GUID. Фиксированное
"meta-compile-batch-$idx.json" в общем %TEMP% сталкивало два параллельных запуска
(«file is being used by another process») — из-за этого полный набор приходилось
гонять с урезанной параллельностью. py-порт уже брал mkstemp.

Аудит: было ~250 файлов с дефектом EOL, стало 0 в обоих портах. Осталось четыре
законных случая — фикстуры roundtrip-crlf-preserve (сохранение стиля) и запрос
динсписка с многострочным текстом. Дрейф снэпшотов — 1 файл. Тесты 641/641.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 15:37:26 +03:00
Nick ShirokovandClaude Opus 5 abe9431d8f fix(9 навыков): без хвостового перевода строки в конце XML (#57)
Конфигуратор не пишет перевод строки в конце файла — последний байт `>`. Это видно
на чистой выгрузке пустой ИБ (Windows и macOS) и на всех 8 выгрузках в cfsrc.
Наши эмиттеры добавляли лишний, причём оба порта и по разным причинам:

- PS: StringBuilder.AppendLine дописывает Environment.NewLine после последней строки;
- PY: '\n'.join(lines) + '\n' — хвост добавлен явно.

В meta-compile заведена единая точка записи Write-XmlFile: там документ собирают
и AppendLine, и явные `n в билдерах Predefined/Content/Flowchart, а выходов семь.
В остальных обрезка стоит в самом вызове записи.

Модули .bsl, JSON-выход form-compile, HTML-справка help-add и XSD из xdto-decompile
сюда НЕ входят: канон Конфигуратора описывает XML метаданных, а у этих артефактов
свои правила. Результаты XmlDocument.Save тоже не трогаются — у них хвоста нет.

Навыки редактирования продолжают сохранять стиль ВХОДНОГО файла: кейс
subsystem-edit/roundtrip-crlf-preserve, где фикстура намеренно с хвостом, остаётся
зелёным. Это контракт #44/#46/#47, и он не отменяется.

Дрейф снэпшотов: 547 файлов, у каждого только последняя строка, содержательных
изменений ноль. Тесты 641/641.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 13:53:05 +03:00
Nick ShirokovandClaude Opus 5 39cf8e49cf fix(17 навыков): самозакрывающийся тег без пробела и UTF-8 в декларации (#57)
.NET XmlWriter пишет `<a />`, а Конфигуратор — `<a/>`. Поскольку Save переписывает
файл целиком, meta-edit при добавлении одного реквизита переводил в пробельную форму
все пустые теги документа. Дефект только у PS-порта: lxml (45 из 49 py-портов) уже
пишет плотно, то есть порты расходились побайтово.

Канон измерен, а не предположен: чистая выгрузка пустой ИБ на Windows и macOS плюс
сплошной скан 8 выгрузок в cfsrc — 476 943 XML, 21 294 119 самозакрывающихся тегов,
пробельных 0. Форма не зависит от ОС, версии платформы (8.3.20-8.5) и наличия
атрибутов. Правило уже было реализовано в skd-edit — оттуда и взято.

Замена безопасна доказуемо: .NET экранирует `>` как `&gt;` и в тексте, и в атрибутах,
поэтому ` />` после Save — только конец тега. Лазейки (CDATA, комментарии) в
1С-метаданных не встречаются — 0 из 476 943 файлов; гард на них всё равно стоит.

Одиннадцать скриптов писали прямо в FileStream без пост-обработки — переведены на
MemoryStream, что заодно чинит `encoding="utf-8"` строчными (335 файлов в снэпшотах).
В cfe-borrow правится и сборка Form.xml: куски берутся из OuterXml, а он спацовывает
так же, как XmlWriter.

Дрейф снэпшотов: 12 838 строк тегов + 335 деклараций, содержательных изменений ноль.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 13:28:58 +03:00
Nick ShirokovandClaude Opus 5 fae22435d4 fix(cfe-borrow,form-*,interface-edit,meta-compile,role-compile,subsystem-compile,template-add): длина среза Configuration.xml
Detect-FormatVersion читает префикс Configuration.xml как
ReadAllText(...).Substring(0, Min(2000, (Get-Item).Length)). Размер файла — в
БАЙТАХ, Substring считает СИМВОЛЫ: на кириллице байт больше, и если файл
короче 2000 символов, длина среза выходит за строку и навык падает
исключением. Проявлялось на маленьких конфигурациях — например на фикстуре
EPF внутри конфигурации на поддержке.

Функция расходится копиями по навыкам, поэтому правка одинаковая в восьми:
длина берётся по самой строке. В help-add так было изначально; в
meta-compile рядом (Detect-CompatibilityMode) уже стоял верный вариант.

py-порты иммунны: там f.read(2000) в текстовом режиме — читает символы и не
бросает исключение. Зеркалить нечего, версии выровнены по обоим портам.

Заодно tests/skills/verify-snapshots.mjs: outputPath кейса читался только из
params, тогда как runner.mjs берёт его с верхнего уровня — из-за расхождения
харнесс искал результат не там, где навык его написал, и это маскировало
падение как «выход не создан».

Проверка: 1С-сертификация mxl-compile 13/13 (кейс guard-allow-external падал
с начала кампании), полная сюита 630/630 на PowerShell и 627+3 skipped на python.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 15:35:17 +03:00
Nick ShirokovandClaude Opus 5 919d49fe14 fix(form-edit,meta-edit,mxl-compile,role-compile,subsystem-*): кавычки в тексте не экранируем
Правило платформы единое и подтверждено дважды. По корпусу трёх конфигураций:
92142 сырых кавычки в тексте элементов и НИ ОДНОЙ &quot;; в условиях RLS —
16334 сырых против нуля экранированных (при этом &amp; платформа пишет, то есть
амперсанд экранируется, а кавычка нет). Загрузка на стенде: оба варианта
принимаются, но выгружает платформа сырую кавычку — то есть &quot; не ошибка,
а лишний шум в роундтрипе.

Решение было принято раньше в form-compile (там оно и записано комментарием) и
в skd-compile/skd-edit, но шесть навыков из него выпали. Приведены к общему виду:
где Esc-Xml использовался только для текста — функция стала текстовой; в meta-edit,
где она нужна и для атрибута, текстовые места переведены на существующий
Esc-XmlText. Атрибуты нигде не затронуты: там экранирование кавычек обязательно.

Дрейф эталонов — три строки, все условия RLS; role-info и role-validate строят
фикстуры прогоном role-compile (кросс-навыковый пересъём).

Проверка: полная сюита 630/630 на PowerShell и 627+3 skipped на python;
1С-сертификация role-compile 9/9, subsystem-compile 9/9, subsystem-edit 6/6,
form-edit 6/6, meta-edit 16/16. В mxl-compile кейс guard-allow-external падает
и до правки — не связан.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 15:08:55 +03:00
Nick ShirokovandClaude Opus 5 6963df6b99 fix(meta-compile,meta-decompile): полиморфный DataPathField и экранирование кавычек в тексте
Первый полный прогон по всем поддерживаемым типам трёх конфигураций
(41366 объектов) вскрыл два дефекта, живущих только в корпусе 2.17.

DataPathField в блоке характеристик полиморфно: обычно -1 («не задано»),
но в 8 случаях содержит путь к полю. Обе стороны жёстко приводили значение
к [int], из-за чего декомпиляция всего объекта падала — 8 документов БП не
разбирались вовсе. Теперь число остаётся числом, а путь проходит через
Expand-CharField/Shorten-CharField, как соседние путевые поля.

Текст элемента экранировался атрибутной функцией: кавычка превращалась в
&quot;, тогда как платформа держит её в тексте как есть (наименование
«Транспортные средства, зарегистрированные в системе "Платон"» в ERP).
В компиляторе для этого давно есть Esc-XmlText/esc_xml_text — переведены
все 128 мест эмиссии текста в обоих портах. Замена строго ослабляющая:
атрибуты не затронуты, экранирование & < > сохранено.

Проверка: три документа БП с путевым DataPathField 3/3, сюита meta-compile
76/76 на обоих портах. Полный прогон корпуса идёт частями.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 13:49:25 +03:00
Nick ShirokovandClaude Opus 5 05beeca7d3 fix(meta-compile,meta-decompile): многоязычные синонимы и регистр RootURL в сервисах
Прогон сервисов на конфигурациях формата 2.17 (ERP + БП, 46 объектов) вскрыл
то, чего не было в УТ: 25 совпадений из 46.

ERP двуязычна, и синоним шаблона, метода, операции и параметра приезжает
объектом {ru,en}. Компилятор интерполировал его в строку — в XML попадал
литерал "@{ru=Версия; en=Version}" вместо пары языковых элементов. Значение
теперь передаётся в эмиттер как есть, он и так умеет обе формы.

RootURL сравнивался с дефолтом (имя в нижнем регистре) регистронезависимо,
поэтому MobileAppReceiptScanner считался дефолтным и терял регистр при
регенерации. Сравнение сделано регистрочувствительным — тот же класс ошибки,
что уже ловили на синонимах методов.

Спека meta-dsl-spec дополнена: объектные формы шаблонов, методов, операций и
параметров; xdtoPackages как список; descriptorFileName; dataLockControlMode;
нотация Кларка для типов из своего пространства имён; Use в ReuseSessions.

Проверка: сервисы ERP+БП 46/46, сервисы УТ 25/25, сюита 76/76 на обоих
портах, 1С-сертификация кейса с многоязычным синонимом пройдена.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 11:47:02 +03:00
Nick ShirokovandClaude Opus 5 87f97e986a feat(meta-decompile,meta-compile): поддержка WebService и полнота его формата
meta-decompile не знал тип WebService — 18 сервисов УТ не проходили раундтрип.
Добавлен разбор (оба порта): namespace, состав XDTO-пакетов, дескриптор,
операции с параметрами.

Компилятор поддерживал тип поверхностно; раундтрип на реальных сервисах
показал, чего не хватало:

- XDTOPackages эмитился скаляром, а это СПИСОК элементов: ссылка на пакет
  конфигурации (xr:MDObjectRef) либо URI внешнего пространства имён
  (xs:string). Presentation пуст, CheckState 0 — 19/19 по корпусу;
- DescriptorFileName не эмитился вовсе, хотя есть у всех 18 сервисов и НЕ
  выводится из имени (DMILService -> dmil.1cws) — нужен явный ключ;
- DataLockControlMode не эмитился (Managed у всех 192 операций);
- Comment не эмитился ни у сервиса, ни у операций и параметров;
- типы из собственного пространства имён (81 случай) писались без локального
  xmlns. В DSL задаются нотацией Кларка "{uri}ИмяТипа", компилятор объявляет
  xmlns сам — как это делает платформа.

Nillable захватывается явно у операций и параметров: по корпусу значения
смешанные (операции 103/89, параметры 128/395), дефолт угадать нельзя.

Порядок операций и параметров приведён к порядку DSL в обоих портах — PS шёл
в порядке хеш-таблицы, py сортировал.

Проверка: 25 сервисов УТ (18 WebService + 7 HTTPService) 25/25 без
расхождений; JSON декомпилятора побайтово совпадает у PS и py на всех 25;
сюита 76/76 на обоих портах; 1С-сертификация обоих кейсов пройдена.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 11:40:10 +03:00
Nick ShirokovandClaude Opus 5 01c45581ef feat(meta-decompile): поддержка HTTPService + починка его компиляции
meta-decompile не знал тип HTTPService, поэтому 7 сервисов УТ не проходили
раундтрип вовсе. Добавлен разбор: rootURL, reuseSessions, sessionMaxAge и
дерево urlTemplates -> methods.

Раундтрип на реальных сервисах вскрыл три пробела компилятора:

1. ReuseSessions: в allowlist были только DontUse и AutoUse, а платформа
   пишет ещё и Use (4 объекта в корпусе) — компиляция падала с ошибкой.
2. Обработчик метода выводился по формуле ИмяШаблона+ИмяМетода; в реальных
   конфигурациях он произвольный (УдаленныйВызовМетодаЧерезТелоЗапроса).
   Метод получил объектную форму {httpMethod, handler, synonym, comment}
   рядом со строчным сокращением "только HTTP-метод".
3. Comment не эмитился ни у объекта, ни у шаблонов и методов, хотя платформа
   пишет тег всегда.

Порядок шаблонов и методов приведён к порядку DSL в обоих портах: PS шёл в
порядке хеш-таблицы, py сортировал — снэпшоты бы разъехались между портами.

В декомпиляторе сравнение синонима с авто-выводом сделано регистрочувствительным
(-ceq): "Post" против "post" считалось совпадением, и синоним терялся.

Проверка: HTTP-сервисы УТ 7/7 без расхождений (было 0 — тип не поддерживался),
сюита 76/76 на обоих портах, 1С-сертификация кейса пройдена.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 21:20:44 +03:00
Nick ShirokovandClaude Opus 5 dac265995e fix(meta-compile): LineNumberLength только объектам с хранением в БД
Раундтрип по обработкам и отчётам УТ (7568 объектов) показал 265 лишних
строк: мы эмитили LineNumberLength каждой табличной части, а платформа
пишет его только объектам, у которых есть таблица в базе.

Проверено на двух конфигурациях независимо: у обработок 0 тегов из 235+257
табличных частей, у отчётов 0 из 106+8; у справочников, документов, ПВХ,
планов обмена и бизнес-процессов тег стоит поголовно. Свойство задаёт
разрядность физического номера строки в таблице БД — у обработок и отчётов
таблиц нет, хранить нечего.

Исключены DataProcessor и Report, а также ExternalDataProcessor и
ExternalReport: внешние обработки той же природы, и при сборке EPF на
формате 2.20 мы бы писали тег, которого платформа не пишет.

Кейс format-220-props дополнен обработкой с табличной частью: у неё тега
нет, у справочника в той же конфигурации LineNumberLength=9.

Проверка: УТ tier-2 7543/7543 без расхождений (было 71 diff),
сюита 76/76 на обоих портах, 1С-сертификация на 8.3.27 пройдена.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 20:58:31 +03:00
Nick ShirokovandClaude Opus 5 4b6c3faa1d fix(meta-compile): строка вида "Документ.Имя" перестала молча становиться ссылкой
Значением параметра выбора и значения заполнения бывает ЗНАЧЕНИЕ — пустая
ссылка, предопределённый элемент или значение перечисления. Объект метаданных
значением быть не может, поэтому двухчастное "Тип.Имя" — это строка.

Правило уже существовало, но только для перечислений ("Enum.X" не значение);
для остальных корней его не было, и строка "Документ.РеализацияТоваровУслуг"
превращалась в xr:DesignTimeRef с переводом имени на английский — молчаливая
подмена смысла: вместо текста получалась ссылка на реальный документ.

Проверка по корпусу (acc+erp+ut): 5520 значений в ChoiceParameters и 8623 в
FillValue — двухчастных с типом метаданных НЕТ ни одного. Прощающий ввод не
пострадал: "Справочник.Номенклатура.ПустаяСсылка" -> Catalog.Номенклатура.EmptyRef,
"Перечисление.X.Y" -> Enum.X.EnumValue.Y работают как раньше (закреплено кейсом).

Проверка: УТ tier-1 1283 -> 1282 совпадения, справочники 519/519,
сюита 76/76 на обоих портах, 1С-сертификация кейса пройдена.
Остаётся 1 расхождение — план обмена без Ext/Content.xml (хвостовая аномалия
конфигурации: на одной БП все четыре платформы файл пишут).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 20:30:51 +03:00
Nick ShirokovandClaude Opus 5 3d5c9e5bee fix(meta-compile,meta-decompile): не-дефолтный TypeReductionMode измерения и nil в FixedArray
Раундтрип по документам и регистрам УТ (1283 объекта) вскрыл два пробела.

TypeReductionMode измерения РС: декомпилятор значение захватывал, но парсер
измерения в компиляторе не переносил ключ дальше, и любое отклонение от
дефолта молча заменялось на TransformValues. Так терялось третье значение
свойства — DeleteData («Удалять данные»); всего у свойства три режима:
Преобразовывать значения (дефолт), Удалять данные, Запрещать.
Синтетика на матрице платформ: DeleteData принимается и переживает роундтрип
на 8.3.25, 8.3.26 и 8.3.27 — порог тот же, что у самого свойства (2.18).

nil-элемент внутри FixedArray (<v8:Value xsi:nil="true"/>) декомпилировался
пустой строкой, компилятор эмитил xs:string. Теперь это JSON null в обе
стороны. Конструкция не новая — есть уже в дампе 8.3.20.

Спеки: у TypeReductionMode перечислены все три значения с названиями из
конфигуратора; версия появления исправлена с 2.20 на 2.18.

Проверка: УТ tier-1 1283 объекта -> 1280 совпадений (было 1273),
сюита meta-compile 76/76 на обоих портах.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 20:03:28 +03:00
Nick ShirokovandClaude Opus 5 75e861da6e fix(meta-decompile,meta-compile): пустая ссылка в ChoiceParameters теряла тип
Раундтрип по справочникам УТ 11.5.27 вскрыл: <app:value xsi:type="xr:DesignTimeRef"/>
с пустым содержимым декомпилировался в "value": "", и компилятор законно
эмитил свой дефолт xs:string — тип ссылки терялся.

Конвенция для этого случая уже была (маркер {emptyRef: true} у fillValue),
пробел был только в ChoiceParameters. Декомпилятор теперь ставит маркер,
компилятор его понимает — и в скалярном значении, и внутри FixedArray.

Форма редкая, поэтому прежние кампании её не поймали: в acc она встречается
1 раз, в erp — ни разу, в УТ — 7 раз.

Is-EmptyRef в PS явно отсекает коллекции: у массива $v.emptyRef разворачивается
в свойства элементов (member enumeration), и массив с одним таким элементом
схлопывал FixedArray в скаляр. В py-порте isinstance(v, dict) такого не допускает —
расхождение поймано снэпшотом.

Проверка: справочники УТ 519/519 без расхождений (было 519 diff),
сюита meta-compile 76/76 на обоих портах, 1С-сертификация кейса пройдена.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 19:25:24 +03:00
Nick ShirokovandClaude Opus 5 2e5289a88f fix(meta-compile,meta-validate,cf-init,*-validate): промежуточные версии формата 2.18/2.19
Версия формата выгрузки идёт лестницей, а не скачком 2.17 -> 2.20:
8.3.20-8.3.24 -> 2.17, 8.3.25 -> 2.18, 8.3.26 -> 2.19, 8.3.27 -> 2.20.
Прежняя дельта мерилась через две ступени и приписала версии 2.20 всё,
что появилось по дороге.

Замер на одной и той же конфигурации, выгруженной четырьмя платформами:
- 2.18: TypeReductionMode (4793 файла), TextToSpeech, 3 редких свойства форм;
- 2.19: новых тегов нет, роли перешли на omit-on-default;
- 2.20: только LineNumberLength.

meta-compile: единый гейт isFormat220 управлял обоими свойствами, поэтому на
проекте 2.18/2.19 TypeReductionMode не эмитился, хотя платформа этих версий
его пишет — роундтрип разъезжался. Порог расщеплён: >=2.18 для
TypeReductionMode, >=2.20 для LineNumberLength.

meta-validate: реестр versionedProps объявлял TypeReductionMode свойством
2.20, из-за чего проверка 18 давала ложную ошибку на корректном файле
2.18/2.19. Исправлено на 2.18.

Валидаторы (meta/form/cf/cfe/epf) считали 2.18 и 2.19 неизвестными версиями
и предупреждали «Unusual version»; cf-init не давал отскаффолдить
конфигурацию под 8.3.25/8.3.26. Обе версии впущены.

Тесты: фикстура empty-config-218, кейс meta-compile на границу свойств
(TypeReductionMode есть, LineNumberLength нет), два кейса meta-validate на
законность штампов 2.18/2.19; кейс error-220-props-in-217 теперь проверяет
оба сообщения с разными порогами. Кейс 2.18 проверен загрузкой в живую
8.3.25 через verify-snapshots.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 16:28:50 +03:00
Nick ShirokovandClaude Opus 5 068928646d feat(meta-compile,meta-decompile): свойства формата 2.20 (платформа 8.3.27)
Дельта формата 2.17→2.20 содержит три безусловных свойства, которых компилятор
не эмитил. Все три пишутся ТОЛЬКО при формате >= 2.20 (Detect-FormatVersion),
поэтому 2.17-проекты не меняются: полная сюита зелёная, ни один существующий
снэпшот не сдвинулся.

- xr:TypeReductionMode — каждому стандартному реквизиту, после CreateOnInput.
  TransformValues, кроме Owner → Deny (правило проверено против выгрузки acc:
  9 из 9 реквизитов совпали, включая Owner).
- TypeReductionMode — измерениям регистра СВЕДЕНИЙ (у прочих семейств и у
  реквизитов/ресурсов платформа его не пишет).
- LineNumberLength — табличным частям, последним в Properties.

LineNumberLength — прикладная возможность 8.3.27 (5..9 → до 999 999 999 строк
вместо 99 999), поэтому получил полноценный DSL-ключ и описание в spec §5.2.
Его дефолт зависит НЕ от версии формата, а от режима совместимости на момент
создания ТЧ (<=8_3_26 → 5, >=8_3_27 → 9) — платформа фиксирует значение и позже
не пересчитывает, поэтому в одной конфигурации соседствуют ТЧ с 5 и 9. Отсюда
новая Detect-CompatibilityMode: читает CompatibilityMode из Configuration.xml
(префикс 64 КБ — тег лежит на ~11-12 КБ, существующим 2000 байт не хватает).

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

Компараторы версий числовые по компонентам — строковое сравнение неверно
("2.9" > "2.17" лексикографически).

Тест-инфра: setup-фикстуры empty-config-220 и empty-config-220-compat24
(строятся тем же cf-init), два кейса — по одному на каждую ось.

Проверка: роундтрип реального 2.20-документа БП (АвансовыйОтчет, 7 ТЧ) —
по новым тегам 0 расхождений, значения и позиции совпали; остаточный хвост
52/39 идентичен такому же на 2.17, то есть пред-существующий. Сюита 570/570
ps1, 567+3 skipped py, ps1==py. 1С-сертификация обоих кейсов на 8.3.27 ✓.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 17:53:54 +03:00
Nick ShirokovandClaude Opus 5 66d45d3654 fix(meta-compile): нормализация MDObjectRef — CatalogRef./русская запись → Catalog.
MDObjectRef ссылается на ОБЪЕКТ метаданных (Catalog.Валюты), а не на тип ссылки.
Эмиттеры пропускали значение как есть, если в нём была точка, поэтому
"CatalogRef.Валюты" доходил до XML без изменений → при загрузке конфигурации
платформа отвечала «Неизвестный объект метаданных».

Инструкция вела в баг сама: reference/catalog.md документировал
owners: ["CatalogRef.Контрагенты"]. Тестами не ловилось — все кейсы
использовали каноническую форму.

- Normalize-MDObjectRef расширена ссылочными формами (англ. *Ref + рус. *Ссылка);
  вида метаданных, оканчивающегося на Ref, не существует → схлопывание однозначно.
  В ТИПАХ реквизитов запись CatalogRef.X верна — там мапа не применяется.
- Добавлен параметр defaultRoot (голое имя без точки), инлайн-подстановка
  "Catalog.$ownerRef" в owners убрана — логика теперь в одном месте.
- Нормализация применена в 4 местах, где её не было: owners, basedOn,
  registerRecords, baseCalculationTypes.
- Кейс catalog-inputbystring-datalock переведён на неканонический вход:
  снэпшот не изменился ни на байт — прямое доказательство нормализации.

Регресс 73/73 ps1+py, полная сюита 566/566. Живая проверка на 8.3.27:
подчинённый справочник с owners CatalogRef./СправочникСсылка. грузится чисто.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 14:26:55 +03:00
Nick ShirokovandClaude Opus 4.8 ddc1641176 fix(support-guard): не блокировать автономные внешние обработки/отчёты (#39)
При поиске корня конфигурации guard поднимался по дереву вверх и «проскакивал»
собственный корень автономной внешней обработки/отчёта (ExternalDataProcessor /
ExternalReport), лежащей внутри дерева выгрузки конфигурации. Если у охватывающей
конфигурации выключена возможность изменения (G=1), внешний объект ложно
блокировался как «объект типовой конфигурации на поддержке», а info-навыки
выводили нерелевантную строку «Поддержка: конфигурация read-only».

Теперь climb останавливается на границе автономного объекта: если целевой файл или
встреченный по пути <каталог>.xml имеет корень ExternalDataProcessor/ExternalReport,
подъём прекращается и объект не привязывается к конфигурации. Корень внешнего объекта
всегда глубже Configuration.xml, поэтому встречается первым — регрессии для обычных
объектов конфигурации нет.

Синхронно во всех копиях guard-а (навыки автономны): хук support-state.mjs
(decideSupport + findConfigRoot), 16 мутаторов (Assert-EditAllowed), 5 info-навыков
и meta-info (Get-SupportStatusForPath / Get-ObjectSupportStatus) — ps1 и py. Для
info-навыков строка «Поддержка:» для внешнего объекта опускается.

Тесты: hooks/test/run.mjs — секция внешней границы (G=1 + встроенная EPF);
tests/skills — кейсы mxl-compile (guard пропускает) и mxl-info (строка опущена).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 19:46:54 +03:00
Nick ShirokovandClaude Opus 4.8 700ede1df7 feat(meta-compile): валидация category у CommandGroup (v1.65)
Category добавлена в validEnumValues (NavigationPanel/ActionsPanel/FormCommandBar/
FormNavigationPanel) — опечатка даёт ошибку со списком валидных, как у прочих
перечислений. Симметрично валидации group у команд. Зеркало ps1+py.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 19:20:54 +03:00
Nick ShirokovandClaude Opus 4.8 5d877973a4 docs(meta-compile): терминология групп команд — «раздела/формы» вместо «секционные»
«Секционные» — выдуманный термин, в 1С его нет. Группы противопоставляются как
командный интерфейс РАЗДЕЛА (панель навигации/действий) и ФОРМЫ. Поправлены
reference/blocks.md, тексты ошибок и комментарии (ps1+py). Поведение не изменилось
(имена переменных внутренние; expectError-подстроки на месте).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 19:15:02 +03:00
Nick ShirokovandClaude Opus 4.8 9ad9c12c9b feat(meta-compile): группы команд — прощающий ввод, allowlist, валидация (v1.64)
Emit-Command: резолв группы (Resolve-CommandGroup) — русские подписи групп
(«Панель навигации.Важное» → NavigationPanelImportant и т.д.), `ГруппаКоманд.X`
→ `CommandGroup.X`. Закрыты два пробела валидации (найдены при 1С-cert):
- пустая/опущенная group → ошибка с подсказкой (список валидных групп), а не
  молчаливый незагружаемый <Group/>;
- секционная группа (NavigationPanel*/ActionsPanel*) + commandParameterType →
  ошибка (тип параметра доступен только для групп формы/CommandGroup — подтверждено
  выгрузкой ЕРП: команды с параметром только в Form*/CommandGroup).
Списки групп из реального erp_8.3.24. Зеркало ps1+py (идентично).

reference/blocks.md: каноничный список групп + правило параметра (без прощающего
ввода — usage-голос). Тесты: catalog-command-groups (рус-синонимы/секц/форма/кастом
через preRun CommandGroup, 1С-cert ✓) + 2 expectError (пустая group; секц+параметр).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 19:11:53 +03:00
Nick ShirokovandClaude Opus 4.8 fd21f5da79 feat(meta-compile,meta-decompile): dataLockControlMode дефолт Managed для авторинга (роундтрип цел)
Единый дефолт Managed вместо per-type Automatic/Managed — удобно для создания
новых объектов (Automatic задаётся явно). Роундтрип остаётся байт-в-байт:
дефолт компилятора и порог omit-on-default декомпилятора сдвинуты синхронно —
объекты с Automatic теперь несут ключ в DSL явно, с Managed — опускают,
итоговый XML с обеих сторон не меняется. Зеркало ps1+py в обоих навыках.
Заодно вылечена латентная рассинхронизация AccountingRegister/CalculationRegister
(компилятор Automatic vs порог Managed).

Верификация: роундтрип-инвариант на реальном корпусе 7/7 match, 0 диффов
(Automatic+Managed справочники, ПС, Sequence, РБ); полный набор тестов 514/514;
пересъём снэпшотов — 91 файл, дрейф ровно DataLockControlMode Automatic->Managed,
ноль посторонних; py-паритет.

meta-compile v1.63 / meta-decompile v0.54

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 15:11:25 +03:00
Nick ShirokovandClaude Opus 4.8 f3a9880d96 feat(meta-compile,meta-decompile): роундтрип TabularSection.Use + пустой FillValue v8:TypeDescription (v1.62/v0.53)
Два пре-существующих хвоста, вскрытых регистрочувствительным харнесом на 8.3.24.

A. Свойство Use табличной части (ForItem/ForFolder/ForFolderAndItem — иерархические
   Catalog/ПВХ). Компилятор хардкодил ForItem, декомпилятор не захватывал. Фикс:
   параметр tsUse в Emit-TabularSection (дефолт ForItem) + захват <Use> ТЧ + DSL-ключ
   `use` объектной формы ТЧ (omit при ForItem). Оба порта.

B. Пустой типизированный FillValue стандартного реквизита ValueType ПВХ:
   <xr:FillValue xsi:type="v8:TypeDescription"/> декомпилятор ловил как пустую строку →
   компилятор писал xs:string. Фикс — маркер fillValue:{typeDescription:true} по образцу
   emptyRef (SA-ридер захват + Emit-StandardAttribute эмиссия; только SA-уровень, на
   реквизитах не встречается). Оба порта.

Валидация: корпус 8.3.24 (1765) roundtrip 1765/1765 byte-exact TOTAL 0 (было 6 diff).
Декомпилятор ps1==py. Регресс 511/511 ps+py (правки аддитивные, снэпшоты не поехали).
spec §5 (use) + §4.2 (typeDescription).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 12:26:58 +03:00
Nick ShirokovandClaude Opus 4.8 706cc919a2 feat(meta-compile,meta-decompile): HE-эвристика аббревиатур в авто-синониме + регистрочувствительный авто-вывод (v1.61/v0.52)
Корень: split_camel_case слепо лоуэркейзил хвост, теряя аббревиатуры (НДС/ЕГАИС/ОС/ЭП),
которые платформа сохраняет. Вскрыто py-раундтрипом на маке (АбонентыЭДО: синоним
"Абоненты ЭДО" регенерился как "эдо"). Дважды замаскировано: декомпилятор опускал синоним
регистронезависимо (ne_ci), харнес Compare-Object был без -CaseSensitive. Масштаб survey: 7706.

Фикс (правило подтверждено эмпирически по корпусу): сохранять максимальный прогон заглавных
>=2, если сразу за ним НЕ буква (пробел/цифра/спецсимвол/конец). ">=2" ловит ОС/ЭП; граница
"не буква" отсекает предлоги (РасчетыСКлиентами->склиентами) и бренды (ЮКасса).
- Компилятор Split-CamelCase -> HE (ps1+py), радиус = авто-синонимы + авто-описания предопределённых.
- Декомпилятор Split-CamelWords -> HE (зеркало) + регистрочувствительная страховка (-ne->-cne /
  ne_ci->ne_cs) в 9 точках: кастом-синонимы (ВетИС/МИР) эмитятся дословно.

Валидация 8.3.24 (1765): синонимы/описания закрыты полностью, ноль case-диффов. Декомпилятор
ps1==py 189/189. Регресс 511/511 ps+py. spec §2. Снэпшоты meta-compile — казинг аббревиатур.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-10 20:09:36 +03:00