Три остатка волны #57, все в одной точке — блоке записи XML.
1. Порты расходились по EOL. PS гонял документ через XmlWriter с дефолтным
NewLineHandling.Replace и принудительно переводил весь файл в CRLF, а py
сохранял стиль исходника: role-compile на LF-ном Configuration.xml давал
10311 байт против 10062. Ещё в пяти навыках None стоял, но финальной
нормализации не было, и на LF-исходнике выходил смешанный EOL (meta-edit:
40 CRLF + 136 одиночных LF) — та же форма, что у исходного дефекта #57.
Приведено к форме cf-edit.ps1: None + канонизация к LF + целевой перевод
строки (стиль файла-назначения, для создаваемого файла — канон CRLF).
Replace не годится как замена: он превращает переводы строк внутри значений
атрибутов в .
2. Гард `if (-notmatch CDATA)` не обрабатывал CDATA, а отказывался от канона
во всём файле — то есть деградировал до «не сделал ничего». Заменён
альтернацией: участки CDATA и комментариев возвращаются как есть, замена
идёт только вне них. На реальных данных поведение не меняется — в корпусе
из 476 942 XML нет ни одного CDATA и ни одного комментария.
3. py: три реализации одного правила детекта EOL сведены к одной. Мажоритарное
правило в role-compile/subsystem-compile давало ДРУГОЙ ответ на смешанном
входе. Дефолты _finalize_xml_bytes для нового файла приведены к канону
(UTF-8, CRLF, без хвостового перевода); meta-edit срезает хвост в обеих
ветках, а не только при создании файла.
Попутно, найдено байтовой сверкой портов:
- py вставлял <Role>/<Form>/<Subsystem> с пятью табами вместо трёх —
подстановка по голому </ChildObjects> удваивала отступ строки; снэпшоты
этого не видели, так как схлопывают пробелы между тегами;
- py-порты xdto-* писали XML-декларацию одинарными кавычками (так отдаёт
lxml), платформа и PS пишут двойные.
Регресс: runner 647/647 ps1, 644/647 py (3 skipped), дрейфа снэпшотов нет.
Корпусный раундтрип метаданных: 4897 объектов, match 100%, совпадает с
эталонами захода #57.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Код нормализации пустого тега повторён в 24 местах, а комментарий к нему за время
работы над #57 расплодился в шесть вариантов формулировки. Это то же расхождение
копий, что и в коде, только в тексте: читающий не понимает, значима разница или нет.
Сведено к одной паре формулировок — про сам тег с гардом и про запись через
MemoryStream. Комментарий теперь начинается с ПРАВИЛА, а не с причины: раньше
верхняя строка рассказывала, что here-string наследует LF от .ps1, и читалось это
как «переводы строк берём из скрипта навыка» — тогда как правило обратное:
создаём файл — пишем канон, правим существующий — наследуем его стиль.
Два пояснения в cfe-borrow унификация чуть не стёрла, они возвращены: источником
там служит OuterXml, а не XmlWriter, и нормализация стоит после вставки реквизитов,
чтобы накрыть и их. Это причина местного отличия, а не дубль.
Ссылки на ишью прорежены: #44/#46/#47 осталась по одной на файл — там, где правило
формулируется, — и убрана оттуда, где была эхом. #57 в скриптах не было вовсе.
Заодно выровнены две мелочи, найденные той же сверкой копий: interface-edit в
py-порте не обрезал хвостовой перевод там, где PS обрезает (поведение совпадало,
код — нет), а cf-edit нормализовал EOL инлайном вместо общей двухшаговой формы.
Версии подняты в ОБОИХ портах всех затронутых навыков, включая те, где текстуально
менялся только .ps1: номер версии в этом проекте читается как «одно и то же
поведение», и разводить его из-за комментария нельзя. Паритет проверен — 69 пар,
ноль расхождений.
Windows 641/641 обоими портами.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Головной дефект тикета: cfe-init выдавал Configuration.xml с 10 CR на 70 строк, а
роль и язык — вовсе без CR. Теперь 70/70, последний байт `>`.
Правило: меняем только РАЗДЕЛИТЕЛИ строк, содержимое текстовых узлов не трогаем.
Платформа его не трогает тоже — в запросах СКД из чистой выгрузки встречаются и
CRLF, и одиночный LF. Поэтому сплошная нормализация файла применяется только там,
где многострочных текстовых узлов нет по построению (скелеты), а в объектном XML
и формах разделители правятся в точке сборки.
Источников оказалось четыре, а не один:
1. Скелеты (cf-init, cfe-init, epf-init, erf-init, form-add, help-add,
template-add) собираются here-string'ами, а .ps1/.py в репозитории хранятся с
LF — отсюда LF и смешанный EOL.
2. py-порты склеивали документ через '\n'.join(lines); PS в тех же местах давал
CRLF через AppendLine — порты расходились побайтово.
3. Билдеры Predefined в meta-compile собирались с явным LF и даже сворачивали
CRLF→LF из общего эмиттера типов.
4. Чтение существующего файла в python БЕЗ newline='' молча схлопывает CRLF в LF
(универсальные переводы строк), и запись потом кладёт LF. Так role-compile и
subsystem-compile переписывали в LF весь Configuration.xml. meta-compile это
уже делал правильно — там newline='' стоял с фикса #44/#46/#47.
Отдельно: XML-парсер по спецификации схлопывает CRLF при разборе, поэтому
lxml-порты (xdto-compile, xdto-edit) отдавали LF-документ там, где .NET возвращал
CRLF через NewLineHandling. Восстанавливаем EOL исходного файла.
Разделение канона и сохранения стиля:
- файл СОЗДАЁМ — канон (CRLF, без хвоста);
- существующий ПРАВИМ — наследуем его EOL, включая перевод строки вставки
(контракт #44/#46/#47). Иначе LF-проект получал бы смешанные файлы — ровно то,
на что заведён #57. Кейсы roundtrip-crlf-preserve остаются зелёными.
Хелпер записи скопирован в каждый навык (навыки автономны). У meta-compile он
называется Write-XmlFileKeepEol / write_xml_file_keep_eol: там нормализовать EOL
НЕЛЬЗЯ (многострочные запрос, синоним, значение заполнения), и одинаковое имя при
разном поведении было бы ловушкой.
Заодно: имя временного файла батча в meta-compile.ps1 получило GUID. Фиксированное
"meta-compile-batch-$idx.json" в общем %TEMP% сталкивало два параллельных запуска
(«file is being used by another process») — из-за этого полный набор приходилось
гонять с урезанной параллельностью. py-порт уже брал mkstemp.
Аудит: было ~250 файлов с дефектом EOL, стало 0 в обоих портах. Осталось четыре
законных случая — фикстуры roundtrip-crlf-preserve (сохранение стиля) и запрос
динсписка с многострочным текстом. Дрейф снэпшотов — 1 файл. Тесты 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>
Версию формата задаёт ПЛАТФОРМА выгрузки, а не режим совместимости: одна и та же
БП с режимом 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>
При поиске корня конфигурации 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>
Тип метаданных Bot (Боты, платформа 8.3.18+) присутствует в
ChildObjects новых конфигураций, но не входил в зашитый список
типов — cf-validate падал с «Unknown type 'Bot'».
- cf-validate: Bot добавлен в список типов и маппинг каталогов (→ Bots)
- cf-edit: Bot в порядке типов + каталог + синоним DSL «бот»
- cf-info: Bot в порядке типов + рус. название «Боты»
- docs/1c-configuration-spec: Bot в перечне ChildObjects (поз. 11)
- tests: фикстуры + кейсы на Bot для cf-validate/cf-edit/cf-info
Позиция Bot — сразу после CommonModule (по выгрузке Конфигуратора).
Разнесение алгоритмов по версии формата/режиму совместимости
остаётся отдельной задачей бэклога.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Приводим авторский контент (.ps1/.psm1/.py/.mjs/.md/.json, пин .bsl)
к единому LF и закрепляем политикой в .gitattributes. Инструмент правки
всегда пишет LF, поэтому единый LF убирает EOL-шум в диффах, ложные
срабатывания blame и налог на ручную синхронизацию CRLF-файлов.
BOM на .ps1 сохранён (git с eol=lf меняет только CR<->LF, BOM не трогает).
Данные 1С (*.xml) и бинарники под нормализацию не берём.
Гейт: PS-порт 459/459, Python-порт 459/459, web-test E2E 22/22 (с пересборкой стенда).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
argparse-конструкция add_argument("-XPath", "-Path") объявляет два АЛИАСА
одного аргумента, а py-порты edit-навыков понимали её как «передать оба
флага» и звали валидатор как `-XPath -Path <путь>`. argparse трактовал
`-Path` как опцию, а не значение → "error: expected one argument", и
авто-валидация тихо падала (exit code не пробрасывается, правка проходила).
Убран лишний -Path в 5 py-местах:
- meta-edit.py, cf-edit.py, subsystem-edit.py, interface-edit.py (авто-вызов)
- meta-validate.py (рекурсивный batch-вызов, строка 34 — тоже был сломан)
ps1-порты корректны (один флаг), не трогались. Версии подняты парно
(ps1+py) для синхронности. Проверка «скрипт не найден → skip» уже была.
Проверено: одиночная валидация, batch-режим, сквозной meta-edit→валидация.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Синхронизация §1B с улучшенным текстом хука (ветка feat/support-guard-hooks):
вместо общего списка всех вариантов — текст под конкретную причину
(capability-off / locked / not-removed) с подставленным реальным путём и
точными командами support-edit. Понятно модели вне контекста.
- Все 16 навыков-мутаторов (оба рантайма): Assert-EditAllowed /
assert_edit_allowed строят сообщение по $code/code причины отказа.
- Терминология «редактирование» (как у платформы 1С).
- Версии заголовков подняты в обоих рантаймах.
- EOL/BOM каждого файла сохранены; deny-тесты 16/16 на PS и PY.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Замыкает петлю issue #23: после отказа support-guard модель может
легитимно включить редактирование. support-edit правит правила поддержки
в Ext/ParentConfigurations.bin выгрузки (только XML; в ИБ — полной загрузкой):
-Path <путь> -Set editable|off-support|locked (пообъектно или корень)
-Path <путь> -Capability on|off (возможность изменения)
Path-based, симметрично гарду — модель берёт путь прямо из отказа.
Реализация: regex-замена in-place по разобранному формату bin (round-trip
байт-в-байт; парсер подтверждён consumed=100% на корпусе acc/erp/K=7).
При выключенной возможности (G=1) пообъектный -Set отказывает с подсказкой
«сначала -Capability on» (явные шаги). Включение возможности ставит всё на
замок (вендорские флаги «не редактировать» в выгрузке недоступны — массовой
разблокировки нет; non-goal). Оба порта дают байт-в-байт идентичный bin.
Диагностика support-guard (32 файла, 16 мутаторов × 2 порта) теперь печатает
готовую команду support-edit под конкретный отказ.
Тесты: фикстуры g0/g1 + кейсы set-editable/off-support/предусловие/capability
со снапшотами bin; зелёные на PowerShell и Python. Все 16 мутаторов +
support-edit зелёные на обоих рантаймах.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Контракт Assert-EditAllowed (дословная копия из meta-edit, оба порта)
добавлен в 13 навыков-мутаторов. Всем единый вызов require=editable —
«кого проверять» решает не навык, а walk-up по файловой системе от пути
цели:
- целевой файл имеет root-uuid → его f1 (правка существующего элемента);
- иначе ближайший вверх <dir>.xml с uuid → f1 владельца (добавление
дочернего: форма/реквизит/макет к объекту);
- иначе корень Configuration.xml → f1 корня (новый объект верхнего уровня).
Это воспроизводит семантику поддержки 1С автоматически, поэтому один и
тот же навык проверяет разное по состоянию дампа: skd-compile/mxl-compile/
form-compile в существующий макет/форму → f1 этого элемента (modify), в
несуществующий → f1 владельца. Проверено обоими сценариями.
Навыки: form-edit, form-add, form-compile, skd-edit, skd-compile, cf-edit,
subsystem-edit, subsystem-compile, interface-edit, template-add, help-add,
mxl-compile, role-compile. Диагностика через [Console]::Error.WriteLine +
exit 1 (как в эталоне). Версии подняты в обоих портах.
Проверено: deny на обоих портах (крафт-фикстуры на копиях, корпус не
тронут); все 16 навыков-мутаторов зелёные на PowerShell и Python
(264 кейса). BOM сохранён везде.
Follow-up (в upload-плане): пред-существующий баг help-add
Detect-FormatVersion (Substring на кириллице, до гарда, не связан);
авторезолв пути в meta-edit; per-skill committed deny-тесты.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- cf-edit: новая операция set-home-page перезаписывает Ext/HomePageWorkArea.xml.
DSL принимает template (OneColumn/TwoColumnsEqualWidth/TwoColumnsVariableWidth),
left/right с записями форм (строка или объект form/height/visibility/roles).
Тихая нормализация ссылок: русские типы, 3-сегмент → авто-Form, файловые пути
- cf-info: краткая HP-сводка (template + счётчики) в overview/full, детальный
вид через -Section home-page (alias -Name) с раскладкой и переопределениями ролей
- cf-validate: Check 9 — валидация ссылок на формы из HomePageWorkArea и
Default*Form свойств; битая ссылка → error
- reference.md: убран реализационный шум (canonical sort, авто-нормализация форм,
panelDef detail, секция авто-валидации); путь src/ в примерах вместо репо-специфичных
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cf-info отображает «Открытых», «Разделов», «Избранного», «История», «Функций»
(совпадает с подписями Конфигуратора). Если модель копирует эти названия в
set-panels value — теперь они тихо мапятся в каноничные английские алиасы.
Документация и сообщения об ошибках упоминают только английские формы.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
JSON-DSL уровня имён: алиасы sections/open/favorites/history/functions
для платформенных uuid, объект {group:[...]} для стека (даёт <group>-
вложенность как у Конфигуратора), несколько записей в одной стороне =
соседние теги (рядом). Файл Ext/ClientApplicationInterface.xml
перезаписывается полностью; panelDef для всех 5 панелей пишется всегда.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Добавлен Alias('Path') / "-Path" к основному файловому параметру
в *-info, *-validate, *-edit, *-decompile (24 навыка × PS+PY).
Не документируется — fallback на случай если модель напишет -Path
вместо -TemplatePath/-FormPath/-ObjectPath/-SubsystemPath/-RightsPath/
-ConfigPath/-ExtensionPath/-CIPath. Поведение строго аддитивное.
Регресс: 336/336 PS, 336/336 PY.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
cf-edit add-childObject was a low-level XML-manipulation operation
with no file-existence validation — callers could register a reference
to any Type.Name in Configuration.xml's ChildObjects without the
underlying file existing on disk. Platform then refused to load:
"Файл объекта не существует".
The 4 failing tests (add-objects, remove-object, add-default-role,
set-default-roles) all used this operation with fake references in
either main input or preRun, and had no way to pass verify-snapshots
because the cf-init-ed config had no actual object files.
User observation: this is the tests being wrong, not the skill.
meta-compile/role-compile/subsystem-compile already auto-register
every new object in Configuration.xml as part of their normal flow
(meta-compile.ps1:2949-3068, role-compile.ps1:667-747,
subsystem-compile.ps1:430-506). Nobody should be calling cf-edit
add-childObject to create a new object — they should be calling the
profile skill. cf-edit add-childObject is only for rare recovery
scenarios: rolled-back Configuration.xml with intact object files,
re-import from DB dump that clobbered the root but left srcfiles.
Changes:
1. cf-edit.ps1/py: Do-AddChildObject now checks that the target file
exists at {ConfigDir}/{PluralDir}/{Name}.xml before registering.
On miss, exits 1 with a message that names the expected path and
points the user at the right skill (/meta-compile, /role-compile,
or /subsystem-compile depending on type). TYPE_TO_DIR mapping for
all 44 metadata types covers irregular plurals (FilterCriteria,
BusinessProcesses, ChartsOfAccounts, ChartsOfCharacteristicTypes,
ChartsOfCalculationTypes).
2. Tests: 4 existing cases rewritten to build realistic fixtures via
meta-compile/role-compile preRun (both skills auto-register, so
the resulting Configuration.xml already references the preRun
objects). add-objects now exercises the round-trip recovery
scenario: meta-compile creates Catalog.Товары and Document.ПриходТоваров
(auto-registered) → cf-edit remove-childObject un-registers both
(files remain) → main run re-registers via add-childObject. This
tests exactly the rollback-recovery use case the operation exists for.
3. New add-missing-errors case: negative test with expectError:
"Object file not found". Verifies the new hard-error path.
4. verify-snapshots.mjs: added symmetric expectError handling (runner.mjs
already had it at line 514). If caseData.expectError is set,
expect skill to fail; check stderr substring match; skip db-load
and mark passed. Without this, negative tests would go red in
verify-snapshots even though runner.mjs accepts them.
5. SKILL.md / reference.md: documented the new constraint and the
redirection to profile skills. Kept mention of legitimate use case
(rollback recovery).
Bumped cf-edit.ps1/py v1.0→v1.1.
Verification:
- runner --filter cf-edit (PS1): 2/6 → 7/7 (6 positive + 1 negative)
- runner --filter cf-edit --runtime python: 7/7 (dual-port clean)
- verify-snapshots --skill cf-edit: 2/6 → 7/7
With this landed P3 from debug/snapshot-verify/NEXT-STEPS.md is closed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Without reconfiguring stderr, Cyrillic error messages appear garbled
on Windows (cp1251 default). Mirrors the existing stdout fix.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Python on Windows defaults to cp1251 for piped stdout, which cannot
handle Unicode box-drawing characters used in info/analysis output.
Added sys.stdout.reconfigure(encoding="utf-8") to all 59 Python scripts.
Tested on real config data: epf-init, epf-validate, cf-info, cf-validate,
meta-info, form-info, role-info, skd-info, subsystem-info — all passing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add cross-platform Python alternatives alongside existing PowerShell
scripts. PS1 remains the default runtime; Python is opt-in via switch
scripts. All parameters are identical between runtimes.
New files:
- 58 Python scripts in .claude/skills/*/scripts/*.py
- scripts/switch-to-python.py and switch-to-powershell.py
- docs/python-porting-guide.md
- __pycache__/ added to .gitignore
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>