Skip to main content
Glama

1c-meta — создание и правка метаданных 1С в выгрузке конфигуратора и проекте EDT

Инструмент пишет файлы выгрузки так же, как их пишет конфигуратор, и не даёт нарушить правила, которые иначе всплывают только при загрузке в базу. Исходники в формате проекта 1C:EDT он читает и пишет так же, как их пишет EDT; формат определяется по каталогу сам (раздел «Проект EDT»). Внешние обработки и отчёты он создаёт и правит в их собственной выгрузке (раздел «Внешняя обработка и отчёт»).

Проверено двумя способами: сборкой обратно (побайтно) всех существующих объектов выгрузки крупной типовой конфигурации и кругом «записали → загрузили конфигуратором → выгрузили обратно», в котором платформа не меняет ни символа.

Лицензия — MIT (LICENSE); сторонние компоненты — THIRD_PARTY_NOTICES.md.

Установка

Нужны Python 3.10 или новее (проверено на 3.13) и git в PATH. Пакеты:

py -3 -m pip install "lxml>=5.0"     # всегда: разбор и сборка файлов выгрузки
py -3 -m pip install "mcp>=1.29"     # только для MCP-сервера (pydantic придёт с ним)
py -3 -m pip install pytest ruff     # только для проверок

На Linux и macOS вместо py -3 — python3. Инструмент запускается из каталога репозитория как есть; пакетом для pip install он не оформлен.

Для проекта 1C:EDT нужна ещё таблица метамодели EDT — порядок и умолчания свойств объектов и форм. В репозитории её нет: она снимается с моделей установленного 1C:EDT, на той машине, где инструмент работает:

py -3 tools/edt_model_from_xcore.py     # без аргумента — пул p2: %USERPROFILE%\.p2\pool\plugins

Получится meta/acl/edt_metamodel.py (в .gitignore). Без неё выгрузка конфигуратора работает полностью, у проекта EDT работают проверки и нормализация, а запись и показ отклоняются с этой же подсказкой.

Выгрузка — каталог, который пишет конфигуратор командой «Конфигурация → Выгрузить конфигурацию в файлы». Результат загружается обратно командой «Загрузить конфигурацию из файлов» — целиком, а не частично (раздел «Ограничения»).

Related MCP server: xbsl-mcp

Быстрый старт

Задание — файл JSON: куда писать (repo — каталог выгрузки, где лежит Configuration.xml) и что сделать. Для пробы годится пустая выгрузка: скопируйте meta/tests/эталоны/формы/Configuration.xml в пустой каталог и укажите его в repo. Примеры дальше ссылаются и на типовые объекты (Справочник.Организации и другие) — в пустой выгрузке их нет, и такие ссылки получат ТИП-НЕТ, пока объекты не заведены. Справочник с одним реквизитом:

{"repo": "C:/путь/к/выгрузке/cf",
 "objects": [
   {"вид": "Справочник",
    "поля": {"имя": "мой_Пример", "синоним": "Пример",
             "реквизиты": [
               {"имя": "Комментарий", "синоним": "Комментарий",
                "тип": {"вид": "Строка", "длина": 100}}]}}]}

Сохраните его в UTF-8 как задание.json и посмотрите, что будет сделано, — на диск при этом не пишется ничего:

py -3 meta/add.py задание.json
Выгрузка: C:/путь/к/выгрузке/cf
Справочник мой_Пример
   замечаний нет
   синоним: Пример
   реквизиты: Комментарий «Комментарий» (Строка(100))
   (ещё 50 полей не названы в задании — значения по умолчанию; …)

План:
   создать  C:\путь\к\выгрузке\cf\Catalogs\мой_Пример.xml (6150 Б)
   изменить C:\путь\к\выгрузке\cf\Configuration.xml (станет … Б, было … Б)

Просмотр: ничего не записано. Для записи повторите с --apply.

План тот — записываем:

py -3 meta/add.py задание.json --apply

То же задание принимает MCP-сервер: metadata_check — просмотр, metadata_apply — запись (раздел «MCP-сервер для агентов»).

Ограничения

  • Формат выгрузки — 2.21, то есть платформа 8.5. Выгрузку другого формата инструмент читает, но метаданные и формы в неё не пишет (раздел «Запуск»).

  • Загрузка обратно — только полная. Служебный ConfigDumpInfo.xml инструмент не правит: его пересобирает платформа при загрузке.

  • Заведено 15 видов объектов конфигурации из 43 (и внешние обработка с отчётом) и 15 видов элементов форм из 31; чего нет, перечислено в разделе «Чего нет».

  • Проект EDT поддерживается наравне с выгрузкой; оговорки — в разделе «Проект EDT».

  • Внешние обработка и отчёт — в своей выгрузке, без удаления и переименования; оговорки — в разделе «Внешняя обработка и отчёт».

Проект EDT

Инструмент работает с двумя форматами исходников. Какой перед ним, он определяет по корню: Configuration.xml — выгрузка конфигуратора, Configuration/Configuration.mdo — проект EDT. Корень проекта EDT — каталог src (в командном репозитории это может быть подкаталог, например BF\src): его и указывают в repo, в --проверить и в остальных командах. Ключей и полей заданий для EDT нет — задание одно и то же.

Чем проект EDT отличается от выгрузки и что из этого следует:

Выгрузка

Проект EDT

Карточка объекта

Catalogs/Имя.xml

Catalogs/Имя/Имя.mdo

Модуль

Catalogs/Имя/Ext/ObjectModule.bsl

Catalogs/Имя/ObjectModule.bsl

Модуль формы

…/Forms/Ф/Ext/Form/Module.bsl

…/Forms/Ф/Module.bsl

Права роли

Roles/Р/Ext/Rights.xml

Roles/Р/Rights.rights

Реестр объектов

ChildObjects в Configuration.xml

списки в Configuration.mdo

Форма, макет

отдельные карточки

строка в карточке хозяина + Form.form, Template.dcs

Служебный файл

ConfigDumpInfo.xml

нет

Формат файла

UTF-8 с BOM, CRLF

UTF-8 без BOM, LF

Адрес модуля в заданиях на код один и тот же в обоих форматах (Справочник.Банки.МодульОбъекта) — путь к файлу инструмент строит сам.

Что работает в проекте EDT:

  • проверка правок задачи (--проверить, verify): пути — от корня исходников, ревизии git читаются из подкаталога репозитория, права ищутся в Rights.rights. BOM и переводы строк сверяются с тем, как файлы лежат в репозитории, а не с форматом выгрузки: лишний BOM — находка ФАЙЛ-ЛИШНИЙ-BOM. Правило служебного файла выгрузки к проекту EDT не применимо и попадает в пояснения, а не в пропущенное — прогон остаётся полным;

  • нормализация формата (normalize_check) — без BOM, перевод строк файла;

  • сверка плана изменений (plan_check, plan_emit) — состав объектов читается из .mdo;

  • создание объектов всех 15 видов, реквизиты и вложенные сущности, правка и удаление свойств, удаление и переименование объектов, атомарные роли (metadata_apply);

  • правка кода вставками (code_apply) в модули без Ext;

  • формы (forms_apply): новая форма объекта и общая форма, правка существующей формы — элементы, реквизиты, колонки, команды, события, те же виды и свойства, что у выгрузки. Новая форма объекта — запись forms в карточке хозяина, Forms/<Форма>/Form.form и Module.bsl; у динамического списка — ещё Attributes/<реквизит>/ExtInfo/ListSettings.dcss;

  • показ объекта и формы (--показать, show), код доработки типовой формы («код»: true) — он читает Form.form, чтобы знать соседей и места вставки.

Чего нет в проекте EDT:

  • у формы — того же, чего нет у формы выгрузки (раздел «Чего нет»), и встроенных картинок (xr:Abs): EDT хранит их отдельным файлом формы. Новую такую картинку запись отклоняет; у существующих элементов картинки, цвета, шрифты, параметры выбора и всё, чего редактор не касается, едут нетронутыми. Настройки динамического списка (ListSettings.dcss) пишутся только новому реквизиту: файл существующего не перезаписывается;

  • свойства выгрузки, которые инструмент сам не пишет (стандартные реквизиты, параметры выбора, связи по типу, картинки, цвет и шрифт элемента стиля и т. п.), в формат EDT не перекладываются. В существующих объектах они не трогаются: карточка .mdo читается и пишется обратно как есть, меняется только то, что задано заданием. Если такое свойство пришлось бы переложить, запись получает отказ с перечнем, а не теряет его молча.

Как устроено. Карточка .mdo — объект EMF, и порядок, умолчания и вид каждого свойства задаёт метамодель EDT. Её таблица (meta/acl/edt_metamodel.py) снимается с моделей *.xcore из пакетов установленного EDT скриптом tools/edt_model_from_xcore.py при установке (раздел «Установка») — в репозитории её нет, это производное от моделей 1С. Чтение переводит .mdo в дерево карточки выгрузки (meta/acl/edt_card.py, Reverse), и дальше работают те же правила, что для выгрузки; запись переводит обратно (Translation). Чего перевод не понимает, проходит сквозь него нетронутым. Текст файла — meta/infra/edt_xml.py, площадка — meta/infra/edt_dump.py, раскладка каталогов — meta/infra/layout.py, форма — meta/infra/forms/edt.py.

Форма EDT — тоже объект EMF, но устроена иначе, чем Form.xml: элемент — обобщённый items с классом и видом в поле type, свойства своего вида — в extInfo, умолчания платформы (visible, enabled, userVisible, placementArea у кнопки…) записаны явно. Перекладка формы — meta/acl/edt_form.py: прямая (FormTranslation, Form.xml → Form.form) и обратная (ReverseForm). Правка существующей формы идёт по кругу: Form.form → Form.xml → тот же редактор, что у выгрузки → Form.form. Таблица умолчаний платформы (meta/acl/edt_form_defaults.py) снята с корпуса и с оракула скриптом tools/edt_form_check.py --умолчания. Элемент, круг которого по богатой перекладке не сходится, обратная перекладка отдаёт «скелетом»: имя, id, вид, путь к данным, дети и спутники — по-настоящему, остальное — переходником; правке редактором это не мешает.

Чем проверено:

  • таблица метамодели — на всех 23 375 карточках и 11 058 формах проекта крупной типовой конфигурации: ни одного свойства не по порядку (tools/edt_order_check.py; в тестах — при заданном META_EDT_CORPUS);

  • текст — разбор и запись тех же 34 433 файлов побайтно (tools/edt_text_roundtrip.py);

  • перевод — .mdo → дерево выгрузки → .mdo на всех 23 375 карточках побайтно (tools/edt_adapter_roundtrip.py);

  • запись — против самого EDT. tools/edt_oracle.py применяет задания meta/tests/эталоны/edt/задания/ писателем выгрузки, импортирует выгрузку командной строкой EDT (1cedtcli import) и кладёт полученный проект в эталоны/edt/ожидание. Тест применяет те же задания к проекту EDT и сверяет каждый файл побайтно (test_edt_write.py), в том числе задания на формы: новая форма, наполнение, правка существующей, список, дерево, виды элементов — эталоны/edt/ожидание-формы. Формы оракула, прочитанные из Form.form, сверяются с теми же формами из Form.xml;

  • круг формы — Form.form → дерево Form.xml → Form.form на всех 11 058 формах проекта побайтно (tools/edt_form_check.py --круг). Командная строка EDT на Windows пишет CRLF, а в репозитории файлы лежат с LF — оракул приводит эталон к LF, как это делает git.

Эталоны пересобираются так (нужны установленный 1C:EDT и JDK 17 из его комплекта):

py -3 tools/edt_oracle.py C:/временный/каталог

Внешняя обработка и отчёт

Внешняя обработка конфигуратором выгружается не в каталог конфигурации, а сама по себе: карточка — корень выгрузки, рядом каталог с тем же именем.

<каталог>/мой_Обработка.xml                     карточка (ExternalDataProcessor)
<каталог>/мой_Обработка/Ext/ObjectModule.bsl    модуль объекта
<каталог>/мой_Обработка/Forms/Форма.xml         формы — как у обработки
<каталог>/мой_Обработка/Forms/Форма/Ext/Form.xml
<каталог>/мой_Отчет/Templates/ОсновнаяСхема.xml макеты — так же

Выгрузка внешней обработки — это каталог без Configuration.xml: в repo указывают его (пустой — под новую обработку). В одном каталоге может лежать несколько обработок и отчётов. Формат текста тот же, что у выгрузки конфигурации: UTF-8 с BOM, CRLF, версия 2.21.

Что работает:

Операция

Как

создать обработку

metadata_apply, вид ВнешняяОбработка: синоним, реквизиты, табличные части, модульОбъекта — текст модуля

создать отчёт

вид ВнешнийОтчет: то же, плюс схема — макет со схемой компоновки и основная схема в свойствах

реквизит, табличная часть в существующую

attributes / additions по адресу ВнешняяОбработка.Имя

форма

forms_apply по адресу ВнешняяОбработка.Имя.Форма: создание (назначение «Обработки» / «Отчета» выводится само), наполнение, правка существующей; модуль формы с обработчиками

модуль

code_apply по адресу ВнешняяОбработка.Имя.МодульОбъекта или …Форма.Имя — или по пути

{"repo": "C:/work/ЗАДАЧА-1/Реализация",
 "objects": [{"вид": "ВнешняяОбработка", "поля": {
   "имя": "мой_Загрузка", "синоним": "Загрузка",
   "реквизиты": [{"имя": "Файл", "синоним": "Файл", "тип": "Строка(0)"}],
   "модульОбъекта": "#Область ПрограммныйИнтерфейс

#КонецОбласти
"}}]}

Чем внешний объект отличается от обычного, и откуда это известно — круг через конфигуратор 8.5.1 на пустой базе, а не корпус (внешних обработок в корпусе нет):

  • в служебном блоке первым стоит xr:ContainedObject — класс вида (постоянный) и свой идентификатор объекта;

  • порождаемый тип один — объект; менеджера у внешнего объекта нет;

  • свойств у обработки пять (имя, синоним, комментарий, две формы), у отчёта — свойства отчёта конфигурации без команд, справки и представлений;

  • табличная часть называется DataProcessorTabularSection… / ReportTabularSection… (не External… — так её выгружает конфигуратор, что бы ни было загружено) и всегда несёт блок стандартного реквизита «НомерСтроки»;

  • карточка формы несёт ещё ExtendedPresentation;

  • основная форма в свойствах — ExternalDataProcessor.Имя.Form.Форма.

Чего нет:

  • удаления и переименования — отказ: удаляют каталог обработки вместе с карточкой, переименовывают в конфигураторе;

  • общих форм и объектов конфигурации в такой выгрузке не бывает — отказ;

  • проверки прав (verify): внешний объект в роли не входит, проверять нечего;

  • сверки плана (plan_check) — как и раньше;

  • «модуль создан задачей» инструмент определяет по git: модуль, которого нет в базовой ревизии, — свой, меток в нём нет. Выгрузка внешней обработки обычно не под git, и тогда каждый существующий модуль — чужой: правка вставкой получает метку. Для обработки, созданной задачей с нуля, текст модуля задают сразу полем модульОбъекта — он пишется без меток.

Эталоны — от конфигуратора: tools/epf_oracle.py применяет задания meta/tests/эталоны/внешние/задания/ в пустой каталог, загружает каждую карточку в файл на пустой базе (/LoadExternalDataProcessorOrReportFromFiles), выгружает обратно (/DumpExternalDataProcessorOrReportToFiles) — выгрузка и есть эталон эталоны/внешние/ожидание. Тест test_external_write.py сверяет запись инструмента с ним побайтно. Оракул сам печатает расхождения записи с выгрузкой: всё, что конфигуратор дописал или переставил, — недоработка инструмента. Пересборка (нужна установленная платформа; база создаётся в рабочем каталоге, открытый конфигуратор не мешает):

py -3 tools/epf_oracle.py C:/временный/каталог

Что умеет

Операция

Виды

Создать объект

15 видов: справочник, документ, перечисление, константа, определяемый тип, регистр сведений, регистр накопления, отчёт, обработка, роль, подсистема, общий модуль, общая команда, подписка на событие, регламентное задание; в своей выгрузке — внешние обработка и отчёт

Создать объект сразу с содержимым

реквизиты; табличные части с их реквизитами; значения перечисления; измерения и ресурсы регистра; права роли; макет со схемой компоновки у отчёта — одной операцией, одним планом

Добавить вложенную сущность по адресу хозяина

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

Изменить значение свойства

у объекта и у любой вложенной сущности, включая пункты настроек компоновки, по адресу

Удалить

объект целиком (карточка, спутники, запись в реестре) или вложенную сущность — в том числе внутри схемы компоновки

Переименовать объект

вместе со всеми ссылками на него

Завести атомарные роли

пара «Просмотр»/«Изменение» по схеме имён команды из профиля — к новому объекту тем же заданием или к существующему

Завести или дополнить управляемую форму

новая форма любого назначения мастера (документа, элемента, списка, записи, обработки…) или существующая: реквизиты формы (в том числе колонки таблиц значений), команды, элементы пятнадцати видов с родителем и местом, свойства по русским именам платформы, обработчики, события формы, удаление; модуль — заготовки обработчиков

Доработать типовую форму

то же описание -> фрагмент кода доработки формы для вызова из ПриСозданииНаСервере — методами платформы или языком команды, подключённым файлом-диалектом из профиля: XML типовой формы инструмент править отказывается

Править код BSL вставками

тип метки выводится из правки, метки с датой, задачей и автором, комментирование заменяемого кода, отступы — инструментом; откат вставок задачи, переименование метода в модуле, перенос метода между модулями; модуль — адресом (таблица адресов — в разделе «Правка кода вставками»)

Проверить правки задачи

файлы, которые задача добавила и изменила против базы — под git или парой каталогов «эталон — копия»: метки вставок тем же разбором, что у записи, приставка новых объектов, BOM и переводы строк, правки только пробелами, права на новые объекты; находки с кодом, файлом и строкой, текстом и JSON

Вернуть файлам задачи формат выгрузки

BOM, переводы строк как в репозитории (у пары — как у эталона), хвостовые пробелы неизменённых строк из базы, отступ пустых строк в методах, завершающий перевод строки как в базе; просмотр и запись, идемпотентно

Сверить план изменений с фактом

план JSON-ом — объекты задачи, их новые члены с типами, методы модулей — против правок задачи: заявлено, но не сделано; сделано, но не заявлено; типы не совпали; и план по факту в том же формате

Удаление отказывает, если на объект ссылаются, и называет место каждой ссылки — адрес до реквизита и свойство. Ссылки ищутся по всей выгрузке одним обозначением: типы реквизитов, источники подписок и обработчики, движения документов, состав подсистем (включая подчинённые), права ролей, ссылки реестра конфигурации вне раздела детей и типы в схемах компоновки данных.

Формы, код BSL и остальные макеты не проверяются: на выгрузке крупной типовой конфигурации 23 443 файла форм читаются две минуты, макетов там 14 447 на 3,4 ГБ (из них схем компоновки 926 на 86 МБ, и они как раз проверяются), а модули при поиске ссылок не просматриваются. Об этом инструмент говорит при каждом удалении, а не молчит. ConfigDumpInfo.xml не смотрится намеренно: там названы все объекты подряд, платформа пересобирает его при загрузке.

Переименование — отдельная операция, а не правка поля «имя»: имя живёт в четырёх местах сразу — в собственной карточке (включая имена порождаемых типов и ввод по строке), в имени файла и каталога-спутника, в записи реестра и в каждой чужой карточке со ссылкой. Меняются все четыре разом; формы, код BSL и прочие макеты не переписываются, и об этом сказано.

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

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

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

Состав свойств зависит от того, где сущность живёт, и это тоже замерено, а не угадано: у реквизита табличной части 26 свойств против 29 у реквизита справочника — нет «использования» и обоих свойств заполнения; ресурс регистра отличается от измерения ровно четырьмя свойствами. Про незамеренное сочетание инструмент говорит вслух, а не пишет наугад.

Запуск

py -3 meta/add.py <задание.json> [--apply] [--профиль <профиль.json>]
py -3 meta/add.py --поля [Вид]

Без --apply — просмотр: печатаются находки инвариантов, разобранные типы и план изменений, на диск не пишется ничего.

repo — куда пишем. Проверьте путь перед запуском: задания копируют из примеров, а пишется туда, что написано в задании.

Правка сохраняет переводы строк файла: конфигуратор пишет CRLF, а карточки выгрузки могут лежать с LF, и навязывать свои значило бы переписывать файл целиком — diff во весь файл вместо одной строки. Новый файл пишется переводами площадки.

Формат выгрузки — 2.21 (так выгружает конфигуратор 8.5). Версию инструмент читает из корня Configuration.xml. Выгрузку другого формата (у платформы 8.3.27 это 2.20) он читает, а писать в неё отказывается: платформа такие карточки не загрузит («Неизвестная версия формата 2.21 загружаемого файла»). Метаданные и формы получают отказ до записи, на диске не меняется ни байта. Правка кода вставками от формата не зависит и идёт в любую выгрузку.

Профиль соглашений команды

Правила живут в инструменте и названы кодами находок (МД-ПРЕФИКС, ФОРМА-ТИПОВАЯ-ТОЛЬКО-КОДОМ, ПОДПИСКА-НА-СВОЙ-ОБЪЕКТ…). А то, что у команд разное, — приставка доработок, метка команды в метках вставок, имена атомарных ролей, язык кода доработки формы и насколько строго держится каждое правило, — задаёт один файл, профиль соглашений:

{
  "приставка": "мой_",
  "метки": {"тег": "#TEAM"},
  "атомарныеРоли": {"имя": "мой_атом_{вид}_{объект}_{право}",
                    "синоним": "(Мой) Атом. {вид} - {синоним} - {право}"},
  "кодФормы": {"язык": "платформа"},
  "правила": {"МОДУЛЬ-КОНТЕКСТ-НЕ-ПО-СТАНДАРТУ": "ошибка", "МД-БЕЗ-СИНОНИМА": "выкл"}
}

приставка — с чего начинаются имена всего, что команда добавляет сама: по ней новое отличается от вендорского, а своя форма — от типовой. Списком — основная первой (её ставят новым именам и называют в находках), остальные тоже свои: старые приставки команды, тестовые модули (["мой_", "ОМ_мой_"]). метки.тег — метка команды, которую открывающая метка вставки ставит перед автором: // {[+](фрагмент ДОБАВЛЕН), 06.10.2026, #TEAM Автор #Задача; без неё метка пишется …, 06.10.2026, Автор #Задача. Разбор меток от профиля не зависит: метку любой команды он узнаёт, а ИД задачи берёт из последнего «#». метки.ставить — false, если команда меток вставок не ставит вовсе: правка кода пишется на место старого кода (замена — без закомментированной копии, удаление — удаляет, новый метод — как есть; история — в git), а проверка правок не держит правила меток (ВСТАВКА-*, ПРАВКА-ВНЕ-ВСТАВКИ, ПРАВКА-В-ЧУЖОЙ-ВСТАВКЕ). Метки прошлых задач в модулях не трогаются. По умолчанию — true. атомарныеРоли — как команда называет атомарные роли (раздел «Атомарные роли»); без схемы инструмент таких ролей не заводит. кодФормы — на каком языке пишется код доработки типовой формы (раздел «Формы»): «платформа» — встроена и по умолчанию; свой язык команды — файлом-диалектом с его настройками (там же). правила — код находки и уровень: «ошибка» (писать нельзя), «предупреждение» (можно, но посмотреть) или «выкл». Неизвестный ключ профиля, код не по форме кода находки и неверный уровень — отказ. Существование кода не проверяется: правило с кодом, которого у инструмента нет, ничего не меняет, поэтому код берут из ответа инструмента или из этого README.

Профиль подключается переменной окружения META_PROFILE (путь к JSON) — у сервера MCP и у терминала; терминалу можно и ключом --профиль, он важнее. Сервер называет соглашения в своей инструкции, и агент знает приставку и метку команды до первого задания. Без профиля соглашения нейтральные: приставки и метки команды нет, и правила, которым нужна приставка, не применяются — инструмент остаётся писателем выгрузки, не навязывая соглашений одной команды другой.

Имена операций — английские (objects, attributes, additions, changes, deletions, renames, atomic_roles), как и repo; поля предметной области — русские (имя, поля, путь, принято).

MCP-сервер для агентов

Второй канал — тот же инструмент для агентов, у которых нет оболочки. Сервер подключается в настройках MCP-клиента; у Claude Code — файл .mcp.json проекта, ключ mcpServers:

"1c-meta": {"type": "stdio", "command": "py",
            "args": ["-3", "C:\\путь\\к\\1c-meta\\meta\\mcp_server.py"],
            "env": {"META_ROOTS": "C:\\путь\\к\\проекту1\\cf;C:\\путь\\к\\проекту2\\cf",
                    "META_PROFILE": "C:\\путь\\к\\профиль.json"}}

py -3 — запуск Python на Windows; на Linux и macOS — "command": "python3" и путь к mcp_server.py без -3. Разделитель путей в META_ROOTS — «;».

Инструмент

Что делает

show(repo, address, verbose, element)

что лежит в выгрузке: карточка объекта (у роли — с правами, у объекта — перечень форм с пометкой основных) или форма деревом элементов (у групп — группировка; element — только ветка элемента с путём к ней); типы — той же краткой записью, что принимает задание

fields(kind)

поля вида; без вида — операции и виды с примером пункта (формы — не операция metadata_*, обзор отсылает к forms_*); Тип — как пишется тип; Код — правила правки кода вставками целиком; вид элемента формы — его свойства

forms_check(repo, forms, accepted, verbose)

просмотр задания на формы: находки и план, без записи; verbose — и XML формы, который будет записан

forms_apply(...)

то же с записью

metadata_check(repo, objects, attributes, additions, changes, deletions, renames, atomic_roles, accepted, verbose)

просмотр задания на объекты и их части — одна операция; verbose — все поля и XML, который будет записан (новые файлы целиком, изменённые — разницей)

metadata_apply(...)

то же с записью

code_check(repo, task, date, author, modules, move_method, verbose)

просмотр правки кода вставками: решение по каждой правке — с местом (метод или область) — и текст, который встанет на её место, с номерами строк нового модуля и строкой-соседом сверху и снизу; без записи

code_apply(..., verbose)

то же с записью; модули пишутся все разом или ни один; verbose — записанный текст, а не только номера строк

verify(repo, base, rev, baseline, target, task, only)

проверка правок задачи против базы — только чтение; раздел «Проверка правок задачи»

normalize_check(repo, base, baseline, target)

что нормализация вернёт файлам задачи — без записи

normalize_apply(...)

то же с записью: все файлы разом или ни один

plan_check(objects, repo, base, rev, baseline, target)

сверка плана изменений с фактом задачи — только чтение; раздел «Сверка плана изменений»

plan_emit(repo, base, rev, baseline, target)

план по факту задачи — JSON того же формата

Описание инструмента не длиннее 2000 знаков: клиент обрезает его на 2048, и хвост длинного описания агент просто не видит. Описание — суть; полные правила правки кода — справкой fields(kind="Код") (у терминала --поля Код). Тест следит за длиной всех описаний и инструкции сервера. У verbose и accepted в схеме — свои описания: смысл подробного показа у каждого инструмента свой, а формат кода в accepted иначе пришлось бы угадывать.

Задание, которое не выполнится, начинается строкой «ОТКАЗ: …» — и отказ языка заданий, и ошибки в находках («ОТКАЗ: ошибок 1, предупреждений 3 — задание не пройдёт; находки ниже»). Признак стоит первой строкой, поэтому любой отказ находится поиском по «ОТКАЗ:», а не дочитыванием ответа до конца.

Задания описаны типами (meta/api/models.py): схема инструмента строится из них, и формат задания агент получает от самого сервера. У задания на объекты типами описаны операции и их пункты, а поля самого объекта — словарь: их сотни, и состав по виду отдаёт fields(kind), включая атомарныеРоли. Схема объявляет лишние ключи недопустимыми, как и язык заданий. Лишний ключ пункта — отказ языка заданий; лишний параметр инструмента (FastMCP сам по себе отбрасывает его молча — например, «принято» вместо accepted) и неверная форма запроса (объект вместо списка) — отказ «ОТКАЗ: …» по-русски с перечнем допустимого, а не тишина и не текст валидатора по-английски.

Ключи задания на код в схеме MCP — латиницей: модуль называется module, как соседи path, edits, line; русское «модуль» понимается тоже, оба сразу — отказ. Язык заданий терминала зовёт его «модуль», переводит контроллер.

Тип значения пишется краткой записью — так его печатает show, и её принимают оба задания: Строка(50), Строка(9, фиксированная), Число(15,2), Число(10,0, неотрицательное), Дата (только дата), ДатаВремя, Время, Булево, Справочник.Контрагенты (ссылка), Документ.Заявка (Объект), Справочник.*, ОпределяемыйТип.Имя, Характеристика.ИмяПВХ, ЛюбаяСсылка; составной — через «|». Объектная запись ({"вид": "Строка", "длина": 50}) принимается тоже. Показ и задание говорят одной записью, поэтому тип, скопированный из show, задание принимает как есть; набор типов (TypeSet) показывается своим именем — ОпределяемыйТип.Имя, а не «Произвольным» (на выгрузке крупной типовой конфигурации таких реквизитов в справочниках и документах 1597). Тест по замеренной выгрузке разбирает обратно каждый из 48 526 реквизитов справочников и документов.

Адрес формы пишется любым из двух способов — Документ.X.ФормаДокумента или адресом её модуля Документ.X.Форма.ФормаДокумента (таблица адресов — в разделе «Правка кода вставками»), общая — Общая.Имя или ОбщаяФорма.Имя, — и принимается всеми инструментами.

Ответы — простым текстом, без обёртки {"result": …}: в ней переводы строк и табуляции показа доходят до агента экранированными.

Git сервер запускает с закрытым stdin и таймаутом: унаследовав канал сообщений, git на Windows ждёт следующего сообщения, и сервер встаёт целиком. Тест на это идёт через настоящий stdio.

Чего у терминала нет: белый список — сервер работает только с выгрузками из META_ROOTS (через «;»), и на чтение, и на запись; без настройки отказ получает любой инструмент с repo, работает только справка fields. Отказ возвращается текстом «ОТКАЗ: …», а не ошибкой вызова: агент читает его и исправляет задание сам. Ответы — тот же текст, что в терминале, но подсказки в них — словами канала: терминал говорит «повторите с --apply», сервер — «вызовите forms_apply с тем же заданием».

Какие бывают поля

Наизусть их не помнят, и в документации им не место — разъедутся. Справку печатает сам инструмент по своим же описаниям:

py -3 ...\add.py --поля                 # виды и что у них бывает внутри
py -3 ...\add.py --поля ТабличнаяЧасть  # поля, допустимые значения, умолчания
py -3 ...\add.py --поля ПолеФлажка      # свойства элемента формы
ТабличнаяЧасть: полей 8, обязательные — имя

   имя
   синоним                          по умолчанию: ''
   комментарий                      по умолчанию: ''
   подсказка                        по умолчанию: ''
   проверкаЗаполнения               допустимо: НеПроверять, ВыдаватьОшибку; по умолчанию: 'НеПроверять'
   использование                    допустимо: ДляЭлемента, ДляГруппы, ДляГруппыИЭлемента; по умолчанию: 'ДляЭлемента'
   длинаНомераСтроки                по умолчанию: '5'
   реквизиты                        список: Реквизит — полями напрямую, без "вид"/"поля"

Объект сразу с содержимым

Дети пишутся списком своих полей — без обёртки "вид"/"поля", которая есть у объекта, — на любой глубине: у справочника есть табличные части, у них — свои реквизиты.

{
  "repo": "C:/путь/к/выгрузке/cf",
  "objects": [
    {"вид": "Справочник",
     "поля": {"имя": "мой_ЛимитыПодразделений",
              "синоним": "Лимиты подразделений",
              "реквизиты": [
                {"имя": "Подразделение", "синоним": "Подразделение",
                 "тип": {"вид": "Справочник", "имя": "Организации"}}],
              "табличныеЧасти": [
                {"имя": "Лимиты", "синоним": "Лимиты",
                 "реквизиты": [
                   {"имя": "Сумма", "синоним": "Сумма",
                    "тип": {"вид": "Число", "разрядность": 15, "дробнаяЧасть": 2}}]}]}}
  ]
}

Типы: примитив — {"вид": "Строка", "длина": 20}, {"вид": "Число", "разрядность": 15, "дробнаяЧасть": 2}, {"вид": "Дата", "состав": "Дата"}; ссылка — {"вид": "Справочник", "имя": "Организации"}, грань «Ссылка» подразумевается. Неизвестный ключ внутри тип — отказ: молчаливое умолчание здесь незаметно и в задании, и в результате.

Регистр сведений описывается так же — измерения, ресурсы и реквизиты сразу:

{
  "repo": "C:/путь/к/выгрузке/cf",
  "objects": [
    {"вид": "РегистрСведений",
     "поля": {"имя": "мой_ЛимитыПоПодразделениям",
              "синоним": "Лимиты по подразделениям",
              "измерения": [
                {"имя": "Подразделение", "синоним": "Подразделение",
                 "тип": {"вид": "Справочник", "имя": "Организации"}}],
              "ресурсы": [
                {"имя": "Лимит", "синоним": "Лимит",
                 "тип": {"вид": "Число", "разрядность": 15, "дробнаяЧасть": 2}}],
              "реквизиты": [
                {"имя": "Комментарий", "синоним": "Комментарий",
                 "тип": {"вид": "Строка", "длина": 200}}]}}
  ]
}

Порядок ключей в задании ни на что не влияет: внутри карточки дети лягут в том порядке, которого требует платформа (у регистра сведений это ресурсы, затем реквизиты, затем измерения — вопреки тому, как показывает дерево конфигуратор).

Документ с движениями — поле движения, список регистров строкой «Вид.Имя» или объектом, как тип:

{"вид": "Документ",
 "поля": {"имя": "мой_ЗаявкаНаОплату", "синоним": "Заявка на оплату",
          "движения": ["РегистрНакопления.мой_ЛимитыОплат",
                       {"вид": "РегистрСведений", "имя": "мой_ИсторияЗаявок"}]}}

Что проверяется — по замеру всех 634 документов крупной типовой конфигурации (4191 движение): в движениях бывают только регистры (не-регистров — ни одного), только существующие, а регистр сведений — только подчинённый регистратору (986 из 986; независимый конфигуратор и не предложит). Движения при запрещённом проведении — предупреждение, не отказ: так у 7 документов из 383, все они «операции». Регистр заводится предыдущим заданием: объекты одного задания видят друг друга только по факту существования, а режим записи регистра из той же пачки прочитать неоткуда.

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

Формы

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

{"repo": "C:/путь/к/выгрузке/cf",
 "forms": [{
   "форма": "Документ.мой_Заявка.ФормаДокумента",
   "создать": {"основная": true, "синоним": "Заявка"},
   "реквизиты": [
     {"имя": "мой_Флаг", "тип": "Булево"},
     {"имя": "мой_Строки", "тип": "ТаблицаЗначений",
      "колонки": [{"имя": "Статья", "тип": "Строка(100)"}, {"имя": "Сумма", "тип": "Число(15,2)"}]},
     {"имя": "Примечание", "тип": "Строка(50)", "таблица": "мой_Строки"}],
   "команды": [{"имя": "мой_Заполнить", "заголовок": "Заполнить"}],
   "элементы": [
     {"вид": "ГруппаФормы", "имя": "мой_ГруппаШапка",
      "свойства": {"Группировка": "Горизонтальная", "ОтображатьЗаголовок": false},
      "элементы": [{"вид": "ПолеВвода", "имя": "Номер", "путь": "Номер"},
                   {"вид": "ПолеВвода", "имя": "Дата", "путь": "Дата"}]},
     {"вид": "ПолеВвода", "имя": "Комментарий", "путь": "Комментарий",
      "после": "мой_ГруппаШапка", "обработчики": {"ПриИзменении": true}},
     {"вид": "ПолеФлажка", "имя": "мой_Флаг", "путь": "мой_Флаг"},
     {"вид": "Страницы", "имя": "мой_Страницы", "элементы": [
        {"вид": "Страница", "имя": "мой_СтраницаСтроки", "свойства": {"Заголовок": "Строки"},
         "элементы": [{"вид": "ТаблицаФормы", "имя": "мой_Строки", "путь": "мой_Строки",
                       "колонки": [{"имя": "Статья"}, {"имя": "Сумма"}]}]}]},
     {"вид": "Кнопка", "имя": "ФормаЗаполнить", "команда": "мой_Заполнить",
      "родитель": "ФормаКоманднаяПанель"}],
   "события": {"ПриСозданииНаСервере": true}
 }]}

Правила:

  • форма — Вид.Имя.ИмяФормы или Общая.ИмяФормы. Без создать форма должна существовать; с создать — не должна. Назначение выводится из имени формы, как это делает мастер (ФормаДокумента — форма документа, ФормаСписка — списка), или задаётся: "назначение": "Документа". основная пишет ссылку в карточку хозяина.

  • вид элемента — ПолеВвода, ПолеФлажка, ПолеНадписи, ПолеКартинки, ПолеПереключателя, Надпись, Картинка, ГруппаФормы, Страницы, Страница, ГруппаКолонок, КоманднаяПанель, ГруппаКнопок, Кнопка, ТаблицаФормы. Дети — вложенным элементы или полем родитель; родитель не указан — корень формы. перед/после — имя соседа; ни то ни другое — в конец. В автоматическую панель формы кнопка кладётся родителем ФормаКоманднаяПанель, в панель таблицы — <Таблица>КоманднаяПанель — и тем же заданием, которым форма или таблица заводится: обе панели рождаются вместе с хозяином, и родителем они годятся сразу, как в примере выше. Родитель, которого нет в форме, — отказ ФОРМА-РОДИТЕЛЬ-НЕТ.

  • путь — путь к данным: реквизит формы голым именем, реквизит хозяина — Объект.Имя или просто Имя; стандартный реквизит переводится так, как пишет конфигуратор (Номер → Объект.Number: так во всех 594 формах документов корпуса). Колонки таблицы получают путь от таблицы.

  • колонки таблицы — имя колонки это поле строки (Статья), имя элемента инструмент собирает сам: таблица плюс поле (мой_СтрокиСтатья). Полное имя элемента вместо поля — предупреждение ФОРМА-КОЛОНКА-ИМЯ: иначе вышло бы двойное имя (мой_Строкимой_СтрокиСтатья).

  • свойства — русские имена платформы, как в синтакс-помощнике (Ширина, ТолькоПросмотр, ПоложениеЗаголовка, Группировка); значения перечислений — русские слова. Словарь на 997 свойств 33 видов — meta/domain/form_properties.py, снят со справочника проекта «Накидка» (crimsongoldteam/md_design, MIT). Свойства со своей структурой, выражаются двумя: картинка — «БиблиотекаКартинок.Имя» или «СтандартнаяКартинка.Имя», список выбора — массив {"значение": …, "представление": …} (число, строка, булево или «Перечисление.Вид.Значение»). Остальные (шрифт, цвет, рамка, сочетание клавиш) — отказ, а не текст наугад. Свойство, которого корпус у этого вида не показывал, тоже отказ: поставить его некуда, а молча терять сказанное инструмент не должен.

  • Свойства, которые пишутся сами (что у элемента «рождается»), взяты с эталона конфигуратора: форма мой_Эталон в проверочной пустой конфигурации — мастер плюс по элементу каждого вида, добавленному руками без настройки. Корпус для этого негоден: формы там прожили годы правок, и «что настроили» от «что родилось» по ним не отличить. По корпусу вышло бы, что у флажка рождается положение заголовка, у группы — поведение, отображение и показ заголовка, у страниц — представление, у таблицы — режим выделения; по эталону ничего этого нет. Зато эталон показывает то, чего корпус не показывает: признак расширенного редактирования у поля, ShowCommandBar у таблицы, подсказку тем же текстом, что и заголовок, у групп и панелей, а у новой формы — пустой корень без WindowOpeningMode и Group.

  • обработчики: {"ПриИзменении": true} — имя по правилу конфигуратора «ЭлементСобытие» (так названы 15 770 из 18 519 обработчиков ПриИзменении корпуса), строка — своё имя. Команда без обработчик зовёт процедуру своим именем (48 259 команд корпуса из 54 139).

  • Модуль. У новой формы и у формы без модуля он создаётся с заготовками по областям стандарта и сигнатурами, замеренными по 10 239 модулям корпуса. В существующий модуль форма их не пишет: процедуры туда кладёт задание на код (code_apply) с метками, а инструмент печатает, что положить.

  • Тип реквизита — Строка(20), Число(15,2), Дата/ДатаВремя/Время, Булево, Справочник.Имя, ТаблицаЗначений; объектная запись тоже принимается.

  • удалить — имена элементов формы, которые убрать: "удалить": ["мой_СтараяГруппа"]; реквизиты и команды — удалитьРеквизиты и удалитьКоманды. Того, чего в форме нет, — отказ ФОРМА-УДАЛИТЬ-НЕТ.

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

Что доказано кругом через платформу на проверочной пустой конфигурации (платформа не меняет ни одной строки): рождение формы документа с регистрацией у хозяина; её наполнение — реквизиты формы, колонки таблицы значений, команда, группа, поля, флажок, страницы, таблица с колонками, кнопки обычная и в автопанели, событие формы, модуль; новая форма с таблицей табличной части одним заданием; правка наполненной формы — вставка перед и после, колонка в существующий реквизит-таблицу, командная панель с кнопками своей и стандартной команды, поле-надпись, удаление элемента. Круги закреплены тестами test_forms.py побайтно.

Устройство: домен (domain/forms.py) не знает ни тегов, ни версии; сценарий (application/edit_form) просит порт read_form/prepare_form_edits; реализация формата — infra/forms/designer221 за договором infra/forms/format.py, выбирается реестром infra/forms/registry.py по version="2.21" самого файла. Другая версия формата — новый пакет рядом и строка в реестре; тест стережёт, что реализацию импортирует только реестр.

Типовая форма в XML не правится. Типовые формы меняются только программно: XML типовой формы — вендорский, и правка в нём сталкивается с каждым обновлением формы вендором, а код доработки рядом с ней живёт отдельно. Правило держит сам инструмент, а не внимательность человека: форма, у которой ни объект, ни она сама не названы с приставкой доработок, даёт отказ ФОРМА-ТИПОВАЯ-ТОЛЬКО-КОДОМ с подсказкой. Своя форма в вендорском объекте (форма с приставкой доработок у объекта без неё) — своя, её XML править можно: это законный и нередкий приём. Без приставки в профиле своё от типового не отличить, и правило не применяется.

Для типовой формы то же описание даёт код доработки формы — ключ "код": true у формы. На каком языке он пишется — соглашение команды (профиль, ключ кодФормы). По умолчанию — методы платформы, которые есть в любой конфигурации (сверено с синтакс-помощником 8.5.1):

ДобавляемыеРеквизиты = Новый Массив;
ДобавляемыеРеквизиты.Добавить(Новый РеквизитФормы("мой_Состояние", Новый ОписаниеТипов("Строка", , Новый КвалификаторыСтроки(20))));
ДобавляемыеРеквизиты.Добавить(Новый РеквизитФормы("мой_Строки", Новый ОписаниеТипов("ТаблицаЗначений")));
ДобавляемыеРеквизиты.Добавить(Новый РеквизитФормы("Номенклатура", Новый ОписаниеТипов("СправочникСсылка.Номенклатура"), "мой_Строки"));
Форма.ИзменитьРеквизиты(ДобавляемыеРеквизиты);

Элемент = Форма.Элементы.Вставить("мой_Состояние", Тип("ПолеФормы"), Форма.Элементы.ГруппаШапка, Форма.Элементы.Комментарий);
Элемент.Вид = ВидПоляФормы.ПолеВвода;
Элемент.ПутьКДанным = "мой_Состояние";
Элемент.ТолькоПросмотр = Истина;
Элемент.УстановитьДействие("ПриИзменении", "Подключаемый_мой_СостояниеПриИзменении");

Реквизиты и колонки таблиц заводятся одним ИзменитьРеквизиты (операция ресурсоёмкая, платформа просит делать её пакетом), команда — Команды.Добавить с заголовком и действием, элемент — Вставить перед соседом или Добавить в конец родителя; вид поля, группы, декорации и кнопки ставится явно. Составной тип реквизита и заголовок реквизита платформа выражает: Новый ОписаниеТипов("Строка,Булево", , Новый КвалификаторыСтроки(20)), заголовок — четвёртым параметром РеквизитФормы.

Свой язык кода — например, построитель форм команды — подключается файлом-диалектом из профиля:

"кодФормы": {"язык": "сборщик", "диалект": "сборщик.py", "модуль": "мой_Формы"}

диалект — путь к файлу Python от файла профиля, прочие ключи — настройки языка, их проверяет сам диалект. Файл объявляет ДИАЛЕКТ — наследника meta.domain.form_dialect.ДиалектКодаФорм с тем же имя, что язык в профиле, — и переопределяет то, что у его языка иначе: текст фрагмента (код); как узнать в модуле процедуру доработки и что в неё дописать (процедура, дополнение); как назвать место вставки и «перед соседом» в пояснениях; какими вызовами язык заводит элементы, реквизиты и команды — по ним узнаётся повтор; находки о том, чего язык не выражает; слова для ответа и инструкции сервера. Образец с выдуманным языком — meta/tests/диалекты/сборщик.py. Диалект — код команды, и исполняется он как код инструмента; нет файла, он не загрузился или его язык не тот, что назван в профиле, — отказ с причиной, а не тихий переход на язык платформы.

Фрагмент печатается в просмотре готовой процедурой для своего общего модуля (с описанием, табуляциями и вызовом для ПриСозданииНаСервере формы), а с --apply ещё и ложится файлом рядом с заданием (или туда, где сказано ключом файлКода); вносится он заданием на код этого же инструмента — с метками. Запись (forms_apply) в режиме кода ничего не пишет и так и говорит. Обработчики элементов и действия команд получают приставку Подключаемый_ (#std492 — номер стандарта из системы стандартов разработки 1С: и то и другое код назначает программно), и подсказка перечисляет их процедуры с сигнатурами для модуля формы. Тем же именем обработчик назван и в показе задания. В готовых правках вызов дан без отступа — уровень места ставит инструмент, это сказано в заголовке блока. Многострочная строка — с «|». Кодом нельзя того, что не выражается программно, и инструмент говорит об этом отказом: создать форму, назначить событие самой формы, удалить элемент типовой формы (его прячут свойством «Видимость»), назначить кнопке стандартную команду. «После» соседа переводится в «перед» следующим, родитель — тот, где стоит сосед, и ответ это объясняет словами: сосед взят по форме в выгрузке, и элементы, которые добавляет код других доработок, в расчёт не входят.

Куда ляжет процедура — "код" объектом: {"модуль": "ОбщийМодуль.мой_ЗаявкиСервер", "процедура": "…"}. Инструмент проверяет, что модуль есть (ФОРМА-КОД-МОДУЛЬ), серверный (ФОРМА-КОД-МОДУЛЬ-НЕ-СЕРВЕР — процедуру зовут из ПриСозданииНаСервере), не клиентский (ФОРМА-КОД-МОДУЛЬ-КЛИЕНТ — элементы и реквизиты формы добавляются только на сервере) и не вызова сервера (ФОРМА-КОД-МОДУЛЬ-ВЫЗОВ-СЕРВЕРА — его экспорт виден клиенту, а форму на сервер не передать; поэтому одного признака «Сервер» мало), и свой (ФОРМА-КОД-МОДУЛЬ-ТИПОВОЙ — предупреждение: реализация доработки живёт в своём модуле, в типовом остаётся вызов), а имя в нём свободно или занято процедурой, которую можно дополнить (ФОРМА-КОД-ИМЯ-ЗАНЯТО; по умолчанию <Форма>_ПриСозданииНаСервере), и выдаёт готовый вызов вместо заглушки <Модуль>. Без модуля — предупреждение ФОРМА-КОД-БЕЗ-МОДУЛЯ, с подсказкой, если у объекта есть свой модуль «приставка + имя объекта»: искать модуль, его контекст и свободное имя руками не приходится. Для процедур-обработчиков ответ называет область модуля формы со строками — или, если её нет, между какими областями её завести по шаблону стандарта #std455. В описании процедуры нет длинного тире: BSL Language Server считает его ошибкой; и нет кавычек-ёлочек — в коде кавычки прямые, и фрагмент, внесённый дословно, иначе не прошёл бы проверку кода. Ёлочки из заголовка задания — предупреждение ФОРМА-КОД-ЁЛОЧКИ уже в просмотре формы.

С названным модулем ответ даёт и места номерами строк: процедура — перед #КонецОбласти области «ПрограммныйИнтерфейс» модуля, вызов — перед КонецПроцедуры у ПриСозданииНаСервере модуля формы. Когда известны оба места, ответ даёт и готовые правки задания на код — блок modules с процедурой, вызовом, номерами строк и текстами якорей; остаются задача, дата и автор: двадцать строк процедуры, перенесённые руками, легко исказить. Ключ модуля — слово канала (module у MCP, модуль у терминала). Элемент, реквизит или команда, которые уже заводит кодом целевой модуль или сам модуль формы, — ошибка ФОРМА-КОД-УЖЕ-В-МОДУЛЕ: повтор доработки под другим именем процедуры завёл бы второй элемент с тем же именем, а модуль типовой формы порой сам заводит элемент с таким именем — так модуль формы авансового отчёта заводит свою надпись итога. «Заводит» — вызов, который создаёт в своём пространстве имён: элемент — Элементы.Добавить/Вставить, реквизит — Новый РеквизитФормы, команда — Команды.Добавить, и заводящие вызовы языка команды, которые называет его диалект; просто имя в кавычках заведённым не считается — ОписаниеОповещения("мой_…") называет процедуру, а не элемент. Описание процедуры — «Дорабатывает форму "…".» (диалект может дописать, чем), без перечня элементов: дополнение процедуры описание не трогает, и перечень устарел бы со второй доработкой. «После» соседа в коде — место перед следующим, и ответ говорит, чем это грозит: уберёт вендор того соседа — код бросит исключение при открытии формы. "код": true без модуля берёт свой модуль объекта («приставка + имя объекта»), если он есть и годится — серверный, не клиентский и не вызова сервера, — и сразу выдаёт готовые правки; иначе — заглушка с предупреждением ФОРМА-КОД-БЕЗ-МОДУЛЯ. Поле флажка на реквизите не Булево и не Число — ошибка ФОРМА-ФЛАЖОК-ТИП (синтакс-помощник: флажок редактирует Булево, Число — при «ТриСостояния»). Реквизит типа ТабличныйДокумент и прочих документов, чьё поле инструмент не заводит, — предупреждение ФОРМА-РЕКВИЗИТ-БЕЗ-ПОЛЯ (с тем, как поле добавить). forms_check с verbose показывает XML формы; оговорка про uuid — только когда новые uuid в показе есть. Элемент без приставки доработок в типовой форме — ошибка ФОРМА-БЕЗ-ПРИСТАВКИ, как МД-ПРЕФИКС у метаданных: ошибку, в отличие от предупреждения, через accepted не принять.

Вторая доработка той же формы дописывается в её процедуру, а не заводит вторую процедуру и второй вызов. Процедура с названным именем уже есть и узнаётся — ответ даёт код и место для него: у кода платформы — перед её КонецПроцедуры (процедура доработки узнаётся по единственному параметру — форме), у языка команды — там и так, как скажет его диалект, с пустой строкой после; вызов в модуле формы уже есть — второй не выдаётся. Процедуру не узнать — ФОРМА-КОД-ИМЯ-ЗАНЯТО. Модуль формы уже вызывает процедуру с этим именем, а модуль не назван или назван другой, — ошибка ФОРМА-КОД-ВЫЗОВ-ЕСТЬ с модулем, который назвать. Имя процедуры задано явно, а модуль формы уже вызывает процедуру доработки этой формы по умолчанию, — предупреждение ФОРМА-КОД-ВТОРАЯ-ДОРАБОТКА: другая доработка — законно, та же — задвоение. show типовой формы напоминает, что элементов, которые добавляет код, в дереве нет: оно из XML.

События элементов — по виду (meta/domain/form_events.py, по синтакс-помощнику 8.5.1: события объекта и его расширения для вида; сверено с 11 042 формами крупной типовой конфигурации — ни одного события вне перечней). Событие чужого вида — ошибка ФОРМА-СОБЫТИЕ-НЕ-ДЛЯ-ВИДА с перечнем событий вида, неизвестное имя — ФОРМА-СОБЫТИЕ-НЕИЗВЕСТНО, всё в одном ответе и до записи: у флажка, например, нет ни «НачалоВыбора», ни «ПриСменеСтраницы». События вида печатает fields(kind="ПолеФлажка") и т. п. Процедура с именем обработчика в модуле формы уже есть — подсказка говорит «уже есть, заводить не нужно», а если она обработчиком этого события быть не может (директива, число параметров по сигнатурам корпуса), — ошибка ФОРМА-ОБРАБОТЧИК-НЕ-ТОТ: серверная «УправлениеФормой()» без параметров клиентским обработчиком не станет.

Форма списка заводится вместе с таблицей динамического списка: назначение «Списка» даёт главный реквизит Список, таблица привязывается к нему ("путь": "Список"), колонки — именами полей. Колонка списка по умолчанию поле-надписи: список показывают, а не правят (19 465 колонок корпуса против 1262 полей ввода); названный вид уважается. Стандартное поле пишется английским именем и здесь — «Дата» становится Список.Date: так его пишет платформа. Поле, которого нет ни у хозяина, ни среди стандартных, — предупреждение, а не отказ: поля списка приходят из его запроса.

Дерево значений — та же таблица: реквизит формы типа ДеревоЗначений с колонками, элемент ТаблицаФормы по нему, поля внутри. Колонка в дерево добавляется как колонка в таблицу — именем реквизита в таблица:

{"реквизиты": [{"имя": "Норматив", "тип": "Число(15,3)", "таблица": "мой_Дерево"}],
 "элементы": [{"вид": "ПолеВвода", "имя": "мой_ДеревоНорматив",
               "путь": "мой_Дерево.Норматив", "родитель": "мой_Дерево",
               "после": "мой_ДеревоФлаг"}]}

Идентификатор колонки живёт внутри своего реквизита, а не в общем счёте формы: инструмент берёт max+1 по его Columns — у деревьев корпуса нумерация встречается и с 1000000. У таблицы дерева инструмент не пишет RowFilter (отбор строк бывает у плоского набора, и круг через платформу его снимает), а Representation оставляет List: иерархию даёт источник данных, а не свойство таблицы.

Условное оформление формы лежит в выгрузке внутри раздела реквизитов, и у всех 563 форм корпуса, где оно есть, стоит последним. Поэтому новый реквизит инструмент ставит за последним реквизитом, а не в конец раздела, а показ оформление реквизитом не считает. Круг через платформу это подтверждает: платформа возвращает и оформление, и реквизит перед ним без правок.

Посмотреть, что в форме уже есть, — --показать:

py -3 meta/add.py --показать <выгрузка> Документ.мой_Заявка.ФормаДокумента

Печатается то, чем оперирует задание: назначение, реквизиты формы с типами и главным, команды с обработчиками, события формы и дерево элементов — имя, вид, привязка, команда кнопки, обработчики. Вид называется по-русски и у того, чего инструмент не пишет (поля табличного, текстового и HTML-документа, диаграммы, календаря и другие): английский тег посреди русских слов читается как ошибка вывода. Слова взяты из справочника свойств, а не придуманы; на заведение такого вида — отказ с перечнем умеющих. Спутники (контекстные меню, подсказки, дополнения таблиц) прячутся и считаются: их на корпусе вдвое больше самих элементов. Показать их — --подробно. Автоматическая командная панель не прячется, если в ней лежат настоящие кнопки. У типовой формы в шапке стоит пометка, что править её можно только кодом.

Это ответ на вопрос «куда вставлять» без чтения Form.xml, а он бывает в сотню килобайт — у самой большой формы корпуса 182 199 байт и 158 элементов.

У типовой формы дерево целиком — сотни строк, а место вставки ищут в одной группе. Третий аргумент (у MCP — параметр element) сужает показ до ветки: путь к элементу от корня, он сам и всё, что внутри; на опечатку — похожие имена. Правая колонка шапки авансового отчёта — 11 строк вместо 330:

py -3 meta/add.py --показать <выгрузка> Документ.АвансовыйОтчет.ФормаДокумента ГруппаШапкаПравая

Картинка, поле картинки, поле переключателя и группа колонок задаются так (круг через платформу чист — она не меняет ни байта):

{"элементы": [
  {"вид": "Картинка", "имя": "мой_Значок",
   "свойства": {"Картинка": "СтандартнаяКартинка.Setting"}},
  {"вид": "ПолеКартинки", "имя": "мой_ПолеФлажка", "путь": "мой_Флажок",
   "свойства": {"КартинкаЗначений": "БиблиотекаКартинок.ЗначокОшибки"}},
  {"вид": "ПолеПереключателя", "имя": "мой_ПолеВарианта", "путь": "мой_Вариант",
   "свойства": {"КоличествоКолонок": 2,
                "СписокВыбора": [{"значение": 1, "представление": "Первый"},
                                 {"значение": 2, "представление": "Второй"}]}},
  {"вид": "ТаблицаФормы", "имя": "мой_Таблица", "путь": "мой_Таблица",
   "элементы": [{"вид": "ГруппаКолонок", "имя": "мой_ГруппаЧисел",
                 "свойства": {"Группировка": "ВЯчейке"},
                 "элементы": [{"вид": "ПолеВвода", "имя": "мой_ТаблицаСумма",
                               "путь": "мой_Таблица.Сумма"}]}]}]}

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

Чего у форм нет: полей табличного, текстового, HTML- и форматированного документа, диаграмм, индикатора, календаря, планировщика, подменю — по числу элементов это 1 % корпуса (347 258 из 350 799 инструмент пишет). Рождённое у картинки, поля картинки, поля переключателя и группы колонок снято с корпуса правилом «есть у 95 % и больше» — эталона конфигуратора на них нет, и это единственное место, где они стоят слабее остальных. Эталоном проверены виды элементов формы документа — формы справочника, регистра и обработки рождаются по замеру корпуса (UseForFoldersAndItems у форм элемента и группы эталоном не подтверждён).

Атомарные роли

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

Как роли называются, — соглашение команды, его задаёт профиль:

"атомарныеРоли": {"имя": "мой_атом_{вид}_{объект}_{право}",
                  "синоним": "(Мой) Атом. {вид} - {синоним} - {право}"}

В шаблоне имени поля {вид}, {объект} и {право} — каждое ровно один раз, вне полей — только буквы, цифры и «_»; в шаблоне синонима — {право} и {синоним} (синоним объекта) или {объект}; {вид} в синониме пишется словами («Регистр сведений»). Схема, которая дала бы разным ролям одно имя или один синоним, — отказ при чтении профиля; без схемы атомарных ролей инструмент не заводит: роль с чужим именем хуже, чем никакой.

Пара заводится к новому объекту тем же заданием:

{"вид": "Документ",
 "поля": {"имя": "мой_ЗаявкаНаОплату", "синоним": "Заявка на оплату",
          "движения": ["РегистрНакопления.мой_ЛимитыОплат"],
          "атомарныеРоли": true}}

true — обе роли, список (["Просмотр"]) — только названные. Объект и его роли пишутся одним планом: объекты одной пачки видят друг друга по факту существования — и только по нему. К уже существующему объекту — ключом atomic_roles:

{
  "repo": "C:/путь/к/выгрузке/cf",
  "atomic_roles": ["Документ.мой_Заявка",
                   {"объект": "РегистрСведений.мой_Лимиты", "права": ["Просмотр"]}]
}

У обработки и отчёта роль одна и без суффикса — права «Использование» и «Просмотр»: имя и синоним строятся той же схемой без {право} и разделителя перед ним (мой_атом_Обработка_мой_Загрузка, «(Мой) Атом. Обработка - Загрузка»), «права» у них не задаются. Так заведено у соседей там, где схема принята: у обработок 32 роли без суффикса против 11 с ним, у отчётов 25 против 5.

Комментарий ролей — ключ комментарий в пункте atomic_roles и в объекте атомарныеРоли ({"права": […], "комментарий": "08.10.2026, #TEAM Автор #ЗАДАЧА-1"}): у соседей он есть почти у всех, и без него роль задачи выделялась бы.

«права» в пункте — это какие из двух атомарных ролей завести («Просмотр», «Изменение»), а не права платформы. Состав прав каждой роли — типовой для вида: самое частое сочетание на выгрузке крупной типовой конфигурации; у документа и регистров его держат почти все роли, у справочника «Изменение» — меньше половины (права на предопределённые данные и история данных расходятся). Насколько ему следуют соседи в вашей выгрузке, инструмент считает сам — по ролям той же схемы, где права выданы на один объект вида: число, снятое с одной конфигурации, в другой было бы неправдой. Если типовой состав у соседей не правило (меньше 85 %), у роли предупреждение РОЛЬ-ШАБЛОН-НЕ-ПРАВИЛО с долей, другими составами, их числом и примером роли каждого («без ВводПоСтроке — 32, например «Роль.…»»), советом сверить с соседями (права роли показывает show по адресу «Роль.<имя>») и тем, как получить другой состав: пунктом objects вида «Роль» с полем «права», а не атомарной парой — права существующей роли заданием changes не меняются. Совет стоит у самой роли, а не в плане: шагом плана он не является. Соседей нет — совета нет. Виды вне четырёх с типовым составом (документ, справочник, регистры сведений и накопления) — отказ.

Соседи читаются из файлов прав только по выданным правам: у иной роли в файле названы тысячи объектов со всеми правами false, и на выгрузке крупной типовой конфигурации (тысячи ролей, сотни мегабайт прав) чтение занимает секунды на вид.

show роли печатает права только на те объекты, где выдано хоть одно; объекты, названные в файле прав явным «false», — одной строкой счётом: у иной роли их сто тысяч, и полный показ вышел бы в девять миллионов знаков.

Если у объекта роли уже есть — отказ с их именами: роли ищутся по выданным правам среди ролей схемы, потому что имя по имени объекта не угадать — длинные имена сокращают, и поиск по имени пропустил бы готовую пару. Имя роли длиннее 80 символов — ошибка (#std474 п. 2.3); для длинного объекта задаётся «сокращение» — короткое имя объекта для имён ролей: в пункте atomic_roles или в «атомарныеРоли» объектом {"права": […], "сокращение": "…"}. Синоним роли инструмент пишет без «ё».

#std474 для всех объектов метаданных: имя длиннее 80 символов — МД-ИМЯ-ДЛИННОЕ, буква «ё» в имени, синониме, комментарии из задания — МД-БУКВА-Ё с исправленным текстом (части схемы компоновки — не метаданные, к ним это не относится).

Каждая роль несёт и шесть прав на конфигурацию (режимы основного окна, клиент системы аналитики) — их ставит конфигуратор новой роли; в корпусе они у большинства атомарных ролей.

Подписка на событие

{
  "repo": "C:/путь/к/выгрузке/cf",
  "objects": [
    {"вид": "ПодпискаНаСобытие",
     "поля": {"имя": "мой_ЗаявкаПередЗаписью",
              "синоним": "Заявка перед записью",
              "комментарий": "#ЗАДАЧА-123",
              "источник": [{"вид": "Документ", "имя": "мой_Заявка"}],
              "событие": "ПередЗаписью",
              "обработчик": "мой_ЗаявкиСервер.ПередЗаписью"}}
  ]
}

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

{"вид": "ОбщийМодуль",
 "поля": {"имя": "мой_ЗаявкиСервер", "синоним": "Заявки, сервер",
          "текст": "Процедура ПередЗаписью(Источник, Отказ, РежимЗаписи, РежимПроведения) Экспорт\nКонецПроцедуры"}}

Контекст модуля задаёт постфикс его имени — так устроен стандарт #std469 и так же устроена замеренная конфигурация (замер по 4103 модулям):

Постфикс

Что включено

В замеренной конфигурации

нет или Сервер

сервер, внешнее соединение, клиент (обычное приложение)

1504 из 1705

ВызовСервера

сервер, вызов сервера

439 из 455

Клиент

оба клиента

631 из 636

КлиентСервер

все четыре контекста

466 из 475

Глобальный

глобальный + оба клиента

56 из 58

…ПовтИсп

плюс повторное использование «на время сеанса»

205 из 222

…ПолныеПрава

плюс привилегированный

Флаги контекста по умолчанию берутся из постфикса; называют их только чтобы отступить от него, и названное не переписывается. Имя и флаги обязаны сходиться там, где стандарт говорит «обязательно»: «вызов сервера» без постфикса ВызовСервера — отказ (в замеренной конфигурации таких модулей 111 — ошибка, которую инструмент не даст повторить), «глобальный» без Глобальный — отказ, Клиент без клиентского контекста или с сервером — отказ. Отклонение состава контекстов от стандарта — предупреждение, не запрет: такие модули в замеренной конфигурации есть.

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

ПОДПИСКА-СИГНАТУРА: у «мой_ЗаявкиСервер.ПередЗаписью» параметров 2,
а событие «ПередЗаписью» у источника «Документ» принимает 4 —
так в замеренной крупной типовой конфигурации у 73 подписок из 77

Источник, событие и обработчик проверяются вместе и при правке существующей подписки (changes): правка одного из трёх сверяется с двумя другими, как они записаны в выгрузке. Источник, переставленный с документа на справочник при обработчике с четырьмя параметрами, получает предупреждение о сигнатуре, а не «замечаний нет». Источник принимается списком и одним обозначением — в том числе так, как его печатает показ: "Справочник.Контрагенты (Объект)".

Принятое предупреждение

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

{"repo": "C:/путь/к/выгрузке/cf",
 "принято": ["ПОДПИСКА-НА-СВОЙ-ОБЪЕКТ"],
 "objects": [
   {"вид": "ПодпискаНаСобытие",
    "поля": {"имя": "мой_ЗаявкаПередЗаписью", "синоним": "Заявка перед записью",
             "источник": [{"вид": "Документ", "имя": "мой_Заявка"}],
             "событие": "ПередЗаписью", "обработчик": "мой_ЗаявкиСервер.ПередЗаписью"}}]}

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

Ключ --подробно печатает в просмотре и то, что решено за вас: у регистра сведений это периодичность и режим записи, у реквизита — полнотекстовый поиск и история данных. Без него такие свойства сворачиваются в строку «ещё N полей».

Что лежит в выгрузке

Просмотр отвечает на вопрос «то ли я заказал» — и отвечает про намерение. На вопрос «то ли записалось» отвечает --показать: он читает карточку с диска и печатает её тем же языком, что и просмотр.

py -3 meta/add.py --показать C:/путь/к/выгрузке/cf Справочник.мой_Эталон

Адрес тот же, что у правки, и уходит вглубь так же: Справочник.мой_Эталон.Реквизит.Комментарий. Печатается заполненное; пустые свойства сворачиваются в строку «ещё N полей пусты».

Чтение ходит по той же схеме видов, что и запись, — не по второму описанию формата. Поэтому круг «записали → прочитали → сверили» проверяет обе стороны разом и работает без платформы; в наборе тестов он и стоит.

Изменение свойства

Адрес читается парами «Вид.Имя» и уходит вглубь так же, как устроена карточка. Правка меняет в файле ровно те строки, которые названы: это закреплено тестом, а не обещанием.

{
  "repo": "C:/путь/к/выгрузке/cf",
  "changes": [
    {"путь": "Справочник.мой_Эталон.Реквизит.КодПодразделения",
     "поля": {"индексирование": "Индексировать"}},
    {"путь": "Документ.мой_Заявка.ТабличнаяЧасть.Строки.Реквизит.Сумма",
     "поля": {"проверкаЗаполнения": "ВыдаватьОшибку"}}
  ]
}

Просмотр показывает «было → стало» по значениям:

Справочник.мой_Эталон.Реквизит.КодПодразделения: индексирование было DontIndex стало Индексировать (Index)

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

Поля-перечни — состав подсистемы, движения и основание документа — в правке задаются явно, режимом:

{"путь": "Подсистема.мой_Раздел",
 "поля": {"состав": {"добавить": ["Справочник.мой_Новый"], "убрать": ["Отчет.мой_Старый"]}}}
  • добавить — в конец перечня; что уже есть, второй раз не добавляется (предупреждение). Добавляемое проверяется, как при создании: объект должен существовать, движения — только по регистрам;

  • убрать — только названное; убирать то, чего нет, — ошибка;

  • заменить — перечень целиком, один, без двух других; ответ называет, что уходит.

Голый список в правке — отказ: он заменил бы перечень целиком, и подсистема с десятками объектов потеряла бы их молча. При создании объекта перечень, как и раньше, — обычный список.

Вложенная подсистема — своя карточка в каталоге родителя (Subsystems/Родитель/Subsystems/Дочерняя.xml, в проекте EDT — …/Subsystems/Дочерняя/Дочерняя.mdo), а не узел его карточки. Адрес — цепочкой: Подсистема.Родитель.Подсистема.Дочерняя (так её называет и обратный поиск ссылок); показ и правка — как у корневой. В составе обозначаются объекты любого вида верхнего уровня: Роль.Имя, ОбщийМодуль.Имя, РегламентноеЗадание.Имя и т. д.

Удаление

{
  "repo": "C:/путь/к/выгрузке/cf",
  "deletions": [
    "Справочник.мой_Ненужный",
    {"путь": "Документ.мой_Заявка.ТабличнаяЧасть.Строки.Реквизит.Старый"},
    {"путь": "Отчет.мой_Отчет.Макет.ОсновнаяСхема.НаборДанных.Лишний"}
  ]
}

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

ОТКАЗ: ошибок 1 — задание не пройдёт; находки ниже
Справочник мой_Склад
   ошибка         [УДАЛЕНИЕ-ЕСТЬ-ССЫЛКИ] на «Справочник.мой_Склад» ссылаются (4): Документ.мой_Приход.Реквизит.Склад (тип), Документ.мой_Приход.ТабличнаяЧасть.Товары.Реквизит.Склад (тип), Отчет.мой_Остатки.Макет.Схема.Параметр.Склад (типЗначения), Роль.мой_Кладовщик (права)

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

Переименование объекта

{
  "repo": "C:/путь/к/выгрузке/cf",
  "renames": [{"путь": "Справочник.мой_Старое", "имя": "мой_Новое"}]
}

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

Реквизит в существующий объект

Это тот же «ребёнок по адресу хозяина», только адрес из одной пары. Ключ attributes — короткая запись для самого частого случая, а операция за ним та же, что у additions.

Префикс у вложенного судится по хозяину, а не по самому имени: реквизит в вендорский объект несёт мой_, реквизит в собственный мой_* — нет. Поэтому ниже мой_КодПодразделения в БанковскиеСчета, а в примерах правки свойств — КодПодразделения в мой_Эталон; это не разные соглашения, а одно правило в двух положениях.

{
  "repo": "C:/путь/к/выгрузке/cf",
  "attributes": [
    {"объект": "Справочник.БанковскиеСчета",
     "поля": {"имя": "мой_КодПодразделения",
              "синоним": "Код подразделения",
              "тип": {"вид": "Строка", "длина": 20},
              "индексирование": "Индексировать"}}
  ]
}

Создание объектов и добавление вложенных сущностей в одном задании не смешиваются.

Вложенная сущность по адресу хозяина

attributes умеет одно — реквизит в объект. additions берёт адрес хозяина и вид ребёнка отдельно, и потому кладёт что угодно куда угодно: измерение в регистр, табличную часть в справочник, реквизит в существующую табличную часть, параметр или отбор — в схему компоновки отчёта.

{
  "repo": "C:/путь/к/выгрузке/cf",
  "additions": [
    {"путь": "РегистрСведений.мой_Лимиты", "вид": "Измерение",
     "поля": {"имя": "Организация", "синоним": "Организация",
              "тип": {"вид": "Справочник", "имя": "Организации"}}},
    {"путь": "Справочник.мой_Эталон.ТабличнаяЧасть.Строки", "вид": "Реквизит",
     "поля": {"имя": "Сумма", "синоним": "Сумма",
              "тип": {"вид": "Число", "разрядность": 15, "дробнаяЧасть": 2}}},
    {"путь": "Отчет.мой_Отчет.Макет.ОсновнаяСхема.ВариантНастроек.Основной",
     "вид": "Отбор",
     "поля": {"использование": true, "левое": "Организация",
              "видСравнения": "Равно", "правое": "Основная"}}
  ]
}

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

Чтобы изменить или удалить уже добавленный пункт, адрес продолжается парой «вид, имя» — но имени у пунктов настроек нет, и вместо него стоит то, чем пункт опознаётся: у выбираемого поля и поля порядка это поле, у отбора — левое, у группировки — имя. Например …ВариантНастроек.Основной.Отбор.Организация. Контейнеры (settings, filter) в адресе не называются никогда — их разворачивает сама адресация.

Вид сравнения, направление сортировки и прочие перечисления пишутся по-русски ("Равно", "НеЗаполнено", "Убывание") — как и всё в задании; английское написание из выгрузки отвергается с перечнем допустимого.

Правка кода вставками

Задание на код — модули, правки в каждом, подпись (задача, дата, автор):

{"repo": "C:/work/cf", "task": "ЗАДАЧА-123", "date": "27.08.2026",
 "author": "Автор",
 "modules": [
   {"модуль": "ОбщийМодуль.мой_X", "edits": [
      {"line": 1353, "lines": 0, "code": "\tИначеЕсли …"}]},
   {"path": "CommonModules/мой_Y/Ext/Module.bsl", "revert": true},
   {"модуль": "ОбщийМодуль.мой_Z", "rename": {"from": "Старое", "to": "Новое"}}],
 "move_method": {"from": "ОбщийМодуль.мой_A", "method": "Имя",
                 "to": "ОбщийМодуль.мой_B", "to_line": 42}}

Модуль, созданный задачей, меток не получает. Созданный — тот, которого нет в базе сверки: шапка "base" — ревизия до задачи (коммит или ветка, от которой она начата), как у проверки правок; без неё — HEAD. Модуль задачи, уже добавленный в индекс или закоммиченный в её ветке, так остаётся её модулем — и повторная правка после ревью идёт без меток, а не обёрткой ИЗМЕНЕН вокруг всего собственного кода. Неизвестная ревизия — отказ; репозиторий без коммитов и каталог вне git — по-старому: новый тот, которого нет в индексе (вне git — никакой). Откат сверяет стыки с той же базой.

lines = 0 — чистая вставка перед якорем, code пустой — удаление. При lines > 0 обе границы (first_line, last_line) обязательны: они играют роль version из LSP и отказывают всей пачкой, если номер строки устарел. Номер строки берётся из текущего текста файла.

Модуль называется адресом или путём файла от корня выгрузки (ровно одно из двух). Адрес — вид объекта по-русски и его имя, за ними — вид модуля (МодульОбъекта, МодульМенеджера…), у формы и команды — Форма.<имя> и Команда.<имя>; у объектов с одним модулем вид модуля не пишется:

Адрес

Файл

ОбщийМодуль.мой_X

CommonModules/мой_X/Ext/Module.bsl

Документ.мой_Заявка.МодульОбъекта

Documents/мой_Заявка/Ext/ObjectModule.bsl

Справочник.X.МодульМенеджера

Catalogs/X/Ext/ManagerModule.bsl

РегистрСведений.X.МодульНабораЗаписей

InformationRegisters/X/Ext/RecordSetModule.bsl

Константа.X.МодульМенеджераЗначения

Constants/X/Ext/ValueManagerModule.bsl

Обработка.X.Форма.Форма

DataProcessors/X/Forms/Форма/Ext/Form/Module.bsl

Документ.X.Команда.Печать

Documents/X/Commands/Печать/Ext/CommandModule.bsl

ОбщаяФорма.X, ОбщаяКоманда.X, HTTPСервис.X

модуль у них один

Конфигурация.МодульСеанса

Ext/SessionModule.bsl

Замер на выгрузке крупной типовой конфигурации: все 22 742 модуля ложатся в эти пять шаблонов, исключений ноль. Адрес Справочник.X без вида модуля — отказ: модулей у объекта несколько, и «первый попавшийся» годится для чтения, но не для записи. Модуля нет — отказ называет, какие у объекта есть.

Что решают правила меток и что инструмент делает сам — тип вставки, метки, их место (своя вставка, чужая: вложенная метка на каждый фрагмент, обёртка снаружи — при правке её метки или переписывании больше половины, с отказом при длине больше 300 строк или вложенных метках), текст запроса, целый метод с меткой хвостом, по вставке на каждый новый метод, отступ пустых строк, пустые строки снаружи, — описано в meta/domain/edits.py вместе с причиной каждого правила.

Правка внутри чужой вставки:

  • по умолчанию — на месте: каждый изменённый фрагмент своей вложенной меткой нужного типа (ИЗМЕНЕН — закомментированный оригинал и новый код, УДАЛЕН, ДОБАВЛЕН), сколько бы фрагментов ни было в одной чужой вставке и сколько бы строк ни занимал каждый; остальной код чужой вставки живой и нетронутый, её метки на месте. Чистая вставка вкладывается всегда. Метка над методом (отдельной строкой над объявлением, закрывающая — хвостом на КонецФункции) правке объявления не мешает: наша метка встаёт между чужой открывающей и объявлением, и разбор находит чужую вставку с прежними границами;

  • снаружи — только в двух случаях: правка задевает строку с меткой чужой вставки (открывающую, в том числе хвостом на объявлении метода, или закрывающую — под нашу метку её не вложить) или правки заменяют больше половины её строк кода (непустых и не комментариев между метками; заменяемые считаются так же — пустые строки и комментарии не в счёт: вставка фактически переписывается; ровно половина — ещё на месте). Обёртка одна на все правки вставки: вставка деактивирована целиком, и её исполняемый код повторён следом с правками: метки и деактивированный старый код — её и вложенных в неё чужих вставок — не повторяются, история остаётся в закомментированной копии. Остальные строки — дословно, с хвостовыми пробелами: срезанные, они дали бы в diff косметические правки чужого кода, а их проверка правок считает ошибкой (ПРАВКА-ТОЛЬКО-ПРОБЕЛЫ). Повторять следом одну правку нельзя: остальные строки вставки ушли бы в комментарий молча, а модуль бы при этом компилировался;

  • правка, пересекающая границу вставки (начало снаружи, конец внутри), — отказ: сузьте её внутрь вставки или расширьте на всю вставку с метками;

  • вставка, вложенные метки которой спарены внутри и которая сама — ровно целый метод (так бывает у вендорских меток хвостом на объявлении и на КонецФункции), оборачивается снаружи, а не отклоняется.

Открывающей меткой считаются четыре формы: // {[+]…, // {#TEAM [+]… (2662 на выгрузке крупной типовой конфигурации), // #TEAM{[+]… (484 в 83 модулях: без этой формы закрывающие таких вставок цеплялись бы к чужим висящим меткам) и // { [+]…; на месте #TEAM — метка любой команды (всего на той же выгрузке 36 776 открывающих); строка //}}MRG[ <-> ] — след инструмента сравнения-объединения (185 строк в 12 модулях), а не закрывающая. Закрывающая без открывающей и открывающая без закрывающей называются примечанием «в разборе не учтена» — в файле они остаются как есть; ближние к правкам (60 строк) и по ту сторону, где могут влиять, — поимённо: закрывающая — если правки выше неё, открывающая — если ниже; остальные — одной строкой с номерами, «на решения не влияют». Метки внутри закрытого блока «[-]» не называются: деактивированный код несёт свои старые метки, а их закрывающая стоит хвостом на закомментированной строке и меткой не считается.

Метка метода без закрывающей: метка хвостом на объявлении или отдельной строкой над методом (через описание и директиву, без пустых строк; «[-]» сюда не относится — удалённое это комментарии) без своей пары — вставкой считается весь метод, до его КонецПроцедуры/КонецФункции, и правка внутри разбирается как в любой чужой вставке. Так на выгрузке крупной типовой конфигурации устроены 394 метода «[+] ДОБАВЛЕН» из 403 незакрытых с меткой хвостом и ещё 107 с меткой над методом — закрывающую там просто не ставили. Метка закрывается при проходе, а не после: иначе незакрытая метка, лежащая в стеке, забиралась бы чужой закрывающей через сотни строк (из 108 пар «метка метода — закрывающая дальше его конца» на той же выгрузке 94 ложные: 27-515 вместо 27-72). «Своя пара» сохраняется: та, с которой метка спарилась бы без неявного закрытия, если у закрывающей та же дата (вставка из нескольких методов, и с метками у методов внутри) или она стоит сразу за концом метода (закрыли другим днём). С этим разбором и четвёртой формой метки на той же выгрузке остаётся 747 закрывающих без пары и 377 незакрытых открывающих (без них было бы 1077 и 462). Примечание называет допущение, когда правка внутри такого метода. Деактивированный код вставки «[-]» в новой версии не повторяется, а живой — повторяется всегда: живой код не теряется ни при каком разборе.

Устаревший якорь — отказ, который называет, где ожидаемая строка сейчас («такая строка сейчас на 166 — номер устарел?»). Расхождение одним отступом называется словами («текст совпал, отступ — нет: ожидалось без отступа, в файле 2 табуляции»), строка из одних пробельных — «пустая строка (1 табуляция)», а не пустыми кавычками. Отступ при этом сверяется: им различаются одинаковые КонецЕсли; разной вложенности. Якорь у замены — первая заменяемая строка; новый метод в конец области якорится её #КонецОбласти, новая область передаётся в code целиком. Вставка между директивой компиляции и объявлением метода — отказ с якорем шапки метода. Отказы по правкам одного модуля приходят все разом («правок с отказом: 2: 1) …; 2) …»), а не по одному за вызов.

Место правки решение называет словами: «перед строкой 55 (в конце области «ОбработчикиКомандФормы»)», «строка 13 (в методе «Служебная»)». Текст якоря «#КонецОбласти» в модуле не один, и по номеру строки не видно, в конец какой области ляжет вставка. Решения идут по порядку строк, как у отката.

Формат файла ответ называет у каждого модуля: «(правок: 2; файл CRLF, BOM — так и останется)». Переводы строк инструмент сохраняет, LF остаётся LF (это закреплено тестом). Формат назван словами, потому что grep из Git Bash отбрасывает CR, и по его выводу сохранённый CRLF легко принять за переписанный LF.

Переименование правит объявление и обращения внутри модуля, где метод объявлен (не объявлен — отказ). Обращение через точку переименовывается, только если перед точкой сам этот модуль: имя общего модуля, имя объекта у модуля менеджера, ЭтотОбъект/ЭтаФорма; вызовы одноимённых методов других модулей остаются и перечисляются в примечании. Вызовы из других модулей инструмент не ищет — их находит поиск по выгрузке, вносятся они обычными правками; у экспортного метода примечание об этом напоминает. Метка на объявлении (вставка — весь метод) делает из переименования обёртку всего метода: закомментированная копия и новый метод целиком. Повтор уже внесённого переименования — отказ «переименование уже внесено?», а не «не объявлен».

Символы, которые BSL Language Server считает ошибкой (InvalidCharacterInFile: мягкий перенос, четыре тире, знак минус, неразрывный пробел — перечень снят с самой диагностики), и кавычки-ёлочки (в коде кавычки прямые) в новом коде — отказ с номером строки. Перенесённый текст (переименование, перенос метода) не проверяется: чужой символ в старой строке не повод отказать. Код формы с таким символом из заголовка задания — предупреждение ФОРМА-КОД-СИМВОЛ (ФОРМА-КОД-ЁЛОЧКИ) уже в просмотре формы.

Дата меток, отличная от сегодняшней, — примечание, а не отказ: в метку ставится дата правки, но задание, начатое вчера, законно доносят сегодня. Часы — у корня сборки, домен даты не знает.

Отступ блока набирать не обязательно: код встаёт на уровень места правки — у замены это уровень первой заменяемой строки, у вставки — якоря, а перед словом, закрывающим конструкцию (КонецПроцедуры, КонецЕсли, Иначе…), — на шаг глубже; пришедший мельче сдвигается вправо целиком, и решение это называет. Метки и пустые строки снаружи — на том же уровне. Отступы внутри блока — как переданы: «отступ можно не набирать» касается уровня блока целиком, а не строк внутри него. Метод, тело которого вровень с объявлением, — отказ (нулевая колонка законна у #-инструкций, комментариев и продолжения литерала «|»).

Новые методы и области: каждый метод — своя вставка, описание и директива компиляции (&НаСервере) над ним — его часть (описание передаётся в code вместе с методом, метка встаёт над описанием), закрывающая метка — хвостом на КонецПроцедуры; #Область/#КонецОбласти вставками не оборачиваются. Область, которая на том же уровне модуля уже есть, — отказ с её строками, как и повтор метода (у BSL Language Server это диагностика DuplicateRegion). Одноимённая область внутри другой — не повтор.

Откат сверяет пустые строки на стыках снятых вставок с версией модуля в HEAD и возвращает модуль побайтно. Окно стыка ищется в HEAD по окружающим строкам, так что чужие правки в других местах модуля не мешают; где сверить не с чем (модуля в HEAD нет, задача уже закоммичена), решение так и говорит — тогда смотрите diff. Сверено или нет — у каждой вставки своё: одно несверенное окно не делает несверенными остальные стыки модуля. Снятые разделители называются у своей вставки с номерами строк: «снимаются строки 3-9 (7) и пустые строки-разделители 2, 10» — так счёт сходится по каждой вставке; возвращаемые строки — с номерами нового текста. Область, которую завела задача, снимается вместе со своими вставками: директивы областей меток не несут, и иначе откат оставил бы её пустой. Чья область, говорит HEAD — в нём её нет; без HEAD область остаётся, и примечание о ней говорит. Показ отката — со строками-соседями «·», у пустого места — строками стыка.

Подпись проверяется: автор обязателен — своего автора у инструмента нет, его подставляет тот, кто зовёт инструмент. Автор — только имя (метка команды в начале срезается — её ставит инструмент по профилю; другой # — отказ), дата — дд.мм.гггг; недостающие поля подписи называются все разом. Заглушка вида <Модуль> в коде — отказ. Отказы задания из нескольких модулей приходят все сразу, и каждый называет свой модуль. Рядом с меткой, стоящей отдельной строкой, пустая строка-разделитель не ставится.

Повтор задания после записи — отказ, а не задвоение: новый метод с уже объявленным именем, чистая вставка, у якоря которой уже стоит вставка этой задачи с тем же кодом, переименование в занятое имя.

Просмотр показывает под каждым решением текст, который будет записан, с номерами строк нового модуля («будет записано — строки 653-656») и строкой- соседом сверху и снизу (знак «·» вместо «│»: видно, встал ли разделитель рядом с чужой меткой), запись — где искать записанное. Табуляция в пустой строке видна знаком «→» — проверять её по байтам не нужно. Номера в решениях и в «разметке модуля» — по тексту до правки, в показе — нового; про «разметку модуля» шапка говорит, только когда она в ответе есть. Чужие метки без пары называются поимённо только рядом с правками (60 строк), остальные — одной строкой с номерами. Откат показывает, что вернётся на место снятой вставки; длинный блок — краями, verbose / --подробно — целиком. Заданию из одного отката дата и автор не нужны.

Запись идёт общим планом, как у метаданных: модули пишутся все разом или ни один — сбой на втором модуле откатывает первый.

Правила правки закреплены тестами в meta/tests/test_code.py, включая побайтную сверку с реальной вставкой.

Проверка правок задачи

Инструмент пишет выгрузку и ставит метки вставок; verify проверяет то же самое постфактум — по правкам задачи: файлам, которые она добавила, изменила или удалила относительно базы. Разбор меток у записи и проверки один (domain/edits.py), и что записал code_apply, проверка принимает без находок по меткам — это закреплено тестом.

py -3 meta/add.py --проверить C:/work/cf [--база <ревизия>] [--ревизия <ревизия>]
                  [--задача <ИД>] [--только <код,код>] [--json <файл>]
py -3 meta/add.py --проверить --эталон <каталог> --копия <каталог> [--json <файл>]

Источник правок один из двух. Git: рабочая копия против базы (по умолчанию HEAD) вместе с новыми файлами, которых git ещё не знает, или ревизия (--ревизия) против базы; переименования — парой «удалён — новый». Пара каталогов: обработка или отчёт вне репозитория — эталон из вложения против правленой копии; состав — сравнением по содержимому.

Код

Уровень

Что требует правило

МД-ПРЕФИКС

ошибка

новый объект метаданных назван с приставкой команды (код тот же, что у записи)

ВСТАВКА-БУКВА-Ё

ошибка

тип вставки — ИЗМЕНЕН, ДОБАВЛЕН, УДАЛЕН без «Ё»: по этим словам вставки ищут

ВСТАВКА-БЕЗ-ТИПА

ошибка

в открывающей метке назван тип вставки

ВСТАВКА-БЕЗ-ДАТЫ

ошибка

в открывающей метке дата дд.мм.гггг

ВСТАВКА-БЕЗ-ЗАДАЧИ

ошибка

в открывающей метке ИД задачи — последним «#»

ПРАВКА-ВНЕ-ВСТАВКИ

ошибка

правка существующего кода — внутри вставки, удалённое сохранено комментарием

ПРАВКА-В-ЧУЖОЙ-ВСТАВКЕ

предупреждение

правка внутри вставки другой задачи — своей вставкой поверх (нужен ИД задачи)

ВСТАВКА-НЕ-ЗАКРЫТА

ошибка

открывающая метка, поставленная задачей, закрыта

ВСТАВКА-МЕТОДА-НЕ-НА-КОНЦЕ

ошибка

вставка вокруг метода закрывается на строке КонецПроцедуры/КонецФункции

ВЫГРУЗКА-СЛУЖЕБНЫЙ-ФАЙЛ

ошибка

ConfigDumpInfo.xml в правки не входит — его пересобирает платформа

ФАЙЛ-БЕЗ-BOM

ошибка

текстовый файл выгрузки — UTF-8 с BOM

ФАЙЛ-ПЕРЕВОДЫ-СМЕШАНЫ

предупреждение

переводы строк внутри файла одни

ФАЙЛ-ПЕРЕВОДЫ-ЧУЖИЕ

ошибка

переводы строк — те, что приняты в репозитории

ФАЙЛ-ХВОСТЫ-СРЕЗАНЫ

предупреждение

хвостовые пробелы строк эталона не срезаны (пара каталогов)

ПРАВКА-ТОЛЬКО-ПРОБЕЛЫ

ошибка

строка не заменена на себя же с другими пробелами (git)

ПРАВА-ТОЛЬКО-В-ЧУЖОЙ-РОЛИ

ошибка

права на новый объект выданы в своей роли, а не только в чужой

ПРАВА-НЕ-ВЫДАНЫ

ошибка

новый объект входит в роль с включённым правом

СОСТАВ-ТИПОВ-УЖЕ

предупреждение

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

Уровень каждого правила меняется профилем (правила), правило можно выключить; приставка и «своя роль» — тоже из профиля. Без приставки МД-ПРЕФИКС не проверяется, а права считаются выданными в любой роли. Приставка задаётся и списком: основная первой, остальные тоже свои — старые приставки команды, тестовые модули YAxUnit ("приставка": ["мой_", "ОМ_мой_"]).

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

Пропущенное называется: правило, которое не проверялось (нет приставки, не задан ИД задачи), идёт в «Пропущено» с причиной, и итог тогда — «НЕПОЛНЫЙ ПРОГОН», а не «ЧИСТО». Пояснения, которые вердикта не меняют, — отдельно: переводы строк не сверяются, если git нормализует их сам (core.autocrlf); права не проверяются у пары каталогов без конфигурации.

Ответ для хука — --json <файл>:

{"источник": "рабочая копия против abc1234 (с новыми файлами)",
 "находки": [{"код": "ВСТАВКА-НЕ-ЗАКРЫТА", "уровень": "ошибка",
              "файл": "CommonModules/мой_X/Ext/Module.bsl", "строка": 11461,
              "текст": "открывающая метка вставки без закрывающей: вставка не закрыта"}],
 "пропущено": [{"что": "ПРАВКА-В-ЧУЖОЙ-ВСТАВКЕ", "почему": "не задан ИД задачи"}],
 "пояснения": ["переводы строк не сверяются: git нормализует их сам (core.autocrlf)"]}

файл — от корня выгрузки, строка — в новой версии; у находки по объекту метаданных (права) файла нет — объект назван в тексте. У правил о файле целиком (ПРАВКА-ВНЕ-ВСТАВКИ, ПРАВКА-В-ЧУЖОЙ-ВСТАВКЕ, формат) строки нет, номера строк — в тексте. Код возврата: 0 — нарушений нет (предупреждения допустимы), 1 — есть нарушения, 2 — ошибка входа (нет выгрузки, нет ревизии): это не «чисто», хук обязан считать его провалом.

СОСТАВ-ТИПОВ-УЖЕ смотрит только члены, которых до задачи не было; больше 300 карточек в правках — похоже на перезалив выгрузки, и правило идёт в «Пропущено», а не читает тысячи карточек. Карточка, которая не читается, называется в пояснениях.

Что зависит от формата выгрузки. Метки вставок, приставка, права и составы типов — от модели и годятся любому формату; ВЫГРУЗКА-СЛУЖЕБНЫЙ-ФАЙЛ — только выгрузке конфигуратора; правила ФАЙЛ-* — общие для выгрузки и проекта EDT. Раскладка (что модуль, что карточка, где права роли) — в infra/layout.py, чтение состава карточки — в перекладке (acl/mapping.py): проекту EDT — своя раскладка и своё чтение рядом.

Нормализация формата

Инструменты записи агентов не пишут BOM, срезают хвостовые пробелы у любой записываемой строки и не доносят до файла строку из одних пробельных символов; разовая правка не держится — следующая перезапись теряет её снова. Поэтому нормализация — операция, а не событие: идемпотентна и запускается перед каждой проверкой правок и перед коммитом.

py -3 meta/add.py --нормализовать C:/work/cf [--база <ревизия>] [--apply]
py -3 meta/add.py --нормализовать --эталон <каталог> --копия <каталог> [--apply]

По каждому текстовому файлу задачи (кроме служебных):

  1. BOM — файл выгрузки в UTF-8 с BOM;

  2. переводы строк — принятые в репозитории (по образцам его модулей и карточек); где git нормализует их сам (core.autocrlf), — свои переводы файла: переписывать файлы там незачем; у пары каталогов — как у эталона, а эталон с разными переводами не навязывает ни одних;

  3. хвостовые пробелы — строкам, которые совпадают с базой без хвостов, окончание из базы (класс «строка заменена на себя же без хвоста»); строки внутри настоящих правок не трогаются;

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

  5. завершающий перевод строки — как в базе.

Строка, где пробельный символ уже есть, не переоформляется: это была бы косметическая правка чужого кода, а её проверка правок запрещает (ПРАВКА-ТОЛЬКО-ПРОБЕЛЫ). Без --apply — просмотр (код 1 — есть что исправить), с --apply — запись всех файлов разом; ревизию не переписывают.

Сверка плана изменений

План — намерение: какие объекты задача создаёт, меняет или удаляет, какие члены у них появляются — реквизиты, измерения, ресурсы, табличные части, значения перечисления, с типами — и какие методы в модулях. Сверка с фактом задачи идёт в обе стороны: заявлено, но не сделано; сделано, но не заявлено; типы не совпали. Глазами структурную сверку «замысел — код» делают по многу раз и с пропусками; здесь она — правило.

py -3 meta/add.py --сверить-план C:/work/cf --план план.json [--база <ревизия>]
                  [--ревизия <ревизия>] [--json <файл>]
py -3 meta/add.py --план-по-факту C:/work/cf [--база <ревизия>] [--ревизия <ревизия>]

Источник правок тот же, что у проверки (--эталон/--копия тоже принимаются). План — JSON:

{"объекты": [
  {"вид": "РегистрСведений", "имя": "мой_Остатки", "действие": "создать",
   "измерения": {"Договор": ["Справочник.ДоговорыКонтрагентов"], "Период": ["Дата"]},
   "ресурсы": {"Сумма": ["Число(15,2)"]},
   "модули": {"МодульМенеджера": ["ЗарегистрироватьИзменение"]}},
  {"вид": "Перечисление", "имя": "мой_Виды", "действие": "создать", "значения": ["Первый", "Второй"]},
  {"вид": "ОбщийМодуль", "имя": "мой_Сервер", "действие": "изменить", "методы": ["Пересчитать"]},
  {"вид": "Роль", "имя": "мой_атом_Остатки_Просмотр", "действие": "изменить"}
]}
  • действие — создать, изменить, удалить; у изменяемого объекта перечисляется только то, что задача добавляет.

  • Состав — по группам реквизиты, измерения, ресурсы, табличныеЧасти, значения: имя -> список типов (null — тип не сверяется) или просто список имён. Тип пишется записью показа (Строка(50), Справочник.Имя) или записью синтакс-помощника (СправочникСсылка.Имя) — это один тип; сверяется род типа: длина, точность и состав даты — нет. Тип-набор (ОпределяемыйТип.Имя) — тоже тип.

  • Методы — методы у вида с одним модулем (общий модуль, общая команда, сервисы), модули у прочих: МодульОбъекта, МодульМенеджера, МодульНабораЗаписей, МодульМенеджераЗначения. Служебные методы перечислять не нужно: заявляются те, что составляют замысел задачи.

  • Неизвестный ключ, неизвестное действие, вид, которого выгрузка не знает, объект дважды — отказ (код 2) со всеми бедами разом: опечатка («реквизит» вместо «реквизиты») молча выключила бы сверку того, что за ней стоит.

Код

Уровень

Что требует правило

ПЛАН-ОБЪЕКТ-НЕ-СДЕЛАН

ошибка

объект, заявленный к созданию или изменению, есть в выгрузке

ПЛАН-ОБЪЕКТ-УЖЕ-БЫЛ

ошибка

объект, заявленный к созданию, до задачи не существовал

ПЛАН-ОБЪЕКТ-ОСТАЛСЯ

ошибка

объект, заявленный к удалению, удалён

ПЛАН-ОБЪЕКТ-БЕЗ-ПРАВОК

предупреждение

в объекте, заявленном к изменению, есть правки

ПЛАН-ОБЪЕКТ-НЕ-ЗАЯВЛЕН

ошибка

объект, который задача создала, изменила или удалила, заявлен

ПЛАН-ПОПУТНОЕ-НЕ-ЗАЯВЛЕНО

предупреждение

роль или подсистема, тронутая задачей, заявлена: их правят попутно — право на новый объект, место в интерфейсе

ПЛАН-КАРТОЧКА-НЕ-ЧИТАЕТСЯ

ошибка

карточка заявленного объекта читается

ПЛАН-ЧЛЕН-НЕ-СДЕЛАН

ошибка

заявленный член объекта есть в выгрузке

ПЛАН-ЧЛЕН-НЕ-ЗАЯВЛЕН

ошибка

член, который появился в задаче, заявлен

ПЛАН-ТИПЫ-РАСХОДЯТСЯ

ошибка

род типов члена — заявленный

ПЛАН-МОДУЛЬ-НЕ-СДЕЛАН

ошибка

модуль, где заявлены методы, есть

ПЛАН-МЕТОД-НЕ-СДЕЛАН

ошибка

заявленный метод объявлен в модуле

ПЛАН-МЕТОД-НЕ-ЗАЯВЛЕН

предупреждение

экспортный метод, появившийся в задаче, заявлен

Уровни меняются профилем, как у проверки. Объект «тронут», если задача добавила, изменила или удалила любой его файл — карточку, модуль, форму, права роли. Методы читаются тем же разбором объявления, что у правки вставками. Расхождение не обязательно дефект кода: план мог устареть, — но объяснить его обязан тот, кто сдаёт работу.

Ответ — текстом (итог «СОВПАДАЕТ» или «РАСХОЖДЕНИЙ: N») и --json тем же видом, что у проверки, с полем объект («Вид.Имя»); файл — карточка или модуль, строка — у метода. Коды возврата — как у проверки: 0, 1, 2.

План по факту (--план-по-факту, plan_emit) — тот же формат по сделанному: объекты, файлы которых задача тронула; их новые члены с типами записью показа, с уточнениями (Число(15,2)); новые методы модулей — все, а не только экспортные. Сверка принимает его без расхождений. Он нужен, чтобы увидеть сделанное целиком или начать план доработки существующей задачи; план новой задачи пишется до кода — это намерение, а не отчёт.

Язык

Идентификаторы английские — как в остальном Python проекта. Русский остаётся там, где он данные, а не код: ключи полей домена ("имя", "источник"), имена видов ("ПодпискаНаСобытие"), значения перечислений ("НеИспользовать"), тексты находок, комментарии и docstring'и. В этом и смысл слоя перекладки: русский — язык предметной области, а не язык программы.

Устройство

meta/domain/       понятия и инварианты. Не знает ни тегов, ни файлов
meta/acl/          перекладка домен ↔ формат. Ядро — три модуля по скорости
                   изменения: `vocabulary.py` — таблицы соответствий (растут
                   с каждым видом), `schema.py` — состав и порядок свойств,
                   `mapping.py` — сама сборка (почти не меняется). Рядом —
                   адреса модулей (`modules.py`), код доработки формы
                   (`form_code.py`, `platform_code.py`) и показ формы в HTML
                   (`form.py`: страница по дереву формы, без канала — из кода)
meta/application/  сценарии — у каждого свой модуль (`add_object/use_case.py`,
                   `show_form/use_case.py`, `atomic_roles_use_case.py`…),
                   договоры площадки и справки (`ports.py`), факт задачи
                   для плана (`plan_facts.py`), общий итог (`dto.py`) и
                   порождение uuid (`ids.py`)
meta/infra/        файлы: сериализация, запись планом с откатом, чтение конфигурации;
                   модули кода — формат файла, история в git (`modules.py`)
meta/jobs/         язык заданий: JSON -> модель приложения, семьи операций, справка языка;
                   задание на код (`code.py`) — всё задание одна работа
meta/report/       язык ответов: результат -> строки текста
meta/cli/          канал терминала: ключи, файл задания, печать
meta/api/          канал MCP: модели запроса, контроллер, инструменты
meta/composition/  корень сборки: подключает инфраструктуру к сценариям, сценарии к каналам
meta/add.py        точка входа терминала — запускает корень
meta/mcp_server.py точка входа MCP — запускает корень

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

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

Зависимость направлена внутрь: приложение знает только интерфейсы из ports.py. Другой формат исходников — это новая реализация интерфейса, домен не меняется: так добавлен проект EDT (EdtDump — наследник площадки выгрузки, выбирается корнем сборки по каталогу). Проверяется тестом, который выполняет сценарий на площадке «в памяти», не касаясь ни XML, ни диска.

Проверки

Одной командой — линтер и все тесты:

py -3 check.py          # всё
py -3 check.py --fast   # без сверки с корпусом

По отдельности — py -3 -m ruff check . и py -3 -m pytest -q.

Границы слоёв

meta/tests/test_architecture.py — гард направления зависимостей: разбирает исходники через ast, ничего не импортируя, и падает, если появилось ребро наружу. Правила:

domain       — ни от чего
acl          → domain
application  → domain
infra        → domain, acl, application (только ports)
jobs         → domain
report       → domain, jobs, application (dto, ports)
cli, api     → domain, jobs, report, application (dto, ports); друг друга не видят
composition  → все
точки входа  → только composition

Сторонние библиотеки заперты в своём слое: lxml — только infra, mcp и pydantic — только api. Отдельно проверяет, что domain и acl не знают про файловую систему (это утверждение встречается в описаниях — здесь оно перестаёт быть обещанием), и что все слои на диске описаны в таблице гарда. Ребро application -> acl или import os в домене его роняют.

Сверка с корпусом

Идёт по настоящей выгрузке; путь к ней задаётся переменной окружения META_CORPUS — каталог выгрузки с Configuration.xml. Без выгрузки такие тесты помечаются пропущенными, остальные работают.

Что доказано на корпусе — выгрузке крупной типовой конфигурации:

  • 639 подписок и 4100 общих модулей собираются побайтно;

  • 29 374 реквизита из 34 542 по 13 видам — побайтно; остальные 5168 несут то, чего домен не выражает, и это число печатает сам тест;

  • вложенные сущности: 6673 ресурса, 10 814 значений перечислений и 2139 табличных частей вместе с их реквизитами — побайтно (непокрытое тест тоже печатает: 543, 815 и 752).

Что доказано кругом через платформу:

  • карточки, выгруженные платформой после загрузки написанного инструментом, совпадают побайтно, включая переводы строк (meta/tests/эталоны/);

  • схема компоновки проходит тот же круг целиком: отчёт с макетом, набор данных, параметры, четыре вида пунктов настроек и переименование справочника со всеми ссылками — платформа не меняет ни строки, CheckConfig с проверкой целостности и ссылок чист. xsi:nil у пустого значения параметра и useRestriction у параметров инструмент пишет так, как их пишет платформа.

Регрессии языка заданий

Регрессии языка заданий закреплены фикстурами в meta/tests/test_regressions.py. Они проходят задание целиком — через язык заданий и инварианты, а не через перекладку напрямую: так ловится то, чего не видно ни в тесте перекладки, ни в плане записи, — например, значение заполнения, не согласованное с хозяином реквизита, или опечатка в ключе типа (длина/точность вместо разрядность/дробнаяЧасть), которую молчаливое умолчание превратило бы в Число(10,0) вместо Число(15,2) при плане записи того же размера. Отдельный гард следит, чтобы коды находок и представления типов оставались русскими строками: механическое переименование идентификаторов не должно задевать строки-данные.

Чего нет

  • у форм пишутся элементы пятнадцати видов из 31 (99,0 % корпуса по числу — 347 258 элементов из 350 799); не пишутся поля документов (табличного, текстового, HTML, форматированного), диаграммы, индикатор, календарь, планировщик и подменю; из макетов — только макет со схемой компоновки, табличный документ и текст не пишутся;

  • заведено 15 видов объектов из 43, встречающихся в замеренной конфигурации, — по числу объектов это 17 033 из 22 284. Не заведены, по убыванию распространённости: общие картинки (2327), функциональные опции (647), элементы стиля (590), общие формы (500 — как объект metadata_apply не заводятся, но создаются заданием на формы: forms_apply, «Общая.Имя» с «создать»), пакеты XDTO (442), общие макеты (303), параметры сеанса (154), журналы документов (46), группы команд (44), планы обмена (39), планы видов характеристик (32) и ещё семнадцать видов по десятку и меньше;

  • вложенные подсистемы не заводятся: они живут отдельными файлами в каталоге родителя;

  • из наборов данных схемы компоновки описан только набор-запрос (893 в замеренной конфигурации); набор-объект (273) и объединение (109) описываются другими полями и не заведены;

  • заданием на метаданные текст общего модуля задаётся только при создании; дописать метод в существующий модуль — заданием на код (code_apply, раздел «Правка кода вставками»);

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

  • табличная часть замерена у справочника, документа, обработки и отчёта; у планов и прочих видов состав не замерен — инструмент об этом говорит и не пишет;

  • порядок детей внутри табличной части не замерен: сейчас там бывает один вид, и появление второго — отказ, а не тихая ошибка;

  • ссылочный тип у параметра схемы компоновки не пишется: приставку для типов конфигурации карточка объявляет в корне (cfg), а схема — на самом элементе и с порождённым именем (d4p1), правило порождения не замерено. Примитив пишется;

  • связи внутри схемы идут по именам полей — выбираемое поле называет поле набора, запрос называет параметр. Удаление внутри схемы об этом предупреждает и не сверяет;

  • три свойства реквизита со вложенной структурой: связи параметров выбора, параметры выбора, связь по типу — отвергаются, а не записываются неверно;

  • значение заполнения умеет «не задано» и пустую строку, настоящее значение — отказ;

  • ConfigDumpInfo.xml не трогается намеренно: его configVersion считает платформа, снаружи он не вычисляется. Загрузка должна быть полной.

  • формат выгрузки один — 2.21; совместимость с 2.20 и другими не замерена, и писать в такую выгрузку инструмент отказывается.

  • сверка плана видит состав объекта верхнего уровня и собственные модули: реквизиты табличных частей, формы и их модули, команды объекта в план не входят; длина и точность типов не сверяются — только род типа. Источник — выгрузка конфигурации (под git или парой каталогов той же раскладки): выгрузка внешней обработки, где карточка лежит в корне, сверке плана не подлежит.

Что проверяется на отношения, а не только по отдельности

Мало проверить, что объект существует и что имя права существует, — нужно ещё, чтобы они имели друг к другу отношение. Таких проверок три. Строгость у них разная, и зависит она от того, чем доказано утверждение: «в выгрузке не встречается» — наблюдение, «платформа не примет» — факт, и по выгрузке он не доказывается, только опытом.

  • имя против стандартных реквизитов вида — ошибка. Измерение «Период» в регистре имя занимает: платформа заводит стандартные реквизиты сама и в выгрузку их не выписывает, так что состав детей о них молчит. Это не наблюдение по корпусу, а опыт: платформа отвергает загрузку словами «Недопустимое имя измерения - Период», и отвергает одинаково у периодического регистра и у непериодического;

  • право против вида объекта — предупреждение. Проведение справочнику, ввод по строке регистру. Таблица снята по 3,07 млн пар «объект — право» в 2709 ролях замеренной конфигурации;

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

Порядок групп в карточке

Куда ставить группу детей — например Attribute, если реквизитов в карточке ещё нет, — по большинству не угадывается. На взгляд порядок групп внутри объекта неканонический: 23 варианта на 1783 карточки, и Attribute идёт первой лишь в 96 % из них. Но опыт показывает обратное: карточка, записанная с переставленными группами (TabularSection перед Attribute), после загрузки в базу и выгрузки обратно получает группы на их обычных местах: порядок канонический, его держит платформа.

Свод по 7179 карточкам замеренной конфигурации даёт этот порядок для 23 видов без единого противоречия: «разные варианты» — подмножества одного общего порядка, а не разброс. Таблица — acl/vocabulary.py, CARD_ORDER; она пересобирается по корпусу в тесте, а не хранится как мнение.

Порядок разный у разных видов, и это не описка: у документа формы идут между реквизитами и табличными частями, у справочника — после них; у регистра бухгалтерии измерения впереди ресурсов, у регистра сведений — наоборот. Правило «по большинству» — Attribute всегда первой — ошибалось бы ровно на этих случаях.

Словарь

Термин

Что значит

выгрузка

каталог, который пишет конфигуратор командой «Выгрузить конфигурацию в файлы»: в корне Configuration.xml — реестр объектов, рядом — каталоги по видам

карточка

XML-файл объекта в выгрузке (Catalogs/Имя.xml): свойства объекта и его детей — реквизитов, табличных частей, измерений

спутники

файлы рядом с карточкой, в каталоге объекта: модули, формы, макеты, права роли; у элемента формы — служебные элементы, которые рождаются вместе с ним (контекстное меню, расширенная подсказка, дополнения таблицы)

порождаемые типы

типы, которые платформа заводит вместе с прикладным объектом: у справочника — объект, ссылка, выборка, список, менеджер

грань

какой стороной тип объекта участвует в типе значения: ссылка, объект, менеджер, набор записей…; пишется в скобках — Справочник.Контрагенты (Объект)

вставка, метки вставки

правка существующего кода, обёрнутая парой комментариев: открывающей // {[+](фрагмент ДОБАВЛЕН), дата, автор #задача и закрывающей // } автор, дата; тип — ИЗМЕНЕН [*], ДОБАВЛЕН [+], УДАЛЕН [-]

приставка доработок

начало имён всего, что команда добавляет сама (мой_), задаётся профилем; объект и форма без неё — типовые

корпус, замеренная выгрузка

выгрузка крупной типовой конфигурации, по которой сняты числа этого README и правила «как пишет платформа»; тесты сверки идут по ней (META_CORPUS)

проверочная конфигурация

пустая конфигурация, на которой проверяется запись: на ней проходят круги через платформу и снят эталон форм

круг через платформу

записали инструментом → загрузили конфигуратором → выгрузили обратно → сверили побайтно

Related MCP Connectors

Related MCP Servers