xdto-compile терял свойства без единого слова: на реалистичной чужой схеме
из шести объявленных доезжало одно. Вложенные xs:sequence/xs:choice теперь
уплощаются (модель хранит плоский список), xs:all трактуется как
последовательность, xs:group и xs:attributeGroup раскрываются по ссылке —
и о каждом приближении навык пишет предупреждение. Молчаливая потеря — тот же
класс дефекта, что мы ловим у платформы, лечится так же: сообщением, не отказом.
xdto-validate получил проверки на грабли, найденные при разработке: порядок
элементов верхнего уровня (платформа отвергает пакет, не называя причины),
конфликты объявлений (name+ref, type+вложенный тип, тип без разновидности),
несовпадение рода базового типа, дубли имён свойств.
Новые правила прогнаны по всем 760 пакетам выгрузок: всё, что породила
платформа, валидно по определению, поэтому каждая ошибка там — ошибка правила.
Первый прогон дал 7, и все три класса оказались реальным поведением платформы:
length вместе с minLength/maxLength встречается, два пакета делят один
targetNamespace (Envelope и SOAP_Envelope_1_1 в БП), form="Text" называется
не только __content. Правила понижены до предупреждений либо сняты. Заодно
убран шум: предупреждение о неиспользуемом import срабатывало на четверти
корпуса — теперь только вместе с anyType, где оно и означает проблему.
Итог: 0 ошибок на корпусе, предупреждений 53 вместо 242.
Инструкции переписаны под читателя-исполнителя: убраны детали реализации
и наши мерки, каталог проверок валидатора (его вывод самодостаточен),
локальные пути в примерах заменены нейтральными. Таблица соответствий
XSD и справочник аннотаций вынесены в xdto-compile/xsd-reference.md.
Round-trip 760/760 сохранён, паритет PS/PY сохранён.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Три навыка для работы с пакетами XDTO. Формат описания — обычная XSD,
своего DSL нет: рутину снимает конвертер (локальные объявления префиксов
dNpM на каждой ссылке, инвертированная кратность lowerBound/upperBound,
фасеты атрибутами вместо дочерних элементов, обязательный порядок
элементов верхнего уровня). То, чего XSD выразить не может — nillable
у атрибута, qualified у свойства, «атрибут записан явно» — едет
атрибутами из пространства имён модели XDTO по правилу «то же имя,
что в Package.bin». Свойства объекта метаданных живут в xs:appinfo,
поэтому пара decompile → compile замыкается без потерь.
Инвариант bin → xsd → bin проверен побайтово на 760 пакетах выгрузок
Бухгалтерии и ERP 8.3.24 (харнесс debug/xdto/roundtrip-corpus.mjs).
Сборка из рукописной XSD проверена загрузкой в базу 8.3.24 — именно
она вскрыла обязательный порядок import→property→valueType→objectType,
невидимый для корпусной сверки: все выгрузки уже канонические.
xdto-validate ловит два класса тихих дефектов, которые платформа не
диагностирует: подмену неразрешённого чужого типа на xs:anyType при
импорте XML-схемы и nillable у свойства-атрибута, теряемый экспортом
схемы в Конфигураторе.
Тесты: 18 снэпшот-кейсов на синтетических схемах (типовые конфигурации
в репозиторий не тащим), паритет PS↔PY на общих эталонах. Раннер
получил caseFiles — копирование файлов кейса в workDir для навыков
с файловым, а не JSON входом.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Две проверки, обе про формат 2.20.
Проверка 18 — реестр versionedProps «тег → минимальная версия формата». Если
свойство присутствует в файле со слишком старым штампом, при сборке на платформе
той версии оно будет молча отброшено: платформа рапортует успех (exit 0), а
свойство теряется — проверено экспериментально на 8.3.24. Реестр расширяется
одной строкой на свойство и служит заделом под 2.21 (8.5) и последующие: он же
подсказывает, что конструкция требует более нового формата.
Проверка 19 — LineNumberLength вне диапазона 5..9 (границы из документации 1С).
Компаратор версий числовой по компонентам: строковое сравнение дало бы
"2.9" > "2.17".
Кейсы: error-lnl-out-of-range, error-220-props-in-217. Регресс 25/25 ps1+py.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Версию формата задаёт ПЛАТФОРМА выгрузки, а не режим совместимости: одна и та же
БП с режимом Version8_3_24 даёт 2.17 на платформе 8.3.24 и 2.20 на 8.3.27.
- cf-init: параметр -FormatVersion (2.17|2.20|2.21, дефолт 2.17 — читается всеми
платформами). Конфигурация создаётся с нуля, наследовать не от чего; выводить
версию из CompatibilityMode было бы неверно. Без параметра нельзя было собрать
2.20-проект — в том числе для тестовых фикстур.
- cf-edit: шаблон Ext/HomePageWorkArea.xml нёс жёстко вписанный version="2.17",
то есть в 2.20-конфигурации создавал файл чужой версии. Теперь наследует версию
из редактируемого Configuration.xml (образец — cfe-init, который так уже умеет).
epf-init/erf-init/cfe-init не трогаем: у первых двух наследовать не от чего
(внешние объекты живут вне конфигурации), cfe-init уже наследует от базовой
конфигурации корректно.
Регресс cf-init 6/6, cf-edit 12/12 — ps1 и py.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Дельта формата 2.17→2.20 содержит три безусловных свойства, которых компилятор
не эмитил. Все три пишутся ТОЛЬКО при формате >= 2.20 (Detect-FormatVersion),
поэтому 2.17-проекты не меняются: полная сюита зелёная, ни один существующий
снэпшот не сдвинулся.
- xr:TypeReductionMode — каждому стандартному реквизиту, после CreateOnInput.
TransformValues, кроме Owner → Deny (правило проверено против выгрузки acc:
9 из 9 реквизитов совпали, включая Owner).
- TypeReductionMode — измерениям регистра СВЕДЕНИЙ (у прочих семейств и у
реквизитов/ресурсов платформа его не пишет).
- LineNumberLength — табличным частям, последним в Properties.
LineNumberLength — прикладная возможность 8.3.27 (5..9 → до 999 999 999 строк
вместо 99 999), поэтому получил полноценный DSL-ключ и описание в spec §5.2.
Его дефолт зависит НЕ от версии формата, а от режима совместимости на момент
создания ТЧ (<=8_3_26 → 5, >=8_3_27 → 9) — платформа фиксирует значение и позже
не пересчитывает, поэтому в одной конфигурации соседствуют ТЧ с 5 и 9. Отсюда
новая Detect-CompatibilityMode: читает CompatibilityMode из Configuration.xml
(префикс 64 КБ — тег лежит на ~11-12 КБ, существующим 2000 байт не хватает).
Декомпилятор: TypeReductionMode захватывается только при отклонении от правила
(компилятор выводит его сам), LineNumberLength — всегда при наличии тега:
выводить его дефолт значило бы дублировать логику компилятора с риском разойтись.
Компараторы версий числовые по компонентам — строковое сравнение неверно
("2.9" > "2.17" лексикографически).
Тест-инфра: setup-фикстуры empty-config-220 и empty-config-220-compat24
(строятся тем же cf-init), два кейса — по одному на каждую ось.
Проверка: роундтрип реального 2.20-документа БП (АвансовыйОтчет, 7 ТЧ) —
по новым тегам 0 расхождений, значения и позиции совпали; остаточный хвост
52/39 идентичен такому же на 2.17, то есть пред-существующий. Сюита 570/570
ps1, 567+3 skipped py, ps1==py. 1С-сертификация обоих кейсов на 8.3.27 ✓.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Таблица «Все поля кейса» отставала от раннера: не были описаны idempotent,
runtimeOnly, skipValidation, а expect ограничивался упоминанием files/
stdoutContains/stdoutNotContains — preserves и структура preRun не
документировались вовсе.
Из-за таких пробелов формат кейса приходится выяснять по коду — а это ровно
тот способ, который однажды дал 9 кейсов meta-edit с несуществующим ключом:
тесты зелёные, навык no-op, снэпшот фиксирует исходник.
Добавлено (сверено с runner.mjs и с реальными кейсами):
- idempotent, runtimeOnly, skipValidation в основную таблицу;
- таблица ключей expect + вложенная таблица preserves (file/bom/eol/encoding/
finalNewline/noCR13) с пометкой, что preserves и эталон дополняют друг друга:
первый следит за байтовым стилем, второй за структурой;
- формы шагов preRun (прогон навыка и writeFile).
editFile намеренно не описан — это шаг интеграционных тестов, не preRun кейса.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
compareSnapshot при отсутствии каталога эталона возвращал {match:true,
reason:'no snapshot (skipped)'}, причём reason никуда не выводился. Кейс без
эталона был молча зелёным, а «намеренно нет» и «эталон потерялся / не создан
при добавлении кейса» — неразличимы. README закреплял это как штатное
(«совпадает со snapshot (если есть)»).
Теперь эталон обязателен везде, кроме expectError, readonly external: и явного
opt-out. Диагностика — на месте кейса, с готовой командой; сводной статистики
не добавляем (вне контекста она ничего не сообщает).
- noSnapshot: "<причина>" — легальный пропуск. Причина обязательна: отключение
сверки должно стоить автору формулировки, а ревьюеру быть видно в diff'е;
осмысленность причины рантайм проверить не может. true/"" → падение.
- Нет эталона и нет opt-out → падение с рецептом (команда --update-snapshots
либо подсказка объявить noSnapshot).
- Мёртвый эталон (noSnapshot + существующий каталог) → падение: не сверяется,
но выглядит покрытием.
- updateSnapshot пропускает кейсы с noSnapshot — иначе --update-snapshots сам
порождал бы противоречие. Опечатка в имени поля fail-safe: opt-out не
сработает, кейс упадёт как «эталон отсутствует».
- Диагностика вынесена в общий snapshotErrors() — обе ветки (runCase /
runCaseAsync) больше не дублируют логику.
Размечены 3 кейса meta-validate: навык только читает и печатает, эталон
зафиксировал бы выход preRun (meta-compile), а не проверяемого навыка.
Проверка: до разметки сюита падала ровно на этих 3 кейсах (независимое
подтверждение аудита). Негативные сценарии проверены все пять: потерянный
эталон, мёртвый эталон, noSnapshot без причины, update на opt-out кейсе
(не создаёт), update на обычном (создаёт байт-в-байт прежний).
Полная сюита 566/566 ps1; python 563 passed + 3 skipped — идентично HEAD.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Кейсы проверяли только БАЙТОВЫЙ стиль файла (expect.preserves: BOM/CRLF/
encoding/finalNewline/noCR13) и что валидатор не ругнулся. Что в CRLF-файл
записан КОРРЕКТНЫЙ XML, не проверял никто: снэпшота не было, а
compareSnapshot при отсутствии эталона молча возвращает pass.
Снэпшот ортогонален preserves — сравнивается нормализованное содержимое
(структура), preserves остаётся на байтовых характеристиках. Дублирования нет.
Регресс 12/12, 20/20, 6/6 — ps1 и py. 1С-сертификация 3/3.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Значения xsi:type="xr:MDObjectRef" не проверялись вообще. Ошибка «тип ссылки
вместо объекта метаданных» обнаруживалась только платформой при загрузке
(«Неизвестный объект метаданных»), причём в логе, а не в коде возврата.
Проверка 17 по первому сегменту пути (переиспользован $validTypes +
$structuralOnlyTypes):
- сегмент оканчивается на Ref → Error: вида метаданных с таким именем
не существует, ссылка гарантированно нерабочая; в тексте подсказана
исправленная форма;
- неизвестный сегмент без Ref → Warn (список видов может быть неполон).
Ловит дефект статически, без платформы, независимо от происхождения файла.
Кейс error-mdobjectref-type-form + фикстура. Регресс 23/23 ps1+py.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Та же дыра, что и в meta-compile, но здесь нормализации не было вообще —
Normalize-MDObjectRef отсутствовала как функция. set-owners "CatalogRef.Валюты"
(или modify.properties.Owners) записывал неверную ссылку молча.
- Перенесена мапа корней + Normalize-MDObjectRef (зеркало meta-compile).
- В complexPropertyMap добавлены флаги mdref/root; нормализация подключена
в Add-/Remove-/Set-ComplexPropertyItem рядом с существующим expand.
Покрывает Owners, RegisterRecords, BasedOn, RegisteredDocuments.
- References графы журнала документов — эмитились напрямую, тоже нормализуются.
Инструкция навыка не менялась: SKILL.md и json-dsl.md уже показывают
каноническую форму Catalog.Контрагенты.
Кейс modify-property-mdobjectref (документированный путь modify.properties).
Регресс 20/20 ps1+py, 1С-сертификация снэпшота пройдена.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
MDObjectRef ссылается на ОБЪЕКТ метаданных (Catalog.Валюты), а не на тип ссылки.
Эмиттеры пропускали значение как есть, если в нём была точка, поэтому
"CatalogRef.Валюты" доходил до XML без изменений → при загрузке конфигурации
платформа отвечала «Неизвестный объект метаданных».
Инструкция вела в баг сама: reference/catalog.md документировал
owners: ["CatalogRef.Контрагенты"]. Тестами не ловилось — все кейсы
использовали каноническую форму.
- Normalize-MDObjectRef расширена ссылочными формами (англ. *Ref + рус. *Ссылка);
вида метаданных, оканчивающегося на Ref, не существует → схлопывание однозначно.
В ТИПАХ реквизитов запись CatalogRef.X верна — там мапа не применяется.
- Добавлен параметр defaultRoot (голое имя без точки), инлайн-подстановка
"Catalog.$ownerRef" в owners убрана — логика теперь в одном месте.
- Нормализация применена в 4 местах, где её не было: owners, basedOn,
registerRecords, baseCalculationTypes.
- Кейс catalog-inputbystring-datalock переведён на неканонический вход:
снэпшот не изменился ни на байт — прямое доказательство нормализации.
Регресс 73/73 ps1+py, полная сюита 566/566. Живая проверка на 8.3.27:
подчинённый справочник с owners CatalogRef./СправочникСсылка. грузится чисто.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
form-add и template-add вставляли запись в ChildObjects безусловно.
Если форма/макет уже зарегистрированы (например, form-compile
регистрирует <Form>, не создавая файл метаданных, а затем вызывается
form-add) — возникал дубль <Form>/<Template>, ломавший валидацию.
Приведено к идемпотентной модели, уже применённой в form-compile и
meta-compile: перед вставкой ищем существующую запись по имени; при
наличии — пропускаем и печатаем "Already registered ... (skipped
duplicate)". Зеркально в ps1 и py, регрессионный тест-кейс.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Оси таблицы (columns/rows), точки/серии диаграмм и объектные группировки без
явного selection получали пустой пивот молча — ресурсы не попадали в ячейки
пересечения. Теперь при отсутствии ключа selection/order эмитится
SelectedItemAuto/OrderItemAuto (как строковый shorthand и как ручное добавление
оси в Конфигураторе). Пустой [] уважается как «явно ничего».
skd-decompile теперь эмитит [] для отсутствующих selection/order на осях,
группах и диаграммах — decompile→compile round-trip остаётся бит-в-бит (иначе
compile впаял бы Auto на боевых узлах без выбора, напр. ветках use=false).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Два задокументированных ассерта бросали ВСЕГДА, то есть были мертвы:
- formTitle читал state.title, которого не заполнял никто: getFormStateScript
собирал форму без заголовка. Единственным носителем оставалась панель
открытых окон (activeTab), а она отключается в настройках 1С — на такой базе
заголовок был недоступен ничем.
- formHasField читал state.fields[name], хотя fields — массив объектов
{name, value, …}. На массиве это всегда undefined.
getFormState теперь отдаёт title. Берётся он из шапки самой формы: заголовок
лежит в атрибуте (title у .toplineBoxTitle, data-title у родителя), сам элемент
пустой — поэтому поиском по тексту он и не находился.
Выбор шапки — не «первая видимая»: при открытом всплывающем окне видимы ДВЕ,
родителя и окна, и наивное правило отдавало заголовок родителя — правдоподобный
неверный ответ, при котором тест «окно выбора открылось» зеленел бы по
документу. Приоритет взят тот же, что уже отлажен для крестика закрытия в
closeCrossScript: плавающее окно ps<N> с наибольшим индексом → собственная шапка
формы → и только потом панель открытых окон. Привязка к id, а не к тексту — не
ломается на другой локали. Панель осталась последним звеном: она отключаема, а
при всплывающем окне ещё и показывает родителя.
Диагностика раннера (resetState) тоже переведена на title с прежним activeTab
как запасным.
formHasField ищет по массиву и перечисляет доступные имена в ошибке (раньше
Object.keys по массиву давал индексы). formTitle отличает «заголовок недоступен»
(title === null) от несовпадения.
Почему не поймали раньше: из 12 ассертов сюита вызывала 8, и оба сломанных были
среди четырёх невызываемых. Теперь все четыре задействованы на настоящем выводе
getFormState — formTitle/formHasField/noErrors в 12-formstate (включая случай
всплывающего окна), tableRowCount в 09-filter. Отдельного юнит-теста намеренно
нет: состояние для него пришлось бы писать руками, а именно неверное
представление о форме состояния и породило оба дефекта.
Доки приведены к массиву: примеры вида s.fields['X']?.value в regress.md и в
спеке заменены на fields.find(f => f.name === 'X').
Проверено: заголовок на списке, форме элемента и всплывающем окне; каскад
разведён по значениям (шапка выигрывает у панели, при пустой шапке — откат);
позитив и негатив всех четырёх ассертов на реальном состоянии формы; полный
регресс 29/29 до и после, file/name/status идентичны.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
filterList отчитывался успехом, пока динамический список ещё выполнял поиск:
readTable возвращал предыдущие строки, а следующий клик по строке попадал в
чужой объект. Отличить такой результат от верного нельзя — сценарий проходит
зелёным по не тому документу.
Дыра оказалась не в filterList, а в общем ожидании. waitForStable следил только
за .loadingImage/.waitCurtain/.progressBar и счётчиком полей ввода — ни то, ни
другое не меняется при перерисовке строк списка. Замер на реальной базе: за весь
поиск старый признак isLoading не сработал НИ РАЗУ.
При этом 1С всё это время показывает над списком информбар «Поиск...» — тот же
.stateWindowSupportSurface, который движок уже отдаёт в errors.stateText. Читать
умели, ждать — нет.
waitForStable теперь считает видимый маркер занятости признаком «не готово»:
счётчик стабильности сбрасывается, дедлайн продлевается, пока маркер виден, но
не дольше BUSY_MAX_WAIT (60 с) — реальный поиск на боевом списке идёт десятки
секунд, а зависшая операция всё равно завершает ожидание.
Маркеры сопоставляются ПО ТЕКСТУ (Поиск/Ожид/Searching/Please wait), а не по
факту наличия информбара: тот же носитель несёт терминальные сообщения отчётов
(«Отчет не сформирован», «Не установлено значение параметра»), и ожидание их
исчезновения вешало бы каждый отчёт до таймаута.
Радиус общий, а не точечный в filterList: маркер — индикатор длительной операции
вообще, тот же класс гонки достижим из clickElement и openCommand. Частное
лечение для кнопок (CDP-монитор в click-form) уже есть; второй костыль сделал бы
третий неизбежным. CDP-монитор как гейт не годится отдельно: он считает
готовностью паузу 300 мс без запросов, а при фоновой операции с периодическим
опросом такие паузы штатны.
Проверено на реальной базе: текст информбара — «Поиск...», виден t=6.7..13.2 с,
опрос, идентичный гейтовому, увидел занятость дважды (isLoading — ноль раз);
полный регресс 29/29 до и после, file/name/status идентичны, суммарное время
879.9 → 859.0 с, отчёты не зависли.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Инструкция навыка описывает использование: конфиг и хуки берутся из корня
сьюта при любом переданном пути, разные сьюты в одном прогоне отвергаются.
Маркеры подъёма и ограничители (.git / .v8-project.json / cwd) — контракт
реализации, их место в спеке; читатель инструкции о них не спросит.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Конфиг и хуки резолвились строго от каталога первого позиционного пути, поэтому
запуск подкаталога сьюта был невозможен: `test tests/app/00-smoke/` падал с
«No URL provided and no webtest.config.mjs found» — хотя спека прямо обещает
запуск подкаталога («Фильтр по пути с CLI»).
Опаснее отказа по URL были два молчаливых следствия: при `--url=` прогон
подкаталога терял `_hooks.mjs` и ехал по неподготовленному стенду без единого
предупреждения, а `_allure/` не находился. Плюс `file:` в отчёте считался от
переданного пути, из-за чего один и тот же тест получал разный ID в зависимости
от способа запуска и рвал историю Allure/JUnit.
Введён корень сьюта: подъём от каталога пути до первого `webtest.config.mjs`
ИЛИ `_hooks.mjs` (конфиг необязателен — сьют только с хуками иначе снова терял
бы подготовку), с ограничением подъёма каталогом `.git`/`.v8-project.json`, а
при их отсутствии — cwd. Граница ничего не выбирает, только останавливает, так
что ложная граница даёт «корень не найден», а не чужой корень. От найденного
корня берутся все пять ролей: конфиг, хуки, каталог отчёта, пути в отчёте,
`_allure/`.
Попутно: пути из разных сьютов в одном прогоне теперь отвергаются (раньше
молча выигрывал первый путь, и сьют B ехал по подготовке сьюта A); найденный
корень печатается в шапке; отсутствие корня — предупреждение в stderr;
диагностика говорит про корень сьюта, а не только про URL.
Проверено: 12/12 офлайн-кейсов резолвера; полный регресс 29/29 до и после —
`file`/`name`/`status` идентичны; `_suite-root/nested/` (сценарий, который
падал) проходит с подхваченными конфигом и хуками; `_hang/` 6/6.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Постусловие непустого каталога валидировалось только на Full/Partial. Новый
1cv8-тест гоняет Changes (в существующий дамп) и UpdateInfo (в свежий каталог) →
подтверждает, что режимы дают реальный выход и постусловие не даёт ложного
падения. UpdateInfo проверяется ассертом наличия ConfigDumpInfo.xml.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Прежняя маскировка (^/N|/P по токену) на *nix цепляла путь, начинающийся с
заглавной /N или /P (напр. /Projects, /Numbers) — косметическая пере-маскировка
в строке Running. Заменено на редактуру конкретных значений (пароль/пользователь)
через литеральную замену: секрет скрывается везде, где встречается, а похожие на
флаг пути не трогаются. 11 навыков, оба порта.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Пропущенный при разборе #49-52 навык того же класса: epf-dump разбирает EPF/ERF
через платформу в каталог XML. Успех определялся только по коду возврата (ложный
успех при пустом выходе), а строка Running светила /P<пароль> (1cv8) и --password=
(ibcmd). Добавлены postcondition непустого OutputDir и маскировка, обе ветки,
оба порта. Покрывает и erf-dump (общий скрипт).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Убрана спекулятивная гипотеза причины (headless/GUI/лицензия) из сообщения:
краш сигналом/exception может быть вызван чем угодно, а зашитая догадка
заякоривает модель-координатора на неверном диагнозе. Оставлены только факты
(сигнал/exception-код, признак аномального завершения), следствие (ИБ может
быть несогласованна) и нейтральное действие (verify before retrying).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
cfe-patch-method стал source-aware: читает оригинал метода из -ConfigPath.
Тест не передавал -ConfigPath и опирался на пустой ObjectModule источника →
шаг перехвата падал «Не указан -ConfigPath». Добавлен seed-шаг с процедурой
ПриЗаписи в исходный модуль + -ConfigPath в вызов перехватчика.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Навыки печатали полную командную строку платформы, включая /P<пароль> (1cv8)
и --password= (ibcmd), в диагностику. Добавлен per-token маскер (/N, /P,
--user=, --password= → ***); привязка к началу токена не трогает пути.
Оба порта, обе ветки движка, все 9 навыков с параметрами подключения.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Кейсы с .cmd-заглушкой платформы (Start-Process исполняет .cmd) проверяют, что
db-create/db-run/db-dump-cf не рапортуют успех, когда платформа вышла с 0/умерла
без артефакта. Гейт runtimeOnly пропускает кейс на несовместимом порту (py
list-exec не запускает .cmd) — гоняются под powershell на Windows.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Мутирующие навыки не производят одиночный артефакт, но при крахе платформы
(нет GUI-сессии/лицензии) возвращали голый код вроде -11. Добавлен аннотатор:
POSIX-сигнал (напр. -11 → SIGSEGV) и Windows exception-код (напр. 0xC0000005) →
внятное сообщение с предупреждением о возможной несогласованности ИБ. Без
ожидания фоновых процессов и без ps-скрейпинга.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Тот же класс ложного успеха, что и в db-create: exit 0 без реального результата.
Добавлен postcondition на выходной артефакт — файл ненулевого размера для
db-dump-cf/db-dump-dt/epf-build (покрывает и erf-build через общий скрипт),
непустой каталог для db-dump-xml. Обе ветки движка (1cv8 и ibcmd).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Код возврата launcher-а сам по себе не доказывает, что файловая ИБ создана:
в неблагоприятной среде платформа может вернуть 0, не создав ничего. Добавлен
postcondition — для файловой ИБ проверяется наличие ненулевого 1Cv8.1CD (обе
ветки: 1cv8 и ibcmd); при его отсутствии — честная ошибка и ненулевой код.
Серверная ИБ не проверяется (нет файла для stat).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Раньше db-run безусловно печатал «launched» сразу после запуска, не отличая
успешный фоновый старт от мгновенного падения (нет дисплея/лицензии). Теперь
короткое контрольное окно ловит ранний выход → ненулевой код без «launched»,
иначе печатается PID. Строка Running маскирует /N и /P (не светить пароль);
маскировка привязана к границе токена, чтобы не портить путь с сегментом /N|/P.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Прошлый коммit сломал build-ports.yml: GHA-выражение с бэктиками/двоеточием/
кавычками в env: ломало парсинг workflow (run failed, 0s). Переношу вычисление
строки в bash-шаг по matrix.runtime — надёжно к спецсимволам.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Добавлен requirements.txt (lxml/Pillow/psutil) — единый pip-манифест py-рантайма.
build-ports.yml копирует его в build только для python-сборок (в PS-порты не попадает)
+ добавлен в paths-триггер. Порт-README: блок «Требования» стал runtime-условным
(плейсхолдер {{RUNTIME_REQUIREMENTS}}) — py-вариант даёт `pip install -r requirements.txt`,
PS-вариант больше не упоминает python-deps. Заявленный минимум исправлен 3.10+ → 3.9+
(все скрипты компилируются на 3.9.6, регресс зелёный). Main README: команда установки
в py-секцию.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
ConfigDumpInfo.xml — служебный файл версий выгруженных объектов, управляемый
платформой (для инкрементальной ВЫГРУЗКИ, db-dump-xml Changes). При загрузке не
используется; некорректные записи в нём только помешали бы. configVersion
вычисляет только платформа — руками не сгенерировать. meta-compile его намеренно
не трогает: посылка #45 неверна.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Корневой modify-property Type у ПВХ/Константы расплющивал структурный <Type>
(<v8:Type> + квалификаторы) в скалярный текст, а meta-validate это пропускал.
meta-edit (v1.21): modify-property Type перестраивает дескриптор через готовый
build_value_type_xml (составной тип, квалификаторы, ref-типы); прочие структурные
свойства с дочерними узлами → ошибка до записи файла вместо тихой порчи.
meta-validate (v1.10): корневой <Type> со скалярным текстом без <v8:Type>/<v8:TypeSet>
теперь ошибка (был false negative).
Порты PS1/PY синхронны. Регрессионные кейсы: modify-property-type-pvh (структурный
Type + ref), error-scalar-root-type (детект порчи).
Co-Authored-By: Claude Opus 4.8 <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>
- description: «Генерация и актуализация…», без потери триггер-слов
- Типы перехвата: применимость к процедурам/функциям вынесена в колонку
- Две секции актуализации слиты в одну, телеграф переписан ровным тоном
- «КФ» → «конфигурация-источник» (единый термин)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Принцип: в SKILL.md — только то, чего модель НЕ видит в выводе/результате.
- Убрано: «контекст/сигнатура/обрамление определяются автоматически» (параметров
для них нет, результат виден); секция «Что переносится из оригинала» целиком
(всё видно в сгенерированном коде); устаревшие имена файлов воркспейса.
- Добавлено: раздел «Маркеры #Вставка/#Удаление» — синтаксис и семантика, которые
модель пишет сама и из копии тела не считает (удаляемые строки остаются между
маркерами, 0-я колонка, unmarked = дословно оригинал = суть контроля, пример).
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>
Реплей preRun-шага writeFile в verify-snapshots.mjs писал файл без mkdir -p
(в runner.mjs фикс уже был). Кейсы, пишущие в ext/ (cfe-borrow общего модуля
не создаёт ext/.../Ext/), падали с ENOENT. Зеркалит фикс из runner.mjs.
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>
Popup-группа надёжно отличается от сворачиваемой по DOM-маркеру панели
<base>#panel_div (+ #CloseBtn). В getFormState().groups она помечается
behavior:'popup', а её collapsed берётся из display панели (закрыта =
collapsed:true), не из инлайн-сиблинга (у popup содержимое в отдельном
слое, а не под mainGroup).
clickElement по заголовку popup и открывает, и закрывает — тот же
словарь {expand}/{toggle}, что и у сворачиваемых. После открытия
содержимое панели становится читаемым в getFormState (fields/
hyperlinks/texts). Тест 25-decoration-form покрывает popup; SKILL
дополнен. Полный регресс 29 passed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
getFormState().groups → [{name, title, collapsed}] для сворачиваемых
групп (оба варианта ControlRepresentation: заголовок-гиперссылка и
картинка-каретка #titleBtn). Обычные несворачиваемые группы не
попадают. Состояние — по display первого контент-сиблинга за #title_div
(переживает свободные элементы между группами: при обходе Form.xml дети
группы идут до следующего сиблинга).
clickElement(title, {expand}/{expand:false}/{toggle}) раскрывает/
сворачивает группу — единый словарь с грид-узлами/деревьями, клик по
#titleBtn (вариант «картинка») или заголовку-гиперссылке. Новый
kind:'formGroup' в findClickTargetScript + хендлер click-group.mjs.
Фикстура СтраницаНастроек расширена вариантами A/B + негатив (обычная
группа) + стресс-привязка (свободный элемент между группами); тест
25-decoration-form покрывает чтение и expand/collapse/toggle. Полный
регресс 29 passed.
Popup-группы: содержимое в отдельном слое, состояние пока не читается
надёжно (follow-up).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Свойство «Отображение управления» (TitleHyperlink/Picture) уже эмитилось
через generic-скаляры, но не было в таблице свойств группы SKILL.md;
в спеке значилось неверное `Picture | Text`. Добавлен фокус-кейс с обоими
литералами, снапшот верифицирован загрузкой в 1С 8.3.24.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Страницы настроек (напр. «Администрирование → Интернет-поддержка и
сервисы») собраны из гиперссылок, frameButton и сворачиваемых групп —
без единого input.editInput / textarea / a.press. detectForm/detectForms
считали такую форму отсутствующей → getFormState = {form:null,
formCount:0}, навык её не видел.
Расширен союзный селектор детекции (.staticTextHyper/.frameButton/
.checkbox/.radio/.tumblerItem/.grid). detectForm двухуровневый: обычные
формы выбираются по редактируемым контролам (поведение не меняется), по
расширенному счёту — только когда у формы нет ни одного поля ввода.
form0 (рабочий стол) по-прежнему исключён фильтром n>0.
Регресс: обработка-фикстура СтраницаНастроек (форма без командной панели:
гиперссылка + сворачиваемая группа) в подсистеме Администрирование +
тест 25-decoration-form. Полный набор 29 passed.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
SKILL.md навыков ссылались на docs/mxl-dsl-spec.md, которого нет
в порт-ветках — агент искал документацию внутри папки навыка.
- mxl-compile: спека внесена как reference/dsl-spec.md (self-contained),
SKILL.md ссылается на неё относительно навыка + добавлен empty в правила
- mxl-decompile: спеку не тянет (нужна только для авторинга JSON),
убраны описания внутренностей скрипта — оставлено «как применять»
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
REPO выводится из расположения самого файла (import.meta.url), а не хардкодом
'C:/WS/tasks/skills'. На POSIX 'C:/…'-строка не абсолютна, и resolve(cwd, path) в
support-guard/skill-suggester склеивал её в удвоенный несуществующий путь — из-за чего
8 кейсов ложно «падали» на Mac (guard/suggester не находили фикстуры). Production-логика
корректна; чинится только тестовая обвязка. CORPUS теперь env-переопределяем
(CC_1C_CFSRC), дефолт прежний; acc/erp-кейсы под existsSync → SKIP без корпуса.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
При поиске корня конфигурации guard поднимался по дереву вверх и «проскакивал»
собственный корень автономной внешней обработки/отчёта (ExternalDataProcessor /
ExternalReport), лежащей внутри дерева выгрузки конфигурации. Если у охватывающей
конфигурации выключена возможность изменения (G=1), внешний объект ложно
блокировался как «объект типовой конфигурации на поддержке», а info-навыки
выводили нерелевантную строку «Поддержка: конфигурация read-only».
Теперь climb останавливается на границе автономного объекта: если целевой файл или
встреченный по пути <каталог>.xml имеет корень ExternalDataProcessor/ExternalReport,
подъём прекращается и объект не привязывается к конфигурации. Корень внешнего объекта
всегда глубже Configuration.xml, поэтому встречается первым — регрессии для обычных
объектов конфигурации нет.
Синхронно во всех копиях guard-а (навыки автономны): хук support-state.mjs
(decideSupport + findConfigRoot), 16 мутаторов (Assert-EditAllowed), 5 info-навыков
и meta-info (Get-SupportStatusForPath / Get-ObjectSupportStatus) — ps1 и py. Для
info-навыков строка «Поддержка:» для внешнего объекта опускается.
Тесты: hooks/test/run.mjs — секция внешней границы (G=1 + встроенная EPF);
tests/skills — кейсы mxl-compile (guard пропускает) и mxl-info (строка опущена).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>