Платформа хранит текст ячейки по элементу на язык. Декомпилятор брал ПЕРВЫЙ и терял
остальное, компилятор писал всегда ru захардкоженно. В корпусе ERP текст лежит и под
ru, и под en у 10 730 макетов из 10 924 — то есть терялось у 98%.
Форма взята готовая — конвенция ML-значений из docs/meta-dsl-spec.md §4.4, по которой
уже живут synonym, tooltip и title в метаданных и формах: строка означает русский
текст, объект даёт по надписи на язык в порядке ключей. Новых понятий не вводили,
ключи те же (text и template).
Документный ключ для языков НЕ заводили, и это измерение, а не экономия: блок
languageSettings мы и так эмитим байт в байт как платформа (currentLanguage ru,
defaultLanguage ru, один languageInfo). Отклонения редки — 8 макетов ERP с
currentLanguage en, 3 без него, 157 с объявленной парой ru+en, всего около 1,6%.
Заодно это снимает развилку из разбора PR #62: список языков компилятор дописывать
не должен, платформа его не дописывает.
Эффект на пилоте: категория row[].cell[].tl упала с 47 252 до 12 488 — минус 74%,
самое крупное улучшение кампании. Остаток частично классифицирован: 348 строк — ячейки
с ПУСТЫМ <tl> у оригинала, которых мы не эмитим вовсе; полностью разобрать мешает
обрезка диффа по 400 строк на объект.
Снэпшоты не дрейфанули: кейсы пишут текст строкой, и вывод для неё прежний.
Проверено: тесты 54/54 на обоих рантаймах, verify-snapshots 24/24, вывод портов
совпадает байт в байт и в компиляции, и в декомпиляции.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Инкремент B кампании mxl-roundtrip. Табличный документ может задавать индивидуальные
ширины колонок для группы строк — в XML это несколько элементов <columns>, из которых
раскладка БЕЗ <id> является умолчанием (ровно одна в каждом макете корпуса), а остальные
адресуются GUID, на который ссылаются строки и области. Декомпилятор читал только первый
<columns>: ширины прочих раскладок терялись, а на макетах с пустым умолчанием он отдавал
columns: 0. Несколько раскладок есть у 63% макетов ERP.
DSL: документные columns/columnWidths — раскладка по умолчанию, дополнительные объявляются
в columnSets (ключ — идентификатор), область ссылается ключом columnSet. Форма повторяет
уже принятую в этом DSL пару «словарь именованных штук наверху — ссылка по имени на
потребителе», как styles/style и fonts/font.
Склейки раскладок по содержимому НЕТ, и это исправление собственной ошибки: замер, на
котором строилось прежнее решение, вырезал идентификатор через findtext('columnsID'),
тогда как у набора он называется <id> (columnsID — имя только у ссылок). Вырезание не
срабатывало, и наборы «различались» просто из-за разных GUID. Правильный замер: в 337
макетах из 517 есть наборы с полностью одинаковым содержимым, до 59 дублей в одном
макете. Содержимое раскладку не опознаёт — опознаёт идентификатор.
Попутно закрыты два дефекта, вскрытые корпусом:
- проверка обязательных полей смотрела на истинность значения, а не на наличие ключа:
columns: 0 — осмысленная величина (умолчание пустое, все строки в именованных
раскладках), и компилятор объявлял её отсутствующей. Тот же класс, что был с col;
- позиции колонок сверялись с документным columns, тогда как ширина сетки у каждой
раскладки своя. Проверки диапазона col, автопотока и короткой формы переведены на
ширину раскладки области.
На пилоте из 40 макетов ERP отказов больше нет ни одного (было 26): цикл проходят все 40
на обоих рантаймах. Скомпилированный XML портов совпадает байт в байт.
Записана находка (debug/mxl-roundtrip/WORKFLOW.md): порты mxl-decompile расходятся по
выходному JSON на 11 макетах из 12 — порядок стилей в словаре, при идентичном XML.
Не ловилось потому, что кейсы снэпшотят входной Template.xml, а не выходной JSON.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Инкремент A кампании mxl-roundtrip. Блочная форма DSL не выражала больше половины
корпуса: у 34% макетов ERP есть строки вне именованных областей, у 21% нет ни одной
области типа Rows. Декомпилятор такие строки терял, а на макетах целиком из
Rectangle отдавал areas: [] — компилятор отвечал "Required field 'areas' is missing".
На пилоте из 40 макетов это 10 отказов из 26.
Что сделано:
- имя у блока стало необязательным. Блок без имени — просто кусок сетки; именованную
область он не создаёт. Отдельный «плоский режим» не нужен: макет без выразимых
блоков это один безымянный блок;
- namedAreas — именованные области координатами, для всего, что блоком не ложится
(не-Rows и пересекающиеся). Тип области НЕ указывается: он выводится из заданных
осей, ровно как в ТабличныйДокумент.Область() — только строки дают полосу строк,
только колонки полосу колонок, обе оси прямоугольник. Так нельзя написать
противоречие вроде type: Rows с колоночными координатами;
- диапазон записывается уже существующей грамматикой DSL (как ключи columnWidths):
число или "N-M". Список через запятую запрещён — область непрерывна, платформа
разрывную не хранит;
- прощающим вводом принимается платформенный адрес "R1C1:R2C2" и правило «0 значит 1»;
в документацию не вынесено;
- декомпилятор перестал пропускать области не-Rows (там стоял безусловный continue) и
режет сетку на блоки детерминированно: непересекающиеся Rows задают границы, дыры
становятся безымянными блоками, остальное уходит в namedAreas.
Отдельно: именованные элементы теперь эмитятся отсортированными по имени. Платформа
хранит их именно так — на выборке 541 макета с несколькими элементами иного порядка
нет ни разу. Сортировка ординальная и регистронезависимая; Sort-Object по умолчанию
сортирует по текущей культуре и на кириллице дал бы другой порядок. Отсюда дрейф 13
снэпшотов — чистая перестановка, диффы симметричны, число элементов не изменилось.
На пилоте отказы areas: [] закрыты полностью (10 → 0), цикл переживают 24 макета
вместо 14. Оставшиеся 16 отказов — колоночные раскладки, это инкремент B.
Правка на ps1, зазеркалена в py; вывод портов и в компиляции, и в декомпиляции
совпадает байт в байт.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Размер шрифта в платформенных макетах бывает дробным (8.3, 6.8, 9.8, 11.3, 14.3).
Оба навыка приводили его к целому: py падал голым ValueError на int(), ps1 ТИХО
округлял через [int] — снова шумный отказ против тихой порчи, как было с col.
Найдено раундтрипом по корпусу ERP 8.3.24: на стратифицированной выборке из 40
макетов это давало 12 отказов декомпиляции — 30% выборки и ВСЕ отказы этого этапа.
После правки декомпиляция не падает ни на одном, цикл переживают 14 макетов
вместо 10.
Размер читается инвариантной культурой и остаётся целым, когда дробной части нет,
иначе "10" превратилось бы в "10.0". Эмиссия в XML тоже переведена на инвариантную
культуру: интерполяция строкой отдавала бы "8,3" под русской локалью.
Правка на ps1, зазеркалена в py.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Безусловная склейка OutputPath с текущим каталогом давала "C:\cwd\C:\out.json" и
роняла запись сообщением про формат пути — навык не умел писать по абсолютному
пути вообще. Py-порт этот случай обрабатывал, ps1 нет. Приведено к общей идиоме
репозитория; кейс на абсолютный путь падает без правки и проходит с ней.
Кейс заодно вскрыл, что порты пишут РАЗНЫЙ JSON: ps1 отдавал стиль ConvertTo-Json
из PS 5.1 (выравнивание ключей, \uXXXX вместо кириллицы), py — json.dumps(indent=2).
Один и тот же макет давал 4708 байт против 1744. Не всплывало потому, что ни один
кейс не снимал сам JSON — все проверяли только stdout.
mxl-decompile был единственным декомпилятором мимо общего сериализатора: у
form/meta/skd-decompile для этого свой ConvertTo-CompactJson. Перенесён вариант
skd-decompile как самый полный, py дополнительно переведён на newline='' —
как у соседей. Теперь порты совпадают байт в байт, а decompile→compile даёт
исходный XML байт в байт.
Семья заведена в check-inline-drift.mjs (4 функции, 3 варианта): раньше шесть
копий жили вообще без гарда. В form-decompile переименована локальная переменная
в string-literal — копии различались только ею.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Дорожка CLI из кампании паритета: PowerShell не различает регистр ни в именах
параметров, ни в значениях [ValidateSet], argparse различает и в том и в
другом. Проверено на читающем навыке: cf-info -Mode BRIEF на PS отрабатывает,
на py падал.
ci_parse_args (эталон — meta-compile) вставлен в 68 py-портов, вызовы
parser.parse_args заменены. Навыков с DSL меньше трети, поэтому именно эта
правка делает паритет общим: *-info, *-validate, db-*, cfe-*, web-* тоже
принимают ввод так же, как их .ps1.
Реестр check-inline-drift дополнен списком потребителей — и сразу окупился:
поймал, что массовая замена переписала последнюю строку внутри самого
хелпера и копии стали рекурсивными.
Версии бампнуты синхронно в обоих портах всех 68 навыков.
Проверка: 673/673 (PS), 670+3 skipped (PY), гарды 4/4; паритет версий портов
проверен по заголовкам.
Co-Authored-By: Claude Opus 5 (1M context) <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>
Приводим авторский контент (.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>
Replace literal `.claude/skills/<owner>/...` paths in SKILL.md with the
`${CLAUDE_SKILL_DIR}` variable that Claude Code substitutes at invocation
time. Same-skill references become `${CLAUDE_SKILL_DIR}/<rest>`,
cross-skill references (erf-* → epf-*) become
`${CLAUDE_SKILL_DIR}/../<other>/<rest>`. All paths now wrapped in double
quotes to handle install locations with spaces.
This unblocks plugin-mode installation: literal `.claude/skills/...`
paths fail when the skill is loaded from `~/.claude/plugins/cache/...`
or from a personal `~/.claude/skills/` install. Drop-in mode continues
to work because Claude Code resolves the variable in all install scopes.
Verified via pilot:
- Project drop-in (cc-1c-skills repo)
- Personal `~/.claude/skills/cf-info/`
- Plugin via `/plugin marketplace add <local-path>`
Scope: 61 SKILL.md, 125 path replacements (8 cross-skill).
Scripts unchanged — they already use \$PSScriptRoot and ../<sibling>
patterns that resolve correctly under the bundled cache layout.
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>
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>
- Add "Используй когда/после..." triggers to 26 skill descriptions for better discovery
- Remove technical implementation details from descriptions (file names, XML elements, check lists)
- Enrich CFE skill SKILL.md bodies with parameters, examples, prerequisites, verification
- Clean description pattern: WHAT the skill does + WHEN to use it (user-facing terms only)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Decompiler: remove styles not referenced by any cell or rowStyle
- Decompiler: compress consecutive empty rows into {"empty": N}
- Compiler: support {"empty": N} row placeholder
- DSL spec: document empty field on rows
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Font deduplication: identical fonts reuse existing name (6→4 on Акт)
- Don't emit "rowStyle": "default" for empty styles
- Don't emit "cells": [] on empty rows
- Don't emit "default": {} in styles section
- Result: ~10% smaller JSON output, cleaner for Claude to read/write
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reverse of /mxl-compile: reads Template.xml and produces compact
JSON definition with auto-generated font/style names, rowStyle
detection, span/rowspan mapping, and column width compression.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>