| name | round-trip-xml |
| description | Подсвечивает пачки расхождений short round-trip (XML → модель → XML) для анализа и автоматически генерирует одиночный reproducer для выбранного diff'а после подтверждения пользователя. |
round-trip-xml — reproducer для диффа short round-trip
Перед диагностикой metadata round-trip обязательно прочитай:
.agents/knowledge/metadata/INDEX.md
.agents/knowledge/metadata/sources-of-truth.md
.agents/knowledge/metadata/round-trip-cycle.md
Что делает скилл
У скилла два режима:
- Triage-пачка — информационный обзор нескольких diff-файлов short round-trip. Нужен, чтобы пользователь выбрал следующие проблемы для отдельного анализа. В этом режиме скилл не создаёт ветки, slug, фикстуры, тесты и планы исправления.
- Одиночный reproducer — один изолированный TDD-reproducer для выбранного файла с расхождением в short round-trip (XML → модель → XML). Без явного выбора используется алфавитно первый diff, как раньше.
На выходе: ветка round-trip/<kebab-slug> с XML-фикстурой (откалиброванной под выход toXML), соответствующей ей TS-фикстурой (ожидаемая форма объекта) и двумя it(...) блоками в fromXML.test.ts и toXML.test.ts. Красным в итоге остаётся ровно одна сторона — та, где сидит баг (fromXML или toXML). Скилл не коммитит и не запускает полный pnpm test — это делает пользователь.
Жёсткие инварианты
- AI ничего не решает автономно. Целевой модуль, slug, границы кластера — всегда предложение одним сообщением, пользователь подтверждает или корректирует.
- Triage-пачка только информирует. В triage-режиме AI показывает список diff'ов, вероятные модули, вероятные файлы кода, категории, описания и фрагменты diff. Он не предлагает slug, ветки, фикстуры, тесты или план исправления.
- Чистое рабочее дерево
nkdk-core. Если git status не чистый — стоп, попросить пользователя.
- Не перезаписывать существующий
<slug>.xml в целевом модуле. При коллизии — стоп, спросить новый slug.
- TS-фикстура — ожидаемая форма, построенная от
types.ts модуля. Не снимать с текущего сломанного выхода fromXML.
- XML-фикстура — полный объект-обёртка (
xr:StandardAttribute / xr:Parameter / аналог), идентичный тому, что toXML эмитит для TS-формы. Минимальный chunk на уровне property-поля не подходит — export <slug> будет падать на дефолтных полях обёртки, а не на целевом баге. Как выйти на полный вид фикстуры — см. шаг 5 (калибровка через toXML).
- Не писать новые правила fromXML/toXML/fromYAML/toYAML — по AGENTS.md проекта работаем только через
rules.ts (правила скилл не трогает, он только фиксирует баг).
- Один узкий запуск vitest разрешён — только для калибровки XML-фикстуры (шаг 5). Полный
pnpm test не запускать; финальная проверка — на стороне пользователя.
- После создания файлов — стоп. Финальное сообщение: «запусти
pnpm test».
Шаг 1. Запуск round-trip
Вызови ./.agents/skills/round-trip-xml/round-trip.sh из корня nkdk-core. Скрипт:
- Читает
NKDK_XML_REPO (обязательная) и NKDK_XML_DIR (опциональная) из .env.
- Проверяет, что рабочее дерево
nkdk-core чистое — иначе падает.
- Делает
git restore . в XML-репо.
- Определяет каталоги запуска:
- если
NKDK_XML_DIR сам содержит зарегистрированные XML-каталоги (Catalogs, Documents, DocumentNumerators, Sequences) — проверяет только его;
- иначе проходит по дочерним каталогам
NKDK_XML_DIR в алфавитном порядке и берёт только те, где есть зарегистрированные XML-каталоги.
- Запускает
nkdk short-round-trip-test последовательно по каждому каталогу.
- Если после каталога появился хотя бы один diff — останавливается на этом каталоге и дальше не смотрит.
- В одиночном режиме выводит выбранный diff-файл и полный diff. Без параметров выбирается первый по алфавиту diff найденного каталога,
--diff-index N выбирает N-й файл из отсортированного списка.
- В triage-режиме (
--triage --batch-size N --start-index K) выводит пачку diff-файлов найденного каталога.
Если скрипт написал === Round-trip чистый === — стоп, нечего фиксировать.
Если упал на guard'е dirty-tree — попроси пользователя сохранить/откатить правки. Не запускай git stash, git restore, git clean сам.
Triage-пачка
Используй этот режим, когда пользователь просит показать несколько ошибок для анализа: «покажи следующие 5 ошибок», «дай пачку с 6-й», «покажи 10 diff'ов». Это не начало reproducer workflow.
Команды:
./.agents/skills/round-trip-xml/round-trip.sh --triage --batch-size 5
./.agents/skills/round-trip-xml/round-trip.sh --triage --batch-size 5 --start-index 6
После запуска сформируй краткий список. Каждый пункт должен содержать:
1. <относительный XML-путь из diff>
XML-файл: <абсолютный локальный путь из XML_FILE_ABS>
XML-каталог: <значение ACTIVE_XML_DIR, если есть в выводе скрипта>
Вероятный модуль: packages/core/metadata/<...> или неизвестно
Вероятный код: packages/core/metadata/<...>/rules.ts или неизвестно
Категория: потеря пустого тега / порядок XML-узлов / лишний default / потеря атрибута / потеря xsi:type / потеря id или ссылки / неизвестно
Описание: <что изменилось при round-trip>
Diff:
<короткий релевантный фрагмент>
Сомнения: <почему модуль или причина неочевидны, если есть сомнения>
Правила triage-ответа:
XML-файл всегда бери из XML_FILE_ABS. Если путь абсолютный и файл существует локально, в ответе Codex оформи его кликабельной ссылкой.
- Для определения
Вероятный модуль и Вероятный код можно читать packages/core/metadata/**/rules.ts, types.ts и соседние тесты через rg/sed.
- Показывай diff-фрагмент как источник истины. Описание дополняет diff, а не заменяет его.
- Если несколько пунктов похожи на одну категорию, отметь связь, но не объединяй пункты.
- Не создавай ветку, slug, фикстуры, тесты и план исправления. После списка остановись и жди, какую проблему пользователь захочет разобрать отдельно.
Шаг 2. Анализ диффа и предложение плана
Этот шаг выполняется только для одиночного reproducer workflow: после обычного запуска или после round-trip.sh --diff-index N. Не выполняй его для triage-пачки.
Прочитай diff. Определи:
- Целевой модуль — наименьший значимый metadataItem, владелец правила, в котором расходится поведение. Искать в
packages/core/metadata/**; корневой XML-тег расхождения должен совпадать с xmlRootTag правила модуля (или его свойства). Если правило принадлежит подчинённому metadataItem, целевой модуль — подчинённый, не родитель.
- camelCase slug — короткое описание бага на английском, без транслитерации русских имён (slug описывает баг, а не файл). Примеры:
isFolderParentSwap, fillValueXsiTypeLoss, synonymEmptyTagCollapse.
- Границы кластера. Если в одном файле несколько родственных диффов на одном и том же узле с одной причиной — объединяй в одну фикстуру. Если причины разные — один reproducer за прогон, остальное в следующий прогон.
- Планируемые файлы:
- XML:
packages/core/metadata/<module>/__fixtures__/<slug>.xml
- TS:
packages/core/metadata/<module>/__fixtures__/<slug>.ts — экспорт <slug> (camelCase имя = имя файла)
- Планируемые тест-блоки:
it("import <slug>") в fromXML.test.ts
it("export <slug>") в toXML.test.ts
- Имя ветки:
round-trip/<kebab-slug> (преобразование slug из camelCase в kebab-case).
Отправь пользователю одно сообщение со всем планом:
План reproducer'а:
- Модуль: packages/core/metadata/<путь>
- Slug: <camelCaseSlug>
- Ветка: round-trip/<kebab-slug>
- Кластер: <что объединяется / что откладывается>
- Файлы:
- __fixtures__/<slug>.xml (<описание минимального chunk>)
- __fixtures__/<slug>.ts (ожидаемая форма <ТипИзTypes>)
- Тесты:
- fromXML.test.ts → it("import <slug>")
- toXML.test.ts → it("export <slug>")
Подтвердить / скорректировать?
Не создавай файлы до подтверждения. Пользователь может сместить модуль, переименовать slug, пересобрать границы кластера.
Шаг 3. Тупиковые ситуации — стоп + вопрос
Перед шагом 2 или во время него останавливайся и спрашивай пользователя (одним блоком, пронумерованным, с предложенным решением), если:
- Не удаётся однозначно определить владеющий модуль (корневой тег расхождения матчится несколькими правилами / принадлежит коллекции).
- Нельзя безопасно кластеризовать diff (несколько независимых причин).
- Не удаётся построить ожидаемую TS-форму от
types.ts (неизвестное поле, неразрешимая ссылка).
- Коллизия имени:
<slug>.xml уже существует в целевом модуле.
Никаких тихих догадок.
Шаг 4. Создание ветки
После подтверждения:
- Ещё раз проверь
git status — чисто.
- Если ветка
round-trip/<kebab-slug> не существует — git checkout -b round-trip/<kebab-slug> от текущей.
- Если ветка существует —
git checkout round-trip/<kebab-slug> и дописывай в неё (reproducer докидывается к уже имеющимся).
Шаг 5. XML-фикстура — калибровка через toXML
Фикстура должна совпадать байт в байт с тем, что toXML эмитит для TS-формы из шага 6. Руками такой объект собрать трудно (toXML раскрывает все defaultValue*-поля, порядок полей идёт по rules.ts, пустые number — как xsi:nil="true", и т.д.), поэтому собираем в два прохода.
Проход 1 — черновая фикстура
Создай packages/core/metadata/<module>/__fixtures__/<slug>.xml:
- Корневой тег =
xmlRootTag из fromXML.test.ts / toXML.test.ts модуля.
- Внутри — внешняя обёртка (
xr:StandardAttribute name="..." / xr:Parameter name="..." / аналог) + только те узлы, которые воспроизводят diff. Остальное — не заполняй.
- Формат (отступы, переносы, namespaces) — как в соседнем
full.xml / multiple.xml.
- Если
<slug>.xml уже есть в целевом модуле — стоп, вопрос пользователю (шаг 3, коллизия).
Затем создай TS-фикстуру (шаг 6) и тест-блоки (шаг 7) — без них калибровка запустить не может.
Проход 2 — калибровка через export <slug>
Запусти только один тест из корня проекта:
pnpm --filter '@nkdk/core' exec vitest run -t "export <slug>"
Если pnpm --filter не работает в данной конфигурации, перейди в пакет целевого модуля (packages/core — чаще всего) и запусти pnpm exec vitest run -t "export <slug>".
Vitest покажет разницу между Expected (наш черновик) и Received (полный выход toXML). Дальше — по ситуации:
| Ситуация | Что делать с фикстурой |
|---|
Received добавляет дефолтные поля обёртки (xr:LinkByType/, xr:MultiLine>false<, xr:MaxValue xsi:nil="true"/, ...), bug-триггерные узлы совпадают с черновиком | Скопируй Received целиком в <slug>.xml. Это «правильная» форма. |
Received отличается от черновика именно в bug-триггерных узлах | Это означает, что баг сидит в toXML (а не в fromXML). Сохрани в фикстуре правильную версию из исходного 1C XML — export <slug> останется красным до фикса, import <slug> может быть зелёным. |
Received отличается одновременно и по дефолтам, и по bug-триггерам | Скопируй Received и руками откорректируй bug-триггерные узлы на правильную версию из источника. |
Проход 3 — проверка
Перезапусти export-тест:
- Для fromXML-бага:
export <slug> — зелёный, import <slug> — красный (это и есть reproducer).
- Для toXML-бага:
export <slug> — красный на целевом диффе, import <slug> — зелёный.
Если красных тестов больше одного/в неожиданных местах — черновая TS-форма или XML-фикстура собрана неточно; вернись к шагу 6 или повтори калибровку. Не оставляй reproducer с шумом: красной должна быть только сторона, где живёт баг.
См. references/fixture-conventions.md.
Шаг 6. TS-фикстура
Создай packages/core/metadata/<module>/__fixtures__/<slug>.ts с единственным экспортом <slug>, типизированным через satisfies <ModuleType> из types.ts модуля.
- Строй объект от
types.ts, а не от текущего сломанного fromXML-вывода. Это ожидаемая форма после фикса rules.ts.
- Одна фикстура = один файл = один именованный экспорт с именем = slug.
Шаблон — references/fixture-conventions.md.
Шаг 7. Тест-блоки
В fromXML.test.ts и toXML.test.ts модуля добавь по одному it(...) блоку. Структуру describe / rule не дублируй — встраивайся в существующий describe.
Шаблоны — references/test-blocks.md.
Round-trip блок не пиши — пара импорт+экспорт эквивалентна для нашей задачи.
Исключение — form-элементы (forms/elements/<element>/)
У form-элементов (table, inputField, labelField, …) нет собственных fromXML.test.ts / toXML.test.ts. XML-тесты централизованы в packages/core/metadata/forms/elements/__tests__/ как it.each поверх ElementFixtures из fixtures.ts. Для таких модулей схема отличается: reproducer подключается через новую запись в ElementFixtures, калибровка запускается по -t "<slug>". Полные правила — references/form-elements.md.
Шаг 8. Стоп и передача пользователю
После калибровки фикстуры и создания файлов — стоп. Полный pnpm test не запускай, не коммить, не создавай PR.
Финальное сообщение:
Reproducer готов на ветке round-trip/<kebab-slug>.
Файлы:
- packages/core/metadata/<module>/__fixtures__/<slug>.xml
- packages/core/metadata/<module>/__fixtures__/<slug>.ts
Изменённые:
- packages/core/metadata/<module>/fromXML.test.ts (+it "import <slug>")
- packages/core/metadata/<module>/toXML.test.ts (+it "export <slug>")
Калибровка прогнана: `export <slug>` — <зелёный|красный на целевом диффе>,
`import <slug>` — <красный|зелёный>. Баг сидит в <fromXML|toXML|rules.ts>.
Запусти `pnpm test` из корня, убедись, что регрессий нет. Коммит — твоя зона.
Протокол slug'а
- camelCase-английский, 2–4 слова, описывает баг, а не имя XML-файла или путь.
- Один slug → имя XML-файла (
<slug>.xml), имя TS-файла (<slug>.ts), имя TS-экспорта (<slug>), имя ветки в kebab-case (round-trip/<kebab-slug>), текст it("import <slug>") / it("export <slug>").
- Хороший:
isFolderParentSwap, xsiNilFillValueLoss, choiceParameterEmptyCollapse.
- Плохой:
bug1, avansovyjOtchet, standardAttributesIssue (слишком общий).
Политика коммитов
Скилл не коммитит, не пушит, не создаёт PR, не запускает тесты.