Из ~46 навыков-эмиттеров XML восемь не имели ни одной проверки `preserves`:
form-edit, form-remove, meta-remove, skd-edit, template-remove, xdto-edit,
support-edit, cfe-patch-method. Шесть из них правились в предыдущем коммите,
и канон у них держался на том, что правка была механической, а не на проверке.
Ожидания писались ПО КАНОНУ, а не по текущему поведению — и это сразу вскрыло
три дефекта:
1. form-remove очищал слот формы через InnerText="" / text="" — получалась
пустая пара <DefaultObjectForm></DefaultObjectForm>. Платформа пустых пар
не пишет (0 на 476 942 XML). Теперь IsEmpty / text=None.
2. Опустевший <ChildObjects> оставался парой, разнесённой по строкам, —
в PS-порту form-remove и template-remove. Корпус: 1394 самозакрывающихся
<ChildObjects/> на acc+erp, пустых пар 0 в обеих формах.
3. template-add писал пустой макет парой <SpreadsheetDocument></...>.
Во всей выгрузке acc_8.3.27 (65 040 XML) многострочных пустых пар нет
ни для одного тега.
Заодно усилена сама проверка noEmptyPairs: она ловила только СМЕЖНЫЕ теги,
поэтому дефект №2 проходил мимо неё. Добавлен вариант с переводом строки
внутри; дискриминатором служит сам перевод строки — значащий пробельный
текст-узел (<xr:FillValue xsi:type="xs:string"> </xr:FillValue>) его не
содержит и под проверку не попадает.
support-edit покрыт частично (BOM у ParentConfigurations.bin — проверено, что
платформа пишет его с BOM во всех трёх выгрузках), cfe-patch-method — BOM+EOL
у .bsl: хвостовой перевод строки у модулей неканоничен (1235 с ним, 766 без),
поэтому не утверждается.
Регресс: 647/647 ps1, 644/647 py (3 skipped). Эталоны переснятые: три места,
каждое — ровно ожидаемая пара строк.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Конфигуратор пишет <Vendor/>, а не <Vendor></Vendor>: в 8 выгрузках cfsrc
(476 943 XML) пустых пар ноль. Шаблоны подставляли значение ВНУТРЬ пары, поэтому
при пустом значении получалась пара.
Собираем элемент целиком: <Vendor>/<Version> (cf-init, cfe-init),
<DefaultRoles> (cfe-init при -NoRole), <Key> и <Namespace> (meta-compile —
план обмена и веб-сервис). Образец рядом же: <Comment> и <Description> в
meta-compile так делали и раньше.
Дефект был в ОБОИХ портах одинаково — здесь дело не в сериализаторе, а в шаблоне.
Пустая пара <Namespace> в cfe-borrow была не его: он копирует свойства
заимствуемого объекта, а порождал их meta-compile.
Дрейф снэпшотов: 340 <Vendor/>, 339 <Version/>, по одному <Key/>, <Namespace/>,
<DefaultRoles/>. Содержательных изменений ноль. Тесты 641/641.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Конфигуратор не пишет перевод строки в конце файла — последний байт `>`. Это видно
на чистой выгрузке пустой ИБ (Windows и macOS) и на всех 8 выгрузках в cfsrc.
Наши эмиттеры добавляли лишний, причём оба порта и по разным причинам:
- PS: StringBuilder.AppendLine дописывает Environment.NewLine после последней строки;
- PY: '\n'.join(lines) + '\n' — хвост добавлен явно.
В meta-compile заведена единая точка записи Write-XmlFile: там документ собирают
и AppendLine, и явные `n в билдерах Predefined/Content/Flowchart, а выходов семь.
В остальных обрезка стоит в самом вызове записи.
Модули .bsl, JSON-выход form-compile, HTML-справка help-add и XSD из xdto-decompile
сюда НЕ входят: канон Конфигуратора описывает XML метаданных, а у этих артефактов
свои правила. Результаты XmlDocument.Save тоже не трогаются — у них хвоста нет.
Навыки редактирования продолжают сохранять стиль ВХОДНОГО файла: кейс
subsystem-edit/roundtrip-crlf-preserve, где фикстура намеренно с хвостом, остаётся
зелёным. Это контракт #44/#46/#47, и он не отменяется.
Дрейф снэпшотов: 547 файлов, у каждого только последняя строка, содержательных
изменений ноль. Тесты 641/641.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
.NET XmlWriter пишет `<a />`, а Конфигуратор — `<a/>`. Поскольку Save переписывает
файл целиком, meta-edit при добавлении одного реквизита переводил в пробельную форму
все пустые теги документа. Дефект только у PS-порта: lxml (45 из 49 py-портов) уже
пишет плотно, то есть порты расходились побайтово.
Канон измерен, а не предположен: чистая выгрузка пустой ИБ на Windows и macOS плюс
сплошной скан 8 выгрузок в cfsrc — 476 943 XML, 21 294 119 самозакрывающихся тегов,
пробельных 0. Форма не зависит от ОС, версии платформы (8.3.20-8.5) и наличия
атрибутов. Правило уже было реализовано в skd-edit — оттуда и взято.
Замена безопасна доказуемо: .NET экранирует `>` как `>` и в тексте, и в атрибутах,
поэтому ` />` после Save — только конец тега. Лазейки (CDATA, комментарии) в
1С-метаданных не встречаются — 0 из 476 943 файлов; гард на них всё равно стоит.
Одиннадцать скриптов писали прямо в FileStream без пост-обработки — переведены на
MemoryStream, что заодно чинит `encoding="utf-8"` строчными (335 файлов в снэпшотах).
В cfe-borrow правится и сборка Form.xml: куски берутся из OuterXml, а он спацовывает
так же, как XmlWriter.
Дрейф снэпшотов: 12 838 строк тегов + 335 деклараций, содержательных изменений ноль.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Косметика вендора (пустые строки, строки-комментарии) больше не ломает классификацию:
- пустая строка между якорем и уже-перенесённым кодом → раньше ДУБЛЬ, теперь ПЕРЕНЕСЕНО;
- пустая/комментарий у якоря → раньше ложный конфликт, теперь переякоривание.
Три шага разведены:
- размещение якоря — сначала точно (комментарии/пустые включены, держит позицию
вставки относительно стабильного комментария), затем fallback по значимым строкам;
- поглощение — по значимым строкам (пустые/комментарии перешагиваем); вставку из
одних комментариев/пустых не поглощаем;
- вывод тела — всегда v2 дословно, все комментарии/пустые нового оригинала сохраняются.
Пограничный случай (комментарий разработчика у поглощённого кода): значимый код
поглощаем, осиротевший комментарий — строкой ⚠ в отчёте, не роняя в конфликт.
Новые helper: Test-Significant/Get-SignificantProjection (+ py). Верхняя сверка
АКТУАЛЕН остаётся точной (любой diff → перепись тела в v2). Зеркально ps1↔py,
+3 кейса (blankskew/comment-stable/orphan-comment), 22/22 на обоих рантаймах.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Правка, перенесённая вендором в основную конфигурацию, больше не дублируется
и не уходит в ложный конфликт:
- вставка, чей код уже в новом оригинале → раньше ДУБЛЬ, теперь снимается;
- удаление, чей блок уже вырезан → раньше ложный КОНФЛИКТ, теперь снимается.
Обесценивание — свойство операции. Новый статус метода ПЕРЕНЕСЕНО В ОСНОВНУЮ,
когда поглощены все правки (перехватчик можно удалить); при частичном —
АКТУАЛИЗИРОВАН со счётчиками «правок сохранено: N, перенесено в основную: M».
Существующий счётчик «перенесено правок» переименован в «правок сохранено»,
чтобы «перенесено» осталось за поглощением базой. -Check не роняет exit,
если единственное расхождение — перенесённые правки.
Детекция на существующих примитивах (Find-UniqueRun + новые Test-RunAt/
Test-DeleteAbsorbed), только по точному совпадению. Зеркально ps1↔py,
+3 кейса (transferred-insert/delete/partial), 19/19 на обоих рантаймах.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- непереносимый блок показывается в контексте (строки-до/#Вставка/строки-после),
вместо раздельных списков «после:/перед:» + «Блок:»
- нумерация конфликтов: ### Конфликт №N в conflict.md и // [РЕСИНК-КОНФЛИКТ №N]
над припаркованным блоком в .bsl — сопоставление один-к-одному при нескольких
конфликтах в одном методе
- локатор в модуле — по метке №N (grep-стабильно), без номеров строк
- зеркально в .py, снэпшот resync-conflict обновлён
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
По результатам прогона субагентом реального сценария (адаптация метода Бухгалтерии
+ рефакторинг оригинала → конфликт):
- SKILL.md: убрана протёкшая и УСТАРЕВШАЯ реализация — раздел про merge-воркспейс
называл файлы merged.bsl/diff.txt, которых больше нет; раздел «Проверка/актуализация
пачкой» дублировал рантайм-вывод. Оставлено только решенческое (режимы, область,
зона ответственности, зачем проактивно).
- conflict.md: к каждой неразмещённой вставке добавлена привязка к якорю (после/перед из
local) и подсказка «куда переносить» (якорь вынесен/отрефакторен → ищи в диффе новый
вызов, размещай пост-обработкой) + напоминание сохранить BOM. Диагноз дрейфа якоря
теперь виден, не нужно грепать вручную.
- Согласование числительных в итог-строках (было «1 конфликтов»).
Паритет ps1<->py, 16 кейсов зелёные в обоих.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
После обновления КФ контролируемые методы (&ИзменениеИКонтроль) молча уезжают в
рассинхрон — платформа при загрузке не ругается, ошибка лишь в рантайме. Добавлены
два явных режима по всему расширению (или -ModulePath/-MethodName для сужения):
- -Check — отчёт: какие методы дрейфнули (ДРЕЙФ/КОНФЛИКТ/МЕТОД-ИСЧЕЗ), актуальные
числом; ничего не пишет; exit 1 при наличии дрейфа.
- -Actualize — чинит пачкой: авто-перенос + merge-воркспейс на конфликтах.
Одиночный ресинк вынесен в общую функцию resync_one (report_only), одиночный путь и
батч используют её. Зона ответственности узкая — только тело &ИзменениеИКонтроль.
Merge-воркспейс переработан: тонкий index.md (список конфликтов + пути к .bsl расширения)
+ подпапка на метод (conflict.md с блоком/диффом + base/local/remote), без общей портянки.
cfe-validate: крошка-указатель [INFO] при наличии контролируемых методов -> /cfe-patch-method -Check.
Тесты: +check-clean/check-drift/actualize-batch. 16 кейсов, оба рантайма зелёные,
байтовый паритет ps1<->py. verify-snapshots (реальная 1С) Windows ps+py — 16/16.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Упрочнение актуализации &ИзменениеИКонтроль (v2.1):
- Якорь вставки теперь двусторонний (контекст до+после, окно 3) с расширением:
Тир A — уникальная смежная пара; Тир B — одиночная уникальность before/after.
Срезает ложные конфликты, когда строка перед вставкой generic/повторяется
(пустая, КонецЦикла;, КонецЕсли; и т.п.), сохраняя безопасность (не уверены → конфликт).
- Прозрачность: на [АКТУАЛИЗИРОВАН] и ЧАСТИЧНО печатается сводка перенесённого.
- Файлы-версии переименованы в конвенцию git-mergetool: base/local/remote/merged
(+diff base->remote) вместо v1/v2/current; merged.bsl добавлен. Комментарий в
модуле и SKILL.md обновлены.
Тесты: +resync-reanchor (generic-строка -> авто), resync-conflict переделан на
настоящий конфликт (окружающий блок исчез). 13 кейсов, оба рантайма зелёные,
байтовый паритет ps1<->py включая merge-воркспейс.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Навык переписан в v2.0: вместо шаблона-заглушки читает оригинал метода из
конфигурации-источника и генерирует корректный каркас.
Генерация:
- новый -ConfigPath (опционален, если ModulePath — путь к файлу .bsl модуля);
- наследование директивы контекста, полной сигнатуры, обрамляющих #Если и
#Область (в исходном порядке; регион переиспользуется, если уже есть);
- тип Instead (&Вместо с ПродолжитьВызов); гвард: Before/After только для процедур;
- ModAndControl копирует всё тело оригинала;
- воздух (пустые строки) вокруг структурных границ и между методами;
- имя с суффиксом типа только при коллизии;
- убраны -Context/-IsFunction (выводятся из оригинала).
Актуализация (повторный ModAndControl): предок восстанавливается из маркеров,
однозначные правки #Вставка/#Удаление переносятся авто, спорные — [РЕСИНК-КОНФЛИКТ]
плюс файлы-версии v1/v2/current/diff. Статусы АКТУАЛЕН/АКТУАЛИЗИРОВАН/ЧАСТИЧНО.
Паритет ps1<->py (raw-кириллица в .py). Тесты: 12 кейсов, оба рантайма зелёные.
runner.mjs: шаг writeFile теперь делает mkdir -p.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Единый дефолт Managed вместо per-type Automatic/Managed — удобно для создания
новых объектов (Automatic задаётся явно). Роундтрип остаётся байт-в-байт:
дефолт компилятора и порог omit-on-default декомпилятора сдвинуты синхронно —
объекты с Automatic теперь несут ключ в DSL явно, с Managed — опускают,
итоговый XML с обеих сторон не меняется. Зеркало ps1+py в обоих навыках.
Заодно вылечена латентная рассинхронизация AccountingRegister/CalculationRegister
(компилятор Automatic vs порог Managed).
Верификация: роундтрип-инвариант на реальном корпусе 7/7 match, 0 диффов
(Automatic+Managed справочники, ПС, Sequence, РБ); полный набор тестов 514/514;
пересъём снэпшотов — 91 файл, дрейф ровно DataLockControlMode Automatic->Managed,
ноль посторонних; py-паритет.
meta-compile v1.63 / meta-decompile v0.54
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- QuickChoice<true→false> для Catalog (07b2ec3 «дефолты QuickChoice по реальным
конфигам» — часть снапшотов уже обновили в dc0382c, эти были пропущены)
- автоген <Title>/<AutoTitle>/<TitleLocation>/<SavedData> в формах
(76800fc «автоген Title из имени» и серия фич form-compile)
- column-group/ — новый снапшот для кейса из 4f9d9ae (был не закоммичен)
Все 349 unit-тестов и 6 integration-тестов зелёные. verify-snapshots
(платформенная загрузка) — 201/202; отдельный pre-existing fail
cf-edit/set-home-page (Check 9 не валидирует HP form refs) разбираем
отдельно, в дифф этой регенерации не входит.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- HorizontalAlign не эмитится, если не задан явно через autoCmdBar.horizontalAlign.
Раньше был хардкод Right; платформенный дефолт — Left, эталонные формы
типовых (Бригады/ФормаСписка) тег вообще не содержат.
- horizontalAlign принимает Left/Center/Right.
- SKILL.md: убран раздел «Эвристики компилятора» (внутренняя кухня скрипта),
сжато описание autoCmdBar и main — модель пишет DSL явно, помощь
компилятора видна в [INFO]/[WARN]-логе.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- form-add v1.2: ExtendedPresentation only for DataProcessor/Report/External* forms
(Catalogs, Documents, Registers etc. don't have this property — platform rejects it)
- cf-init v1.1: InterfaceCompatibilityMode Taxi → TaxiEnableVersion8_2
(matches all real configs: acc 8.3.20/24/27, erp 8.3.24)
- cfe-init v1.1: read InterfaceCompatibilityMode from -ConfigPath base config
(analogous to existing CompatibilityMode auto-detection)
- Remove workaround in platform-cfe integration test (cf-edit modify-property)
- Update 162 snapshot Configuration.xml + 7 form metadata snapshots
Tests: 301/301 passed
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- runner.mjs v0.4: --with-validation flag runs validators on real output
- postValidate config in 20 _skill.json files (maps skill → validator)
- validatePath in ~100 positive test cases
- skipValidation for 5 cross-reference cases (isolated workspace limitation)
- Integration tests: build-config (19 steps), build-epf (6), build-cfe (4)
- base-config cache from build-config for downstream tests
- Fix chart-of-calculation-types test data (DependenceOnCalculationTypes)
- 285/285 unit + 3/3 integration, all green with validation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add 195 new test cases covering examples from SKILL.md, edge cases,
and parameter combinations. Create _skill.json for form-edit, skd-edit,
subsystem-edit. Add fixtures for negative validate cases. Fix
normalizeUuids in meta-validate/meta-info configs.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>