Список без режима давал молчаливо неверный результат, причём разный. У выгрузки
умолчание Changes игнорировало -Objects и отдавало «изменённое с прошлой
выгрузки». У загрузки на движке 1cv8 умолчание Full игнорировало -Files и
заменяло всю конфигурацию базы, тогда как на ibcmd та же команда выполнялась
частично — одни и те же аргументы вели себя по-разному.
Теперь перечисленные объекты или файлы сами задают частичную операцию. Режим
разрешается до ветвления на движки, поэтому расхождение исчезает по построению;
проверено на стенде: обе ветки дают одинаковый результат.
Заданный вместе со списком -Mode Full или Changes не отбрасывается молча — о нём
сообщает [note]. Список с -Mode UpdateInfo отвергается: это другая операция,
обновление ConfigDumpInfo без выгрузки файлов, и вывод подменил бы её.
Инструкции намеренно не менялись: -Mode Partial остаётся каноничной формой,
а вывод режима — прощающим вводом, который мы не документируем.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
В каскаде стояло «Операция необратима». Это неверно: переподключение работает,
просто требует обоих флагов connect, о чём сказано абзацем выше. Ложная
безвозвратность отпугивала бы от законного действия.
Убрано и «пропускает диалог аутентификации»: в пакетном режиме с реквизитами
диалога нет — это теория из документации, а не действие.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Инструкция велела захватывать «объект и корень». Объекта ещё нет ни в базе, ни
в хранилище, поэтому список отвергается целиком — «Загруженный список объектов
пуст» — и корень тоже не достаётся: модель осталась бы вовсе без захвата.
Верный порядок такой же, как у новой формы или макета: захватываем то, что
существует (корень), частичная загрузка создаёт объект, и при помещении он
называется вместе с корнем. Пример в SKILL.md разделён на два шага.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Проверка прямым вызовом платформы на darwin: /ConfigurationRepositoryF"путь с
пробелом" отвергается, тот же ключ без кавычек проходит. Значит на POSIX лишние
и обрамляющие кавычки значений, и кавычки внутри склеенных ключей — они нужны
только для склейки команды на Windows.
File="…" не трогаем: там кавычки часть синтаксиса строки соединения, и с ними
на POSIX всё работает (проверено db-create).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Воспроизведено на darwin: db-load-xml с путём, содержащим пробел, падает с
«Неопределена информационная база». На POSIX аргументы уходят списком, и
кавычки, нужные для склейки команды на Windows, становятся частью значения.
Тот же механизм ранее молча терял многословный -comment в db-repo.
Правка в общей run_v8 (семья platform: run_v8) — чинит все двенадцать
потребителей разом. Снимается ОДИН слой обрамляющих кавычек, поэтому склеенные
ключи (/N"user", File="…", /ConfigurationRepositoryF"путь") не задеты: у них
кавычки внутри токена, там их ждёт разборщик 1С.
PS1 не затронут: там команда всегда склеивается в строку. Точечный arg_value(),
добавленный в db-repo при разборе дефекта, откачен — семейная правка делает его
лишним.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Живой прогон на darwin показал: многословный -comment теряется целиком, а
однословный доходит. На POSIX аргументы уходят списком, и наши кавычки
становятся частью значения; склейка в одну строку нужна только на Windows,
там же нужны и кавычки.
Ключи вида /F"путь" и /N"имя" не трогаем: там кавычки внутри токена требует
сам разборщик 1С, и они нужны на обеих ОС.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
В history.md лежала цепочка «выгрузить CF версии → создать временную базу →
загрузить → выгрузить XML → сравнить». Это один из способов, поданный как
единственный: сравнить можно и средствами конфигуратора, а по задаче #76
источником сравнения может быть прямо версия хранилища, без временной базы —
тогда рецепт станет ещё и неверным.
Работа навыка заканчивается на dump-cfg; что делать с полученным CF, решает
пользователь.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Перечитал изменённые инструкции свежим взглядом и убрал всё, что описывает
поведение навыка вместо способа им пользоваться: образец вывода отчёта (модель
увидит настоящий), фразу «навык подсказывает оба флага», объяснение, почему
длинный отчёт не печатается, и заверение, что неверная пара путь+расширение
ничего не ломает. Осталось действие: как назвать, что указать, чего ждать от
кода возврата.
Столбец «Что теряется» переименован в «Почему»: у lock -All не теряется ничего,
и строка «Ничего, но…» в таком столбце читалась криво.
Подсказка о недоступном хранилище разделена по схеме адреса: tcp ведёт к
серверу хранилища и порту, http — к веб-серверу и публикации. Прежний общий
текст советовал бы проверять сервер хранилища и порт 1542 для http-адреса, где
это неверно.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
В SKILL.md место только тому, как навык применять. Оговорка о том, что форма
адреса не описана в известной документации и проверена на стенде, — это история
работы, модели она ничего не даёт. Осталось применимое: вид адреса, порт по
умолчанию и то, что недоступный сервер отвечает тем же сообщением, что и
отсутствие реквизитов.
В справочнике проекта оговорка тоже переписана на факты: требование совпадения
версий сервера и платформы вместо рассказа о том, где мы это выясняли.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Установлен crserver 8.3.23, поднят на порту 1542 — сетевое хранилище перестало
быть непроверенным местом. Адрес tcp://<хост>[:<порт>]/<имя>, порт по умолчанию
1542; хранилище создаётся сервером по требованию. Весь цикл, файл списка
объектов и администрирование работают так же, как на файловом, и одинаково в
обоих портах. Отдельного поведения у сетевого хранилища не обнаружено.
Нашлась двусмысленность: недоступный сервер даёт «Соединение с хранилищем
конфигурации не установлено» — ровно то же сообщение, что и отсутствие
реквизитов. Прежняя подсказка «добавьте repository в реестр» в сетевом случае
уводила не туда. Теперь db-repo различает по схеме адреса и советует проверить
сервер и порт, а подсказка db-load-xml и db-load-git называет обе причины.
Документация больше не оговаривается «не проверено»: форма адреса подтверждена
на стенде.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Форма tcp://srv01/MyApp пришла из примера в ишью #77, а мы повторили её в
справочнике реестра и в db-list как факт. Прицельный поиск показал, что в
доступной документации её нет: глава 7.4.15 описывает только каталог хранилища,
а в обоих «Приложение 4» (8.3.24 и 8.3.27) не встречаются ни crserver, ни
tcp:// — совпадения по слову «хранилище» относятся к лицензированию.
Теперь сказано «каталог хранилища или адрес сервера хранилища» и добавлена
оговорка: путь передаётся платформе как есть, конкретный синтаксис в известной
нам документации не описан, на серверном хранилище навык не проверялся.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Пункт плана, остававшийся открытым: каталог исходников расширения модель
указывала руками, хотя он объявлен в записи базы. Каталог выбирает модель, а не
скрипт, поэтому это строка в инструкции — рядом с такой же про configSrc.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Печать отчёта целиком стоит модели контекста, но опаснее другое: обрезанный
отчёт по версиям читается как полный ответ. По куску легко заключить, что
объект не менялся, — в отличие от списка объектов, где неполнота очевидна.
Короткий отчёт (до ста строк) печатается целиком, длинный не печатается
совсем: называется путь к файлу и способы сузить выборку. Порог не
теоретический — даже стенд из девяти версий даёт 124 строки.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Три места, где модель делала работу за навык.
report требовал -OutputFile, хотя отчёт всё равно печатается в вывод: путь
приходилось придумывать ради файла, который никто не читает. Теперь без
параметра отчёт уходит во временный файл, а путь называется — сохранить
осознанно по-прежнему можно.
create и connect с явным путём хранилища теперь печатают готовый блок
repository для .v8-project.json. Без записи в реестре реквизиты придётся
передавать в каждом вызове, а update откажется работать вовсе — вспомнить
об этом модель не может, а подставить готовое мы можем.
Подсказка про -ForceReplaceCfg сразу называет и второй флаг: при
переподключении платформа отвергает дважды подряд, сообщая о причинах по
одной, и модель упиралась бы два раза.
Проверено, что печатаемый блок — валидный JSON: строка из вывода разбирается
парсером, путь читается обратно. В PS обратный слэш в строке замены -replace
не спецсимвол, из-за чего слэши сперва удвоились дважды.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Административные и сервисные команды собирались по документации, а вживую
гонялись сырыми вызовами платформы — сборка аргументов навыком не проверялась.
Прогон через навык показал, что работают dump-cfg (включая -Version), set-label,
add-user, clear-cache во всех областях, optimize, copy-users, create -NoBind.
Нашлись два тупика подряд при переподключении базы: платформа сначала отвергает
непустую конфигурацию, а после -ForceReplaceCfg — то, что за пользователем
хранилища уже числится эта база. Оба раза действие не называется. Добавлены
подсказки на оба сообщения, сценарий описан в references/connect.md.
Заодно постусловие update сработало на настоящей аварии: после неудачного
переподключения база осталась отключённой, и команда отказалась вместо тихой
замены конфигурации.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Файлы references/ несли сокращённую запись «... db-repo.ps1 -Command …», и
scripts/switch.py её не видел: его регулярное выражение требует полной формы
powershell.exe -NoProfile -File <path>.ps1. В python-дистрибуции каскад остался
бы с .ps1, то есть на macOS указывал бы на неисполнимый файл, а гард
переносимости этого не ловит — он следит за плейсхолдером ${CLAUDE_SKILL_DIR},
которого в сокращённой записи тоже не было.
Проверено прогоном switch_runtime_content по всем четырём файлам: до правки
переключатель находил 0 вызовов при трёх упоминаниях .ps1, после — все
конвертируются, .ps1 не остаётся.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
db-list/SKILL.md — фактическая спека .v8-project.json, которую читает модель,
и без repository она давала неполную картину: база под хранилищем не принимает
ни одной операции конфигуратора без реквизитов доступа, причём это касается не
только db-repo, но и всей группы загрузки-выгрузки.
Добавлены repository и extensions[] в пример и таблицу полей, раздел с
описанием обоих (включая то, что у расширения своё хранилище со своим путём,
а пользователь хранилища не наследуется от пользователя базы), пункт в
интерактивный сценарий добавления базы и форма ключей доступа в разделе про
строку подключения.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Операция над всей конфигурацией перечисляет тысячи объектов, и весь список
уезжал в вывод модели. Списки обрезаются на двадцати позициях, полный уходит
в файл, путь к которому назван. Сырой лог платформы при отказе показывается
хвостом в 200 строк: итог и причину платформа пишет в конце.
Тесты на фейковой платформе — 12 кейсов, разбор лога проверяется без 1С на
логах, снятых со стенда. Ключевой кейс закрывает главное правило навыка:
платформа вернула 1, часть объектов захвачена, навык возвращает 0 с поимённым
предупреждением. Обрезка списка проверена на логе из тридцати объектов —
кейс требует и строку «и ещё 10», и отсутствие двадцать первого объекта
в выводе.
Проверено негативным контролем: намеренно сломанное ожидание роняет кейс,
то есть тесты действительно сверяют вывод, а не проходят вхолостую.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Прогон всех веток вывода подряд показал, что длинный совет про перевыгрузку
вставал МЕЖДУ фактами и вердиктом: строка «захват не выполнен» терялась в
середине. Совет теперь печатается последним во всех ветках — сначала факты,
затем вердикт.
Убрана мёртвая подсказка про -Yes: флага больше нет. Снято утверждение «по
убыванию частоты» у причин отказа commit — проверить его нечем. Сняты повтор
слова «проверьте» и склонение «объект(ов)». Заголовок «Не были захвачены»
дополнен пояснением, что снимать нечего.
Примеры в шапке скрипта приведены к именованной подкоманде.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Порт .py зеркалит .ps1 по порядку функций, именам и комментариям — расхождения
только там, где их диктует рантайм. Общие функции взяты из соседних портов
дословно.
Копии зарегистрированы в check-inline-drift.mjs: db-repo присоединён к шести
платформенным семьям, четыре функции блока реквизитов хранилища заведены новыми
семьями, разбор сообщений хранилища — семьёй с эталоном db-load-xml. Гард сразу
нашёл настоящее расхождение: копии Get-RepositoryArgs в четырёх соседях остались
со старым comma-return, эталон правился позже. Ресинхронизировано.
Разбор сообщений хранилища добавлен и в db-load-git: он тоже грузит в базу
частично и получает те же три отказа платформы.
Подкоманда стала именованной (-Command): в группе скрипты принимают только
именованные параметры, слэш-форму в них переводит модель. Позиционный параметр
у пишущего навыка запрещён гардом check-positional-binding. Mandatory при этом
не ставим — обязательный параметр PowerShell запрашивает интерактивно, а в
пакетном запуске это зависание.
Все девять гардов и функциональные тесты db-* зелёные в обоих рантаймах.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Захват всей конфигурации на большой базе идёт долго и блокирует работу всей
команде, а получался он от одного забытого -Objects: «вся конфигурация» была
умолчанием. Теперь для lock, unlock и commit это отдельный флаг -All, который
нельзя совместить с -Objects. Захват корня с -WithChildren, означающий то же
самое, отсылает к нему же.
update без -Objects остаётся обычным вызовом: получение всех изменений — это
нормальный ежедневный сценарий, а не тяжёлая операция.
Флаг отдельный, а не -Force: у -Force уже есть платформенное значение, разное
по подкомандам, и третье значение сделало бы его нечитаемым.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Модель может попросить захватить реквизит, табличную часть, измерение или
ресурс. Платформа на это отвечает «Загруженный список объектов пуст» — без
имени объекта и без причины, отладить такой ответ нечем.
Навык распознаёт вложенные виды подчинённых и подменяет их объектом-владельцем,
сообщая о подмене. Это не меняет смысл запроса: владелец — минимально возможная
единица захвата для того, что просили, потому что своей сущности у части объекта
нет. Отказ стоил бы лишнего круга без пользы.
Заодно убран comma-return у двух функций: в связке с @() у вызывающего он даёт
вложенный массив, из-за чего список объектов склеивался в один fullName.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Проверка навыка субагентом на сквозном сценарии вскрыла фактическую ошибку
в инструкции: реквизиты и табличные части перечислялись наравне с формами как
захватываемые объекты. Платформа их объектами не считает — список объектов
получается пустым, причём без секции «отсутствующие в конфигурации». Правишь
реквизит, табличную часть, измерение, ресурс или модуль — захватывай владельца;
формы, макеты и команды захватываются отдельно.
Сообщение «объект не найден» имеет три разные причины: опечатка, отставание
базы от хранилища и попытка захватить то, что объектом не является. Теперь
перечислены все три.
В цикл добавлен нулевой шаг — получение актуального состояния перед началом
работы: правки должны опираться на актуальные версии в том числе тех объектов,
которые не меняются, но используются. Загрузка и обновление БД слиты в один
шаг через -UpdateDB, поэтому цикл не удлинился.
Захват корня конфигурации с -WithChildren отклоняется: это захват всей
конфигурации, а выглядит как захват корня. Для всей конфигурации есть
однозначная форма — вызов без -Objects.
Текстовый отчёт печатается, а не только сохраняется в файл.
Схема repository и extensions[] описана в docs/v8-project-guide.md,
цикл под хранилищем — в docs/db-guide.md.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Новый навык /db-repo с подкомандами: рабочий цикл (lock, unlock, commit,
update), подключение базы (connect, disconnect), история (report, dump-cfg),
администрирование (create, add-user, copy-users) и сервис (set-label,
optimize, clear-cache). Каскад инструкций в references/.
Ядро навыка — разбор вывода платформы: код возврата о фактическом результате
не говорит. Захват и помещение не атомарны (код 1 при реально захваченном
объекте), а все no-op'ы дают код 0. Вердикт строится по строкам лога и
отражает достижение запрошенного состояния, а не факт изменения.
Захват, обновление и unlock -Force молча подтягивают свежие версии в локальную
конфигурацию. Навык называет полученные объекты и печатает готовую команду
перевыгрузки: без неё частичная загрузка старых исходников молча откатывает
чужие изменения.
Соседние навыки группы получили общий блок разрешения реквизитов хранилища из
.v8-project.json — база под хранилищем не принимает без них ни одной операции
конфигуратора. db-load-xml дополнительно подсказывает действие по трём
сообщениям платформы, db-dump-xml получил -ObjectsFile для передачи списка
объектов между навыками без перепечатывания.
Порты .py следуют отдельно.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
На Windows fs.rmSync и fs.cpSync молча не делают ничего, когда не-ASCII символы
есть в самом аргументе пути (nodejs/node#61067). Для аудитории проекта это боевой
сценарий: кириллическое имя пользователя даёт кириллический %TEMP%, где раннер
создаёт воркспейсы, плюс кириллические имена объектов 1С в путях внутри кейсов.
Замерено на восьми сборках. Затронуты 22.18.0–22.23.2 (последняя LTS Jod) и
24.12.0–24.14.0; исправны 22.15.1, 24.15.0+, 25.x, 26.x. Фикс приехал в fs-слой
Node 24.15 и в ветку 22.x не бэкпортирован, поэтому «обновить Node» вопрос не
закрывает. Зависимость не монотонна по версиям (24.14 чинит rmSync, но не
cpSync) — гард по номеру версии невозможен, решает только сам путь.
Симптомы: rmSync молча ничего не удаляет; cpSync с не-ASCII приёмником молча
ничего не копирует; cpSync с не-ASCII источником валит процесс нативно
(0xC0000409) мимо try/catch. Не затронуты mkdirSync, readdirSync, lstatSync,
copyFileSync (включая перезапись), unlinkSync, rmdirSync — на них стоит обход.
Что ломалось: под кириллическим %TEMP% фикстуры не доезжали до воркспейсов
(meta-info — 15 ложных падений из 26, один кейс ложно-зелёный на пустом
воркспейсе); cfe-validate/module-state-flag-without-file был красным даже при
ASCII %TEMP% (кириллица в самом deletePath); --update-snapshots молча не сносил
старый эталон, то есть портил коммитимые артефакты.
Реализация — единый fsutil в двух побайтно одинаковых копиях (tests/common/ и
внутри автономного навыка web-test), раскатанный на все 42 точки вызова в 11
файлах: tests/skills/*, tests/web-test/*, hooks/test/run.mjs, движок web-test.
Предикат судит по resolve(p), а не по строке аргумента: относительный
ASCII-аргумент при не-ASCII cwd платформа роняет так же молча. Ретраи доживают
до ручного обхода, перезапись как у cpSync с force: true, настоящие ошибки не
глотаются, выживший после удаления путь — громкая ошибка.
Гард tests/skills/check-nonascii-fs.mjs в check-all.mjs проверяет хелпер, а не
платформу (поэтому зелёный и на исправной Node), сверяет хеши обеих копий и
печатает справкой состояние текущей сборки.
Co-Authored-By: androman.pro <5669019+andromanpro@users.noreply.github.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
py-порт в batch-режиме запускал сам себя через subprocess на каждый объект, и на разбор в единицы
миллисекунд приходилось ~146 мс старта интерпретатора с импортом lxml. На корпусном прогоне это
давало 92 мс на объект против 40 мс у PS — при том, что обычно py быстрее в разы. Причина
асимметрии: PS вызывает себя через & в том же процессе, платя свой дорогой старт один раз на батч.
Теперь py делает то же самое: файл компилируется один раз и выполняется в свежем globals() на
каждый объект. Изоляция состояния получается той же, что у & в PowerShell (script-переменные не
переживают вызов), а старт и компиляция платятся однократно.
Прогон по acc_8.3.24 и erp_8.3.24 (33 895 объектов): 274 c против ~2900 c, вывод побайтово тот же
и совпадает с PS-портом строка в строку. Батч с битым объектом в середине по-прежнему доходит до
конца и возвращает ненулевой код, -OutFile пишет те же файлы на объект.
Заодно уточнён комментарий про кэш состава конфигурации: «порождает процесс на объект» было верно
только для py, тогда как в PS кэш не переживает объект из-за новой области видимости.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Прогон по acc_8.3.24 и erp_8.3.24 (33 895 объектов) давал 246 ERROR и 1662 WARN на выгрузках,
которые платформа принимает без единой ошибки, — то есть ложным был весь вывод валидатора.
Две группы при этом роняли exit=1 на типовой конфигурации: то же, из-за чего заводились #79 и #84,
только зеркально.
Разобрано платформой на стенде 8.3.24.1691, каждый случай — копия эталона с одной мутацией:
- пустой <Type/> у реквизита — это тип «Произвольный»; загрузка проходит, round-trip сохраняет
конструкцию без изменений. Было ERROR (245 раз), стало молчание;
- блока Type нет вовсе — загрузка тоже проходит, но платформа молча подставляет тип нового
реквизита, Строка(10). Замысел теряется, загрузка нет: ERROR заменён на WARN;
- IntegrationService и Bot отсутствовали в списках видов метаданных (в спеке 45 типов, навык знал
43) — штатный объект типовой давал «Unrecognized metadata type» и exit=1;
- HierarchyType платформа пишет всегда и при выключенной иерархии игнорирует: из 1254 срабатываний
1253 приходились на её собственный дефолт. Предупреждаем только о явно заданном другом типе;
- источник подписки задают и наборами типов (<v8:TypeSet>), а состав журнала — элементами
<xr:Item>; проверки искали только <v8:Type> и потому не видели ни одной такой подписки и ни
одного журнала. Зато реально пустой источник и пустой состав платформа отвергает
(«Источник событий должен быть задан», «Для журнала не заданы регистрируемые документы») —
там уровень поднят с WARN до ERROR;
- сигнатура процедуры переносится на несколько строк, и Экспорт оказывается не на строке с именем;
регулярка требовала одну строку. Модуль поставщика в двоичном виде (Module.bin) больше не даёт
предупреждения «файл не найден» — текста там нет by design;
- ExchangeDate — легаси-реквизит планов обмена, поддержан в meta-compile и meta-decompile:
добавлен как допустимый, но не обязательный.
После правки корпус даёт 0 ERROR и 1 WARN — тот самый единственный недефолтный тип иерархии.
Объём проверки не потерян: объектов провалидировано 33 895 против 33 894.
Восемь новых кейсов держат обе стороны каждой правки: валидная конструкция молчит, настоящая
ошибка по-прежнему ловится.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Таблица параметров и примеры в SKILL.md описывали слэш-нотацию (--set-default, --purpose),
а скрипт принимает -SetDefault и -Purpose. Модель, читающая таблицу, получала отказ биндинга
на первом же вызове. Документация приведена к исполняемой форме — она теперь одна.
Заодно навык принимает обиходные написания молча: назначение формы русским названием вида
формы или английским с суффиксом Form (регистр, пробелы и разделители не значимы) и флаг
основной формы с дефисом внутри имени. Допустимость назначения для вида объекта по-прежнему
проверяется — неизвестное значение отвергается со списком доступных.
Порты выровнены: PS принимает это через алиас параметра, py — через перечисление опций;
проверено девятью написаниями на обоих, результат совпадает.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Проверки 16 и 20 выбирали уровень по наличию файла, тогда как платформа судит по составу
конфигурации. Из-за этого валидатор ошибался в обе стороны: пропускал ссылку на форму
объекта, которого в конфигурации нет (WARN, exit=0 — платформа такую выгрузку не грузит),
и ложно отказывал на фрагменте частичной выгрузки, где файла формы нет намеренно.
Единое правило: ChildObjects отвечает за существование объекта, наличие файла — только за
полноту выгрузки. Нет в составе -> ERROR, в составе есть, а файла нет -> WARN. Уровень для
фрагмента остаётся мягким сознательно: один и тот же фрагмент грузится или нет в зависимости
от состояния конфигурации БД, которого валидатору не видно.
Матрица снята на платформе 8.3.24.1691 (полная и частичная загрузка, семь ситуаций).
Прогноз про загрузку убран из текста WARN — там он не гарантирован.
Состав читается поиском подстроки в границах секции ChildObjects, без разбора XML:
batch-режим порождает процесс на объект, и полная карта оплачивалась бы каждым объектом.
Корпусный прогон по acc_8.3.24 и erp_8.3.24 (33 895 объектов): расхождений до/после нет.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Значение с пробелом без кавычек разрывалось на токены, и лишние молча растекались по свободным
позиционным параметрам. На документированной форме вызова (powershell -NoProfile -File):
form-compile.ps1 -JsonPath a.json -OutputPath b.xml -Purpose Форма списка
→ Purpose=[Форма] ObjectPath=[списка], ошибки НЕТ
Навык отрабатывал успешно с усечённым значением; реальный триггер — путь с пробелом в -EmitDsl
или -ObjectPath, результат уезжал не туда молча. У навыков с парой -DefinitionFile/-Operation
осколок уезжал в -DefinitionFile, и навык жаловался на параметр, которого в команде не было.
Расхождение портов: py на том же вызове отвечает «unrecognized arguments: списка» — там все
аргументы объявлены опциями. Правка выравнивает PS по py, python не менялся.
41 навык получает [CmdletBinding(PositionalBinding=$false)] и ни одного позиционного параметра.
Без Position=0 (в отличие от read-only навыков): «главный» параметр механически не выводится —
у meta-edit первым объявлен -DefinitionFile, а путь к объекту вторым, — а ошибка выбора тестами
не ловится, раннер передаёт только именованные флаги. Позиционной формы вызова нет ни в одной из
140 строк SKILL.md, внутренние вызовы навыков друг из друга тоже именованные.
check-positional-binding.mjs расширен на пишущие навыки статической проверкой. Поведенческая
(канареечный файл) остаётся только у read-only: у web-stop и db-run все параметры
Mandatory=$false, связывание прошло бы и тело выполнилось — гард остановил бы Apache и запустил 1С.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Часть навыков проверяла путь ко входному JSON сама и отвечала внятной строкой, часть — нет и
роняла дамп: FileNotFoundError с traceback в py, MethodInvocationException с CategoryInfo в PS1.
Один и тот же промах давал разный ответ в зависимости от навыка, а у form-edit — ещё и от порта.
Проверка перенесена в тело семьи read_json_file / Read-JsonInputFile: пол одинаков везде по
построению, и новые навыки получают его вместе с функцией. Навыки со своей проверкой срабатывают
раньше и сохраняют прежний текст, поэтому существующие кейсы не двигаются.
Заодно каталог вместо файла: раньше IsADirectoryError / отказ доступа, теперь
«Expected a JSON file, got a directory».
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Этажом ниже разбора, на чтении файла, жил тот же класс дефектов в худшей форме. Файл в cp1251
с кириллицей: PS1 `Get-Content -Encoding UTF8` менял имя на 12 символов U+FFFD, JSON после этого
разбирался УСПЕШНО, и навык создавал объект с именем из «замен» — молча. Py-порт на том же файле
падал traceback-ом. Файл в UTF-16 давал ту же пару: traceback против ложного «JSON must have
'type' field».
Новая семья read_json_file / Read-JsonInputFile: кодировка берётся из BOM (UTF-8, UTF-16 LE/BE),
без BOM — строгий UTF-8, при провале сообщение называет файл и байт. Кодовую страницу не
подбираем: угаданное имя уехало бы в метаданные так же молча.
Эхо полученного значения печатается теперь только для inline-входа. Для файла оно показывало
первые 60 символов первой строки независимо от того, что ошибка на 120-й, и спорило с позицией
от парсера.
Заодно уравнен пустой вход: PS 5.1 на пустой строке отдаёт $null, а не ошибку, и навык уходил
дальше с $null, тогда как py-порт падал.
Раннеру добавлен ключ inputEncoding (utf-16le / utf-16be / cp1251) — иначе кейс про кодировку
не выразить, writeFileSync пишет только UTF-8.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Эхо полученного значения обрезалось многоточием, и обрыв читался как обрыв самих данных:
агент шёл дописывать «неполный» файл, хотя ошибка была на 57-й строке. Плюс усечение спорило
с позицией от парсера — два сигнала об одном месте, которые не сходятся.
Теперь усечение называет себя: got (first 60 chars, whitespace collapsed). Число символов
не печатаем — после схлопывания пробелов оно не сходилось со смещением из сообщения парсера.
На коротком входе, ради которого эхо и заводилось (съеденные оболочкой кавычки дают
{group:X,commands:[Y]} — 22 символа), усечения нет вовсе.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Неверный входной JSON ронял скрипты необработанным исключением: PS1 отдавал дамп
ConvertFrom-Json с CategoryInfo, py-порт — traceback с внутренностями json/decoder.py.
Имя файла в сообщении не фигурировало, а для полиморфного -Value не было видно, какую
форму ждёт операция.
Общий хелпер ConvertFrom-JsonInput / parse_json_input в 12 навыках × 2 порта (24 места),
зарегистрирован семьёй в check-inline-drift.mjs — копии держит гард. Сообщение в одну
строку: ожидаемая форма, полученное значение, текст парсера в скобках.
Эхо полученного значения нужно потому, что съеденные оболочкой кавычки дают почти тот же
JSON ({group:X} вместо {"group":"X"}), и без него агент считает свой вызов верным. PS 5.1
печатает лишь огрызок и локализованно, Python — только номер колонки.
Возврат в PS1 через Write-Output -NoEnumerate: вынос разбора в функцию добавляет второй
анруллинг, и одноэлементный JSON-массив стал бы скаляром. Импорты внутри тела py-хелпера —
skd-decompile импортирует json локально как _json, а тело семьи обязано быть одинаковым.
В раннер добавлен inputRaw (запись входного файла дословно): через case.input битый JSON
невыразим, JSON.stringify всегда даёт валидный документ.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Вид искался как первое совпадение //md:<Вид> по всему файлу и потому зависел
от порядка перебора видов. У бизнес-процесса есть свойство <Task>, и объект
определялся как задача, после чего имя не находилось вовсе: «Не удалось
определить имя объекта из Properties/Name». Прежний упорядоченный список видов
это маскировал — в нём BusinessProcess стоял раньше Task.
Теперь вид — первый элемент-потомок MetaDataObject, и неподдерживаемый вид
отвергается по имени, а не проваливается в поиск имени объекта.
Нашлось платформенной проверкой всех видов разом: собрана конфигурация с
формой каждого поддержанного вида (15 видов, 43 формы) и загружена в 1С.
Заодно покрытие назначений доведено до полного (добавились FolderChoice, Load,
Record), а мелкие кейсы на один объект объединены: несколько назначений в одном
кейсе, эталон показывает все слоты сразу.
Гард дополнен инвариантом «ровно одна основная форма на вид» и его паритетом
между портами — именно им пользуется умолчание -Purpose.
Инструкция: в таблице параметров умолчание Purpose исправлено с Object на
основную форму вида.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Для журнала документов навык писал в форму битый главный реквизит —
cfg:.Журнал с пустым префиксом. Платформа такую выгрузку не принимает
(«Исключение XDTO при чтении файла»), а навык рапортовал успех.
Причина не в забытой строке: знание о видах было размазано по четырём
независимым спискам (поддерживаемые типы, объектные, обработко-подобные,
карта типов реквизита) плюс switch по свойствам DefaultForm. DocumentJournal
попал в поддерживаемые, но не в карту типов — и подстановка $null дала точку
без типа. Теперь одна плоская таблица: строка на (вид, назначение), в ней тип
главного реквизита и свойство «основная форма».
Того же корня и починено заодно:
- Purpose=Object принимался для видов, у которых формы объекта не бывает
(журнал, перечисление, регистры накопления и бухгалтерии);
- свойство «основная форма» выбиралось без учёта вида: журналу писался
DefaultListForm, которого у него нет, и слот молча не находился;
- AccumulationRegister описывался как RecordSet, хотя у регистра такого
главного реквизита не бывает, а слота под него нет вовсе;
- ChartOfCalculationTypes отвергался, хотя формы объекта у него обычные.
Новое: назначения Folder и FolderChoice (формы группы), RecordSet для всех
регистров, Save/Load для хранилища настроек и Custom — произвольная форма без
главного реквизита, самая частая в типовых после объектной. Умолчание Purpose
теперь основная форма вида, а не жёсткий Object: для регистра сведений это
форма записи, для журнала — форма списка.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Непустое Default*Form / Auxiliary*Form / ChoiceForm могло указывать на
форму, которой нет, а валидатор отвечал OK. Платформа такую выгрузку не
принимает: «Неизвестный объект метаданных».
Новая проверка разбирает обе формы записи. Для своего объекта проверяется
регистрация формы в ChildObjects — это работает и на одиночном файле, без
конфигурации; для общей формы и чужого объекта (форма журнала документов в
слоте документа) нужна конфигурация, и проверяется наличие файла формы.
Отдельно ловится случай, когда форма зарегистрирована, а файла нет.
Значением слота бывает GUID — такие пропускаются, как и в cf-validate.
Заимствованные объекты расширения пропускаются целиком: их формы живут в
основной конфигурации.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Файл целиком исключался из проверки ссылок как «чистится автоматически»,
хотя автоматически в нём чистится только ChildObjects. Ссылки уровня
конфигурации (DefaultReportForm и соседи) после удаления общей формы
оставались висеть и в отчёте не упоминались.
Теперь такие слоты видны в списке ссылок, а с -Force очищаются — наравне со
слотами других объектов и элементами начальной страницы. Ссылки на типы и
вызовы в .bsl по-прежнему не трогаем: чем их заменить, неизвестно.
Два полных обхода конфигурации свёрнуты в один, Get-ChildItem -Recurse
заменён на Directory.EnumerateFiles: на большой конфигурации проход занимал
180 с против 47 с, а проходов было два.
Общие паттерны ищутся в тексте без form-слотов — иначе файл со слотом
попадал в список дважды, а пропуск файла целиком спрятал бы настоящую
ссылку рядом со слотом.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Форма могла удаляться, оставляя ссылки на себя в других объектах: платформа
затем отвергала загрузку с «Неизвестный объект метаданных». Навык правил
только файл своего объекта и про остальную конфигурацию не знал.
Теперь перед удалением конфигурация сканируется на ссылки на эту форму
(слоты Default*/Auxiliary*Form и ChoiceForm, ChoiceForm и SettingsStorage
внутри форм, элементы начальной страницы). Есть ссылки — удаление
останавливается со списком мест; с -Force форма удаляется, а ссылки
очищаются: слот пустеет, элемент начальной страницы вырезается.
Заодно починен матч ссылки: сравнение шло по хвосту «Form.<Имя>» без вида и
объекта, поэтому при удалении своей ФормаСписка обнулялась и ссылка на
DocumentJournal.Ж.Form.ФормаСписка в том же файле.
Сравнение путей переведено на длинную форму: Resolve-Path отдаёт короткое имя
8.3, перечисление файлов — длинное, из-за чего собственный файл объекта не
исключался из скана.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Три файла из 77 навыков нарушали соглашение о переносимости, и сборка
Codex-порта уносила их as is (#75).
Функциональный из трёх один: json-dsl.md писал путь к скрипту литералом
.claude/skills/meta-edit/..., то есть на любой платформе кроме claude-code
команда указывала в несуществующий каталог и молча не находила скрипт.
Заменено на ${CLAUDE_SKILL_DIR}/ — дальше путь разворачивает switch.py.
Ещё два — тексты: проза с названием агента в mxl-compile (заодно избыточная:
у соседних *-compile это одна строка «принимает X → генерирует Y») и
.claude-путь в комментарии state.mjs.
От регресса — check-agent-portability.mjs: вырезает разрешённый
${CLAUDE_SKILL_DIR}/ и падает на любом оставшемся claude. Ловит и плейсхолдер
без завершающего слеша: switch.py разворачивает только форму со слешем,
остальные проехали бы насквозь. Сторонний node_modules исключён — playwright
содержит ClaudeGenerator и .claude/agents, переписывать его нельзя.
switch.py не менялся: ни одного плейсхолдера и ни одного вызова скрипта вне
*.md верхнего уровня навыка нет, рекурсия не нужна.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
При пересборке поля навык сохранял <valueType> через OuterXml/tostring и срезал
из фрагмента ВСЕ объявления namespace. Для префиксов, объявленных в корне схемы,
это верно — DOM переобъявляет их на фрагменте избыточно. Но объявление, которого
в корне нет, значимо: xmlns:d5p1 живёт только локально на <v8:Type> и связывает
префикс, которым квалифицировано значение узла (d5p1:CatalogRef.X). Срез оставлял
висячий префикс — XML остаётся well-formed (префикс в тексте парсер не проверяет),
а 1С отвергает тип. Радиус — modify-field.
Слепой срез заменён на Strip-InheritedXmlns / strip_inherited_xmlns: объявление
выбрасывается, только если корень объявляет тот же префикс с тем же URI. Карта
корня снимается с уже имевшегося RawRootOpening; разделитель \s+, поскольку
корневой тег бывает разложен по строкам. Префикс и URI сравниваются ординально —
в PS -eq и @{} регистронезависимы. Подставлено во все пять точек среза каждого
порта; для корневых префиксов поведение не меняется.
Кейс modify-field-ref-type: modify-field по ref-полю не был покрыт. Проверено,
что кейс краснеет до правки — и по снэпшоту, и по skd-validate.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Три навыка делали одну работу тремя инлайновыми копиями в main(), и одна копия
молча разъехалась: role-compile.py рапортовал об успехе, не тронув файл. Это
ровно тот класс, от которого заведён check-inline-drift.mjs, но в реестр
регистрацию было не записать — он работает по именованным функциям.
Регистрация вынесена в Register-InChildObjects / register_in_childobjects
с контрактом (родительский XML, тег родителя, тег потомка, имя) и исходом
added | already | no-childobj | no-config. Печать сообщений осталась на
вызывающей стороне: тексты у навыков разные, и сведение их меняло бы вывод.
Эталон — meta-compile, форма функции задана на .ps1. role-compile берёт его
тело копией, и гард это подтверждает. subsystem-compile идёт отдельным
вариантом с обоснованием: родителем бывает вложенный Subsystem.xml
произвольной глубины, поэтому отступ он берёт из документа, а запись
дописывает в конец блока — фиксированные три табуляции там неверны,
и группировать по типу нечего.
Вынос поведенчески нейтрален: 837 кейсов зелёные на обоих рантаймах,
снэпшоты не дрейфовали, матрица «навык × порт × изломанная форма
Configuration.xml» даёт те же исходы, а порты — байт-в-байт одинаковый файл.
Попутно из subsystem-compile.py убран мёртвый ET.SubElement перед pass:
дерево мутировалось и выбрасывалось, правка идёт по сырому тексту.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Configuration.xml без <ChildObjects> и с самозакрытым <ChildObjects/> — в обеих
формах py печатал «<Role>X</Role> added to ChildObjects», а файл оставался
байт-в-байт прежним. Модель считала роль включённой в состав конфигурации,
хотя её там не было. PS-порт обе формы обрабатывал верно: самозакрытый тег
раскрывал, при отсутствующем честно предупреждал.
Ветвление перенесено из meta-compile.py, где та же задача давно решена: есть
блок ChildObjects — вставляем внутрь; есть самозакрытый тег — раскрываем первой
записью; нет ни того ни другого — исход no-childobj и файл не трогаем. Ветка
вывода no-childobj в py уже была и до сих пор оставалась недостижимой.
Порядок вставки и способ правки файла не менялись: проверка на боевых выгрузках
(acc/erp, CRLF и LF) показала, что действующая реализация даёт дельту ровно
в одну вставленную строку, а платформа порядок ChildObjects не нормализует.
Кейсы на обе формы. На неисправленном коде оба падают на python и проходят
на powershell. Проверка «успех не объявлен» опирается на stdoutNotContains
и снапшот: Write-Warning в PS 5.1 идёт в stdout с локализованным префиксом,
а py пишет в stderr, поэтому портируемого stderrContains тут быть не может.
Co-Authored-By: Sergei Pleshanov <2357qwr@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Тот же класс дефекта, что закрыт в role-validate, был возможен во всей семье
*-info / *-validate / cfe-diff: лишний позиционный аргумент связывается со
следующим параметром по порядку объявления. Остальные скрипты спасала
случайность типов — на втором слоте оказался [int]MaxErrors/Limit или строка
с [ValidateSet], которые падают на конвертации. Любая перестановка параметров
в param() открывала дыру заново, а снапшот-тесты сверяют вывод, а не связывание.
Во всех 21 скрипте объявлен [CmdletBinding(PositionalBinding=$false)], входной
путь помечен Position=0. Инвариант: анализирующий навык не пишет в файл, который
ему не назвали по имени. Документированные вызовы в репозитории все именованные,
поведение навыков не меняется — 835 кейсов зелёные на обоих рантаймах без дрейфа
снэпшотов.
Гард check-positional-binding.mjs держит инвариант статически (объявление и
единственный Position=0; в py-порте все add_argument именованные) и поведенчески
(лишний позиционный аргумент роняет вызов, канареечный файл остаётся цел).
Семья определяется по имени навыка, поэтому новый *-info/*-validate попадает под
гард сам. На состоянии до правки гард падает на role-validate обеими проверками.
Версии обоих портов подняты синхронно; из шапок убраны чужие хвосты.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Вызов role-validate.ps1 с двумя позиционными аргументами — естественная ошибка
по документации role-compile/SKILL.md, где значилось
`/role-validate <RightsPath> [MetadataPath]`, хотя параметра MetadataPath у
навыка нет. Второй аргумент связывался с -OutFile, и скрипт перезаписывал
указанный файл (например, Roles/Имя.xml) текстом отчёта — исходник роли терялся.
PositionalBinding=$false оставляет позиционным только путь ко входу; -OutFile
передаётся строго по имени. Справка приведена к факту. Python-порт уже иммунен:
argparse принимает только именованные параметры.
Co-Authored-By: Sergei Pleshanov <2357qwr@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Новая проверка (сверка «файл модуля ↔ пометка») переиспользовала $objName — ту же
переменную, из которой собирается финальная строка отчёта. В расширении с заимствованным
объектом ps1 печатал «Validation OK: Extension.Цены» — имя последнего проверенного объекта
вместо имени расширения. py-порт был прав, то есть порты разошлись молча.
Расхождение видно только на непустом расширении с чистым результатом, поэтому ни один
существующий кейс его не ловил: тесты сверяют снапшот, а итоговую строку — нет. Добавлены
утверждения на неё в кейсы valid, with-borrowed-object и module-state-file-without-flag;
проверено, что на старой версии скрипта они падают.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Объект заимствуют, чтобы дописать в него модуль, — теперь пустой файл модуля создаётся
навыком, а не руками. Тип с единственным модулем (CommonModule, HTTPService, WebService)
получает его без указаний, остальные — по -Module; -Module None отменяет. Существующий
файл не перезаписывается никогда.
Вместе с файлом проставляется <xr:PropertyState> со State=Extended. Замер по лестнице
платформ 8.3.20…8.5.1: элемент появился в формате 2.19 (8.3.26), ниже платформа молча
выбрасывает его при загрузке — отсюда гейт по версии формата. Имя свойства равно базовому
имени файла модуля, у заимствованной формы — Form. Правило владения одно: пометку ставит
тот, кто создал файл, поэтому её ставит и cfe-patch-method.
Заодно закрыта перезапись при повторном заимствовании: прямая ветка писала XML уже
заимствованного объекта начисто, унося собственные реквизиты расширения и регистрацию
формы, — молча, с успешным отчётом. Повторный вызов теперь безопасен и служит способом
дозаимствовать модуль.
cfe-validate сверяет «файл модуля ↔ пометка» в обе стороны (предупреждение: перекос
платформа принимает, но выгрузка Конфигуратора так не выглядит).
Проверено: полный регресс 827/0/8 (ps1) и 824/0/11 (py), гарды 5/5, сквозной раундтрип
на 8.3.26 и 8.3.27 — InternalInfo совпадает с выгрузкой платформы.
Закрывает #70. Разбор и эталоны выгрузки — Romandredan.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Платформа: «Назначение прав доступа на заимствованные объекты основными ролями
в расширениях недопустимо». Роль вне DefaultRoles так делать вправе, поэтому
проверяются только основные. Ловится статически, а по симптому — отказ загрузки
расширения — причина не читается.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Роль с правом на `HTTPService.Имя` формально валидна и молча не работает:
платформа проверяет вложенный объект (метод шаблона URL, операцию, канал),
маршруты отвечают 403, и по симптому причина не читается. В Конфигураторе
галки на корне сервиса нет вовсе — это недостижимое состояние, а не стилистика.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>