Skip to main content
Glama
README.md
# 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 работают проверки и
нормализация, а запись и показ отклоняются с этой же подсказкой.

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

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

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

```json
{"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` по адресу `ВнешняяОбработка.Имя.МодульОбъекта` или `…Форма.Имя` — или по пути |

```json
{"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 загружаемого файла»).
Метаданные и формы получают отказ до записи, на диске не меняется ни байта.
Правка кода вставками от формата не зависит и идёт в любую выгрузку.

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

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

```json
{
  "приставка": "мой_",
  "метки": {"тег": "#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`:

```json
"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'
   реквизиты                        список: Реквизит — полями напрямую, без "вид"/"поля"
```

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

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

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

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

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

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

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

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

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

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

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

### Формы

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

```json
{"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))`,
заголовок — четвёртым параметром `РеквизитФормы`.

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

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

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

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

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

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

```json
{"реквизиты": [{"имя": "Норматив", "тип": "Число(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 --показать <выгрузка> Документ.АвансовыйОтчет.ФормаДокумента ГруппаШапкаПравая
```

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

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

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

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

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

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

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

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

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

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

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

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

```json
{
  "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 символов —
`МД-ИМЯ-ДЛИННОЕ`, буква «ё» в имени, синониме, комментарии из задания —
`МД-БУКВА-Ё` с исправленным текстом (части схемы компоновки — не метаданные,
к ним это не относится).

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

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

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

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

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

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

| Постфикс | Что включено | В замеренной конфигурации |
|---|---|---|
| нет или `Сервер` | сервер, внешнее соединение, клиент (обычное приложение) | 1504 из 1705 |
| `ВызовСервера` | сервер, вызов сервера | 439 из 455 |
| `Клиент` | оба клиента | 631 из 636 |
| `КлиентСервер` | все четыре контекста | 466 из 475 |
| `Глобальный` | глобальный + оба клиента | 56 из 58 |
| `…ПовтИсп` | плюс повторное использование «на время сеанса» | 205 из 222 |
| `…ПолныеПрава` | плюс привилегированный | |

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

```json
{"путь": "Подсистема.мой_Раздел",
 "поля": {"состав": {"добавить": ["Справочник.мой_Новый"], "убрать": ["Отчет.мой_Старый"]}}}
```

* `добавить` — в конец перечня; что уже есть, второй раз не добавляется
  (предупреждение). Добавляемое проверяется, как при создании: объект должен
  существовать, движения — только по регистрам;
* `убрать` — только названное; убирать то, чего нет, — ошибка;
* `заменить` — перечень целиком, один, без двух других; ответ называет, что
  уходит.

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

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

### Удаление

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

```json
{"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 <файл>`:

```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:

```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`) |
| проверочная конфигурация | пустая конфигурация, на которой проверяется запись: на ней проходят круги через платформу и снят эталон форм |
| круг через платформу | записали инструментом → загрузили конфигуратором → выгрузили обратно → сверили побайтно |