Серверы и плагины
Сервер LSP для редакторов, сервер MCP для агентов, локальная веб-панель и плагины со своими правилами, данными и уровнями.
Движок один, а обратиться к нему можно по-разному: резидентным сервером для редактора, набором инструментов для агента, страницей в браузере. Есть и способ расширить всё это разом.
LSP-сервер
xbsl-lsp из набора [lsp] (pip install "xbsl[lsp]") запускает линтер долгоживущим
Language Server по stdio. Пофайловые диагностики он даёт на лету, при наборе, а проектные – по
сохранению. Переход к определению, автодополнение и подсказку при наведении он строит по
резидентному индексу проекта, а быстрые исправления предлагает прямо в редакторе. Платить за
старт интерпретатора на каждый вызов при этом не нужно. Ключи: --project-root (корень
исходников относительно папки рабочей области), --select/--ignore/--enable, --data-dir,
--baseline, --templates. Подойдёт сервер любому редактору с поддержкой LSP – VS Code,
Neovim, JetBrains.
С --project-root сервер проверяет и словарь перевода проекта. Другие yaml-файлы вне корня он не
проверяет.
Проверка всего проекта читает только корень и словарь. Любой другой открытый файл, например модуль вне корня, сохраняет находки своей проверки, пока его не закроют.
Сервер живет столько же, сколько окно редактора, и код и данные на диске могут смениться под
ним: self-update, git pull редактируемой установки, обновление надстройки, переустановка,
которая переписывает данные платформы. Проверяет он при этом тем, что загрузил при старте, и его
находки расходятся с CLI и CI. Поэтому сервер сверяет версию движка, установленные надстройки,
свои файлы кода и прочитанные файлы данных платформы с их состоянием при запуске и, если что-то
из этого изменилось, один раз на каждое изменение показывает предупреждение с просьбой
перезапуска (в VS Code – команда “XBSL: Перезапустить линтер”). Проверки продолжаются, сам сервер
не перезапускается.
Всё, что редактору нужно для кода, – это стандартный LSP, поэтому обычный клиент работает без
дополнительной обвязки. Сверх этого сервер отвечает на собственные запросы xbsl/*. На них
построены панели расширения VS Code, и по ним же эти панели можно повторить в другом редакторе:
xbsl/reindexProject дожидается свежего полного индекса и проверки проекта. После публикации
диагностик возвращаются {ok, files, diagnostics}, при ошибке также error. Ожидающий запуск
по таймеру отменяется; активная проверка завершается перед новой. Открытые измененные буферы
сохраняют свою текущую диагностику.
Необязательный workDoneToken получает стандартные уведомления LSP $/progress со значениями
begin, report и end. Локализованные сообщения называют восемь этапов: ожидание, индекс,
чтение исходников, файловые правила, правила проекта, словарь перевода, публикация диагностик
и завершение. Индексация, чтение, файловые правила и проверка словаря считают обработанные
и оставшиеся файлы; проверка проекта – выполненные и оставшиеся правила с названием текущего;
публикация – документы, для которых отправлены диагностики. Проценты в сообщении относятся
к текущему этапу. Стандартное поле percentage достигает 100 только после успешной публикации.
Ошибка или отмена завершают прогресс без сообщения о полном выполнении.
| Группа | Запросы |
|---|---|
| Диагностики и подсказки | xbsl/relint, xbsl/reindexProject, xbsl/hoverDoc, xbsl/templatesReload |
| Документация платформы | xbsl/docsAvailable, xbsl/docsSearch, xbsl/docsPage, xbsl/docsTree, xbsl/docsAsset, xbsl/docsForSymbol, xbsl/docsByName |
| Схемы и словари | xbsl/uiSchema, xbsl/metadataSchema, xbsl/formKeys, xbsl/metaKeys, xbsl/metaCapabilities, xbsl/httpMethods |
| Создание метаданных | xbsl/objectInfo, xbsl/metaNewObject, xbsl/metaAddField, xbsl/metaSetFieldProperty, xbsl/metaAddForm, xbsl/metaAddRoute, xbsl/metaAddSubsystem, xbsl/metaProjectInfo, xbsl/metaMoveObject, xbsl/metaDeleteObject, xbsl/metaRenamePackage, xbsl/metaMoveResource, xbsl/metaRenameResourceFolder, xbsl/metaDeleteResourceFolder, xbsl/metaResourceReferences, xbsl/metaAddLocalization, xbsl/localizationInfo |
| Формы | xbsl/formTree, xbsl/formNodeAt, xbsl/formEdit, xbsl/searchForms, xbsl/bindingComplete |
| Обработчики событий | xbsl/moduleHandlers, xbsl/addHandler, xbsl/addModuleMethod, xbsl/removeHandler |
Запрос на создание метаданных возвращает план: полный текст каждого файла, который был бы
записан. Редактор применяет этот план одной отменяемой правкой, а сам сервер не пишет ничего.
CLI и MCP-сервер на том же коде пишут. xbsl/metaCapabilities отвечает версией сервера и
наборами видов, которые он умеет создавать: объектов, элементов разделов, форм. По ним клиент
строит меню от живого движка, а не от жёстко зашитого списка.
MCP-сервер
Тонкий адаптер над тем же ядром. Агент – например, Claude Code – зовёт проверки как инструменты и получает структурированные диагностики.
pip install -e ".[mcp]"
claude mcp add xbsl -- xbsl-mcp
Каждый пишущий инструмент meta_* применяет изменения и в том же ответе возвращает проверку
записанных файлов: создание и проверка за один вызов. Проверка краткая: число файлов и находок
и до десяти находок по строке на каждую. Полный отчёт по тем же файлам даёт lint_paths. Ядру и CLI зависимость mcp не нужна, она
лежит только в наборе [mcp].
Когда клиент сообщает, что соединение закрыто. Сервер ведёт короткий журнал в локальной
папке пользователя: когда он запустился, как завершился и что его остановило. xbsl self-update --stop-holders останавливает серверы всех сессий, чтобы заменить пакет, и клиент такого сервера
видит только закрытое соединение. xbsl mcp-log печатает журнал и называет причину. Перезапуск
MCP-клиента поднимает новый сервер. Журнал лежит в %LOCALAPPDATA%\xbsl\mcp-journal.jsonl в
Windows и в $XDG_STATE_HOME/xbsl/mcp-journal.jsonl в других системах; переменная
XBSL_MCP_JOURNAL задает другой файл.
Когда движок на диске обновили под работающим сервером. Часть модулей сервер загружает,
только когда они понадобятся инструменту. После self-update или git pull редактируемой
установки такие модули приходят из нового кода, а загруженные остаются старыми, и половины не
сходятся. Перед каждым вызовом сервер читает версию из __init__.py на диске и проверяет, не
изменились ли с запуска файлы кода движка: git pull между двумя выпусками меняет их, а номер
оставляет прежним. Для этой проверки сервер смотрит на несколько папок, где лежат файлы, а сами
файлы перечитывает, только когда папка изменилась – git заменяет файл, удаляя его и записывая
заново, – или когда с прошлого чтения прошло пять секунд: редактор может переписать файл на
месте. Если изменилась версия или файлы, все инструменты, кроме version_info, не выполняются:
вместо этого они отвечают полем error, которое называет изменение и просит перезапустить
сервер, и записью stale. У записи причина version с обеими версиями или причина sources:
версия с обеих сторон одна, а fingerprint – отпечаток файлов на диске, по нему одно изменение
отличают от следующего; ran: false говорит, что инструмент не выполнялся. version_info
отвечает как обычно, показывает engine_on_disk и несет ту же запись stale. Код надстроек
сверяет их собственная проверка (ниже). Если инструмент все же упал, сервер сверяет все файлы
кода, вместе с кодом надстроек, с их состоянием при запуске и, если они изменились, тоже просит
перезапуск; тогда запись несет ran: true: до сбоя инструмент мог что-то записать. Упавшее
правило говорит то же в своей находке. Сам сервер не перезапускается и не завершается (это
делает за него супервизор), а xbsl mcp-log показывает, когда он впервые
заметил каждое изменение.
Когда данные платформы изменились под работающим сервером. Некоторые модули берут константы из
пар терминов платформы, пока сервер их импортирует, и сброс кэшей их не перестраивает. Сервер,
который запустился за мгновение до того, как переустановка записала новые файлы данных, так и
отвечал по старым парам, а CLI читал новые. Поэтому сервер запоминает размер и время изменения
каждого файла данных, который читает, и перед каждым вызовом сверяет их с диском. Проверка
обращается к файловой системе один раз на файл и сами файлы не читает. Если прочитанный файл
изменился или пропал либо появился файл, который сервер искал и не нашел, инструменты отказывают так
же, с причиной data. Сюда относится и корень данных надстройки, у которого index.json появился
после запуска. Запись перечисляет файлы в changed относительно root, у каждого пометка
modified, added или removed. loaded и on_disk называют версии данных, а fingerprint
отличает одно изменение от следующего. version_info несет ту же запись. Файл, которого сервер не
читал, не считается: ни один ответ не опирался на него. Новая версия данных все равно заметна:
каждый сервер читает index.json, чтобы выбрать версию. Не сверяется и база документации: каждый
вызов открывает ее заново.
Перезапустить сервер может только клиент, а агент, который вызывает инструменты, этого не
может. Поэтому отказ инструмента, у которого есть команда CLI, несет поле cli – командную
строку того же вызова для POSIX-оболочки (в Windows – Git Bash). Команда запускает тот же
интерпретатор, что и сервер: он загружает движок из того же места, выполняет код и читает
данные, которые сейчас на диске. Такая команда есть у lint_paths, lint_source,
baseline_prune, list_rules, meta_fold_comments, инструментов translate_* и у
инструментов чтения meta_project_info, meta_object_info, meta_localization_info,
meta_component_tree, meta_resource_references и meta_unused_resources. Команда
инструмента чтения печатает те же данные, что и он сам, только без root и file, которые
инструмент повторяет в ответе. Текст
вызова lint_source и правки, переданные в вызове translate_set, сохраняются во временный
файл, команда читает их оттуда, а cli_note называет этот файл; папку таких файлов старше
суток, оставшуюся от прежнего сервера, убирает следующий. Относительное имя filename у
lint_source попадает в команду уже отсчитанным от рабочего каталога сервера: оттуда его
читает инструмент, и там же правила ищут рядом с ним парный yaml, а команда может запуститься
из другой папки. Находки команды несут этот абсолютный путь. Интерпретатор запускается с флагом
-P, поэтому папка xbsl в текущем каталоге оболочки не подменяет движок. В Python 3.10 такого
флага нет, и там каждый запуск команды сначала переходит в эту временную папку, в подоболочке:
(cd ПАПКА && ...). Остальные инструменты отказывают, как раньше.
Сверяются и надстройки. Если установленные надстройки не те, что загрузил сервер, инструменты
все равно выполняются, на правилах, загруженных при старте, а каждый ответ-объект начинается с
stale: причина plugins, изменившиеся дистрибутивы changed с обеими версиями и просьба
перезапустить сервер; у инструмента с командой CLI запись несет и cli – ответ по надстройкам
с диска. version_info показывает plugins_on_disk рядом с plugins. Заново
перечислять установленные дистрибутивы сервер берется, только когда меняется папка, куда их
ставят, поэтому вызову хватает одной проверки этой папки.
У каждого инструмента meta_* и у lint_paths есть параметр root – корень проекта
вызывающего. Агент в git worktree не делит рабочий каталог с сервером, поэтому параметр и нужен.
Относительные directory, yaml_path, module_path, paths, baseline и compare считаются
от него, а ответ несёт абсолютные пути записанных файлов и находок и поле root, от которого их
считали.
Без параметра берётся рабочий каталог сервера, как раньше. Относительный путь из рабочего дерева
тогда называет другую рабочую копию, и её чистый ответ выглядит как ваш.
Проверка и окружение
| Инструмент | Что делает |
|---|---|
lint_paths(paths, select, ignore, enable, baseline, no_baseline, root, as_ci, as_ci_job, compact, fix, as_ci_full, compare, list_info) |
проверить файлы и каталоги на диске. Относительные пути считаются от root, и сводка его называет. Пути нескольких корней проекта (два рабочих дерева одного проекта – это два корня) не сливаются в один проект: каждый корень проверяется так, как его проверил бы отдельный вызов, – со своим окружением проекта, своим списком принятых замечаний и, с as_ci, задачей конвейера своей рабочей копии. Ответ считает находки всех корней, а summary.projects держит запись на каждый корень: project, его собственные счетчики и его записи о списке принятых, задаче конвейера и правках. Список принятых замечаний .xbsllint-baseline применяется сам, как и в CLI: summary.baselined говорит, сколько находок погашено, no_baseline показывает и замороженные, а устаревшие записи названы поименно в summary.baseline_stale_entries, не только сосчитаны. enable добавляет к умолчаниям правило, выключенное по умолчанию, – так проект спрашивает про пробелы словаря. as_ci берет набор правил и список принятых из задачи конвейера проекта (.gitlab-ci.yml или рабочий процесс GitHub рядом с проектом): предварительная проверка тогда судит тем же, чем задача конвейера, а сводка несет as_ci: файл и задачу, включение, в котором стоит команда, корень рабочей копии, сам набор данными (select, ignore, enable, baseline) и одной строкой (flags), прочие задачи с линтером и непрочитанные включения. Той же записью отвечает --format json в командной строке. as_ci_job называет, какую из них взять: конвейер, проверяющий второе дерево с переводом, запускает линтер дважды и двумя разными наборами. Сводка считает находки по правилам, файлам и уровням (by_rule, by_file, by_severity), а compact сужает ответ: сводка остается вместе с записями о списке принятых замечаний и о задаче конвейера, в errors лежат полные записи находок уровня error, а до 10 находок (report.COMPACT_FINDINGS_LIMIT) findings перечисляет их строками файл:строка правило – сообщение. Больше 10 – findings пропадает, а findings_hint говорит, сколько находок и как их прочитать: полный текст каждой стоит по несколько сотен знаков, а вопрос часто один – чисто ли дерево. Находки уровня info сосчитаны, но не перечислены и в 10 не входят: info_hint называет их число и правила, а list_info=true перечисляет их. compact=true также убирает summary.by_file и сводит summary.as_ci к одной строке brief: файл относительно корня рабочей копии, задача, ключи, где длинный список сосчитан (--enable ×10), другие задачи, пока задачу не назвали, и непрочитанные включения. Всю запись о задаче конвейера оставляет as_ci_full=true, а полную карту файлов дает compact=false. Сколько бы проверка ни нашла, краткий ответ укладывается в 12 000 знаков JSON (report.COMPACT_ANSWER_LIMIT): сверх этого списки, которые растут с проверкой, обрезаются с конца – сначала устаревшие и переформулированные записи списка принятых, затем findings, записи errors, записи корней проекта и by_rule. truncated называет каждый обрезанный список ({shown, total}), а truncated_hint говорит, как получить полный ответ. Счетчики не обрезаются никогда. fix=true применяет доступные правки и возвращает оставшиеся находки; по умолчанию инструмент только читает. Принятые места защищены между проходами, файл списка не меняется. summary.fixed считает примененные правки, summary.files_changed – измененные файлы. compare называет файл, где запуск хранится до следующего вызова; файл тот же, что у ключа --compare в командной строке. Первый вызов сохраняет запуск и отвечает compare: {file, compared: false}. Каждый следующий сравнивает запуск с сохраненным, записывает его на место старого и вместо находок отвечает {summary, compare}. appeared и disappeared считают изменившиеся находки, в rules по строке на каждое изменившееся правило (rule, files, findings, appeared, disappeared), findings перечисляет изменившиеся находки записями (path, line, col, rule, message), а not_compared называет пути и правила, которые проверял только один из двух запусков. Пути сопоставляются по каталогу, который они называют, а затем по написанию, поэтому запуски одного каталога по относительному и абсолютному пути сравниваются друг с другом. С compact при числе изменений больше 10 список заменяет подсказка findings_hint, а весь список до следующего вызова лежит в файле, в разделе changes. |
lint_source(filename, content, select, ignore) |
проверить содержимое в памяти, до записи файла |
baseline_prune(paths, select, ignore, enable, baseline, dry_run, root) |
снять записи, которые этой проверке больше не нужны (ключ CLI --prune-baseline). Ответ называет каждую: путь, правило, сообщение, количество и причину, написанную человеком. Файл сохраняет свой порядок и формат. Записи правил, которых у сервера нет, и файлов вне paths не трогаются, а dry_run показывает, что ушло бы. Пути нескольких корней проекта проверяются каждый отдельно; корни со своими списками (два рабочих дерева) чистятся каждый своим вызовом, а вызов по ним сразу отклоняется с названными файлами |
list_rules(select, ignore, filter) |
доступные здесь правила: id, заголовок, тир, область, severity – и params у правила, которое судит по числу (действующее значение, умолчание, переменная среды для переопределения); select отвечает про одно правило вместо всего реестра, а filter сужает ещё: слово, совпадающее с именем группы (частью id до /), выводит только эту группу, а любое другое ищется как подстрока id или слово названия либо описания: это весь текст i18n, зарегистрированный под id правила (название и шаблоны сообщений, из которых собираются диагностики), на любом из языков, плюс английский докстринг; docs/RULES.md не читается – он не бандлится ни с sdist, ни с wheel. Сравнение без учёта регистра. Фильтр, ничего не нашедший, отвечает {error, near_groups} – ближайшими по написанию группами – вместо пустого списка |
version_info() |
чем отвечает окружение: движок, интерпретатор, версия данных, надстройки. Так различают два окружения, которые отвечают на одном файле по-разному. engine_on_disk и plugins_on_disk говорят, что установлено сейчас, а stale появляется, когда это расходится с загруженным или когда прочитанный сервером файл данных платформы изменился на диске (причина data) |
Документация платформы и схемы
| Инструмент | Что делает |
|---|---|
docs_search(query, limit) |
полнотекстовый поиск по документации 1С:Элемент |
docs_page(id, brief, section) |
страница документации по идентификатору из двух других инструментов; brief – одна шапка: краткое описание и имена разделов вместо текста, section – шапка плюс один раздел статьи (Свойства, Методы, Конструкторы, …; на неизвестный раздел ответ – перечень имён на выбор) |
docs_symbol(name, brief, section) |
документация символа по имени, в любом написании: тип отвечает своей страницей и теми же режимами brief и section, член (Подстрока, Строка.Найти) – блоком одного этого члена со склеенными перегрузками: своей страницы у члена нет. Член, объявленный несколькими типами, отвечает их именами и тем, как переспросить |
type_members(name) |
члены stdlib-типа одним компактным ответом – что может стоять после точки; дешевле страницы, когда нужен только список членов |
ui_schema(component, brief, property) |
ui-схема компонента интерфейса: палитра конструктора и типизированные свойства |
metadata_schema(kind, sections, names) |
какие свойства вправе объявить элемент заданного ВидЭлемента |
Трём инструментам docs_* нужна база docs.sqlite (см. Поиск по документации), а двум схемным – сгенерированные данные о языке. Страница типа – это тысячи знаков: конструкторы, каждое свойство, списки унаследованного. Целиком она нужна тогда, когда её собираются читать. brief отвечает на вопрос “что это за страница и о чём она”, а section – на один вопрос по ней.
Перевод исходников (см. Перевод проекта)
| Инструмент | Что делает |
|---|---|
translate_status(root, against, full) |
покрытие и остаток, дешёвая проверка перед любым решением. Корень без словаря получает отказ с перечнем мест, где словарь искали; against называет git-ссылку и добавляет collisions – ключи, которые файлы словаря в рабочем дереве и на ссылке переводят по-разному или одинаково, тот же отчёт, что у xbsl translate --check-duplicates. Дубли, которые есть и на ссылке, приходят одинаковыми при каждом вызове в ветке, поэтому они только посчитаны (duplicates_total, duplicates_at_ref); весь список отдает full=true. Конфликты перечисляются всегда. drift считает фразы, которые перечисляет translate_drift |
translate_gaps(root, kind, filter, limit, offset, compact) |
чего словарь ещё не покрывает, страницами: частота, первые места, собственное написание платформы подсказкой; compact оставляет в строке только ключ, вид и частоту; в ответе – прочитанный dictionary |
translate_entries(root, kind, filter, limit, offset, compact) |
что словарь уже говорит, с файлом и строкой каждой записи; по умолчанию страница в десять строк, а compact оставляет в строке только ключ, вид и значение |
translate_unused(root, kind, filter, since, limit, offset, prune, compact, budget_seconds) |
записи словаря, которые проект больше не использует; filter принимает подстроку или список, since ограничивает кандидатов одной правкой. Просмотр по умолчанию возвращает полные строки; compact=true оставляет ключ, вид, файл и строку. prune=true снимает все ключи, которые выбрали отборы, какой бы ни была страница, вместе со всеми их объявлениями, и обычно не возвращает список: removed считает вхождения, pruned.keys – пары, pruned.by_kind / pruned.by_file дают разбивку. Явное compact=false возвращает полные строки после снятия. counts охватывает весь отфильтрованный набор кандидатов, и prune тоже: страница определяет только список. budget_seconds (по умолчанию 300) ограничивает обход; ответ partial перечисляет кандидатов и ничего не удаляет. |
translate_set(root, edits, edits_file, target, comment) |
запись: добавить, исправить на месте (во всех местах, где объявлен ключ) или снять, обнулив значение; edits_file – пачка файлом (yaml формата словаря, который читает загрузчик самого словаря, поэтому годится и выгрузка с ключами в кавычках, либо список JSON; пачку без записей инструмент отвергает и называет ключи верхнего уровня, которые в ней есть), comment – заголовок, с которым создаётся новый файл словаря. Каждый план записывается тем написанием, какое читает его проход: запись, которая не сработала бы, возвращается в refused, а та, что читается однозначно – фраза с кавычкой, экранированной по правилам литерала, – записывается этим чтением и попадает в normalized |
Все четыре отвечают страницами поверх одного ядра движка, поэтому наполнение словаря на тысячи записей не требует вычитывать файлы.
Проект и его объекты
| Инструмент | Что делает |
|---|---|
meta_project_info(root, kind, subsystem, brief, package, project, reference) |
карта исходников под корнем: проекты, подсистемы, пакеты, объекты по видам; kind, subsystem и package сужают перечень объектов, project обходит только названный проект, а справочные разделы (виды объектов, виды секций, методы доступа) приходят с reference или brief |
meta_object_info(root, name, yaml_path) |
описание одного объекта: всё, что нужно, чтобы писать его формы и код |
meta_new_project(...) |
создать проект: Проект.yaml, Проект.xbsl и первую подсистему |
meta_new_object(directory, kind, name, ...) |
создать объект: <Имя>.yaml и <Имя>.xbsl у видов с модулем; presentation и object_presentation пишут заголовки списка и объекта справочника или документа, record_presentation – заголовок записи регистра сведений или периодического набора констант, periodicity делает набор констант периодическим |
meta_rename_object(..., dry_run, full) |
переименовать объект и обновить все ссылки в исходниках. Ответ краткий: переименованные файлы, заметка со счётом и outside_projects – правленые файлы, которые не принадлежат ни одному проекту, например словарь перевода рядом с проектом. full=true перечисляет каждый правленый файл |
meta_delete_object(..., dry_run) |
удалить объект целиком: пару yaml/модуль, его формы и описания WSDL клиента SOAP-сервиса |
meta_move_object(root, yaml_path, target_dir, dry_run) |
перенести объект с формами, модулями, строкой и таблицей списка в пакет, другой пакет, корень подсистемы или другую подсистему; импорты, нужные ссылкам на новом месте, квалифицированные имена старого места и Использование чинят правила импорта, прогнанные до и после переноса, а занятое имя и непубличный элемент, который станет нужен другой подсистеме, получают отказ |
meta_rename_package(root, package_dir, new_name, dry_run) |
переименовать каталог пакета со всеми файлами и переписать импорт Подсистема::Пакет, элементы Импорт и квалифицированные имена по проекту |
meta_move_resource(root, resource_path, target_dir, dry_run) |
перенести файл ресурса или папку в другую папку того же каталога Ресурсы и переписать ключи Ресурс{...} и значения свойств-картинок, которые на него указывают; обращения по строке – в notes, перенос в другой каталог Ресурсы запрещён |
meta_rename_resource_folder(root, folder_dir, new_name, dry_run) |
переименовать папку внутри каталога Ресурсы со всеми файлами и переписать ключи, которые на них указывают |
meta_delete_resource_folder(root, folder_dir, dry_run) |
удалить папку внутри каталога Ресурсы с файлами и перечислить ключи и обращения по строке, которые на них указывают; dry_run по умолчанию истина |
meta_resource_references(root, resource_path, limit) |
найти места, которые называют файл ресурса или папку: ключи Ресурс{...} и значения свойств-картинок, ключи из двух каталогов, строки с путем или с ключом файла без расширения; у места есть файл, диапазон, строка и вид; limit (по умолчанию 100) ограничивает список, total считает все места, а hasMore равно true, когда в список попали не все |
meta_unused_resources(root, include_protected, limit) |
найти ресурсы без известных обращений; вычисляемые и неопределенные обращения учитываются отдельно, кандидаты требуют проверки, файлы не удаляются |
meta_add_subsystem(parent_dir, name, ...) |
создать подсистему – папку с Подсистема.yaml |
meta_add_dependency(root, vendor, name, version, ...) |
подключить библиотеку – раздел Библиотеки в Проект.yaml |
meta_set_access(root, ..., default, permissions, calc_by) |
задать КонтрольДоступа.Разрешения у объекта |
Поля, маршруты, методы, формы, локализация
| Инструмент | Что делает |
|---|---|
meta_add_field(yaml_path, field_kind, name, type, props, names, doc, ...) |
добавить элемент раздела: реквизит, измерение, ресурс, значение перечисления, параметр, поле, свойство контракта или компонента интерфейса (имя и тип, без Ид), табличную часть. doc – описание элемента, оно пишется документирующим комментарием: строками ## в начале элемента, которые среда разработки читает и сохраняет; стандартному реквизиту, ключу локализованных строк и пачке из нескольких имен описание не задается. names добавляет за один вызов несколько элементов одного вида с общими типом и свойствами, например значения перечисления; пачка планируется целиком, и занятое имя отменяет ее всю. Стандартный реквизит (Номер, Дата, Код, Наименование, Владелец) судится своим классом: Длина, Уникальность и блок Автонумерация принимаются, а type можно не указывать там, где класс его задает. props принимает вложенный блок словарем или ключом через точку (Автонумерация.Префикс), а список – последовательностью. Блок класса, которого метамодель не описывает (Представление), отвергается как известное ограничение. Секция, которой в файле нет, заводится в конце файла (Свойства компонента – перед его События, в порядке, в котором пишет среда разработки); у регистра поле notes говорит об этом и называет соседний вид, который положил бы элемент рядом с существующими полями: ресурс там, где просили реквизит, и наоборот |
meta_set_field_property(yaml_path, field_kind, name, props, ...) |
задать свойства уже существующему элементу раздела; формы значений те же, что у meta_add_field, вложенный блок заменяется целиком, а Тип читается так же, как его type: экранирование разметки снимается, имена платформы пишутся на языке файла |
meta_add_route(yaml_path, routes, template, methods) |
добавить HttpСервис шаблоны url и заготовки обработчиков |
meta_add_method(module_path, name, params, returns, ...) |
вставить метод в модуль .xbsl, не разрывая блоки аннотаций |
meta_add_form(root, ..., forms, card_min_width, card_placeholder) |
сгенерировать формы объекта и зарегистрировать их в Интерфейс |
meta_add_localization(yaml_path, language) |
добавить файл перевода элементу локализованных строк |
meta_set_localization(yaml_path, name, values, entries, section, dry_run, full_text) |
записать одну строку локализации или сразу много во все языки: текст языка по умолчанию – в сам элемент, каждый другой – в свой файл перевода; язык, о котором ключ молчит, всё равно получает строку значением языка по умолчанию и заметкой. entries пишет много ключей ({ключ: values}) за один проход: файл, который трогают несколько ключей, читается и пишется один раз, а весь пакет применяется целиком или не применяется вовсе. dry_run отвечает сводкой summary (ключ, язык, файл, значение до и после) вместо целых файлов; full_text добавляет файлы рядом с ней |
meta_localization_info(yaml_path) |
картина локализации: объявленные языки и что ещё не переведено |
Компоненты формы – конструктор из скрипта
| Инструмент | Что делает |
|---|---|
meta_component_tree(yaml_path, node_id, name, max_depth, properties, brief) |
дерево узлов компонента интерфейса; большую форму можно брать частями – поддеревом (по идентификатору узла или по его Имя), с пределом глубины, без записей свойств либо одним скелетом (brief – идентификаторы, виды, типы, имена и слоты: несколько килобайт на дерево в сотни) – большое целое дерево несёт подсказку об этих ручках |
meta_add_component(yaml_path, parent_id, slot, ...) |
вставить новый компонент в слот родительского узла |
meta_insert_fragment(yaml_path, parent_id, slot, fragment, ...) |
вставить в слот готовый блок yaml одного компонента (скопированное поддерево). Пояснение # из фрагмента ложится туда, где его читает среда разработки: комментарий над компонентом уходит внутрь узла строками ##, а пояснение без такого места остаётся и называется в notes, см. Комментарии в yaml |
meta_fold_comments(paths, dry_run, take_proposed, root, compact) |
свернуть комментарии, которые среда разработки не читает, в описание ближайшего узла, у которого есть место, – то же, что делает xbsl fold-comments, см. Комментарии в yaml. Ответ – отчет --format json: переносы по каждому файлу, итог проверки, число записанных файлов и счет переносов по виду и действию. dry_run по умолчанию истинно, и ничего не пишется; dry_run=false записывает файлы, прошедшие проверку, а take_proposed применяет и спорные переносы, как --all. По целому дереву отчет занимает сотни килобайт, и compact дает краткий: счет, файлы с наибольшим числом переносов и переносы, на которые стоит взглянуть (спорные и оставленные первыми), до десяти строк каждый список, и все файлы, которые не прошли проверку |
meta_move_component(yaml_path, node_id, new_parent_id, slot, ...) |
перенести узел в другой (или тот же) слот; комментарии над ним едут следом |
meta_move_components(yaml_path, node_ids, ...) |
перенести несколько узлов одной операцией, сохранив их порядок в документе |
meta_remove_component(yaml_path, node_id) |
удалить узел вместе с его комментариями |
meta_remove_components(yaml_path, node_ids) |
удалить несколько узлов одной операцией |
meta_set_component_property(yaml_path, node_id, key, value, value_yaml) |
задать, заменить или удалить свойство узла |
meta_add_handler(yaml_path, node_id, key, method, signature) |
привязать событие узла к методу-обработчику парного модуля |
Каждый инструмент, который принимает узел, понимает и путь из meta_component_tree, и Имя
компонента, если другого компонента с таким именем в форме нет. Путь позиционный и после правки
сдвигается, имя остается. Повторное имя отклоняется со списком путей, из которых можно выбрать.
Те же операции доступны из CLI (Команды) и, для редактора, через LSP-запросы xbsl/meta*.
Супервизор сервера
xbsl-mcp запускает сервер за супервизором, который сохраняет сессию клиента, когда движок
обновляют. Супервизор держит stdio, по которому говорит клиент, и запускает за собой сервер
рабочим процессом: python -m xbsl.mcp_server на том же интерпретаторе, а stderr процесса уходит
в журнал клиента. Супервизор запоминает initialize клиента и, когда рабочий процесс пора
сменить, запускает новый и повторяет ему это рукопожатие. Клиент продолжает говорить как будто
с тем же сервером, и перезапускать его не нужно.
claude mcp add xbsl -- xbsl-mcp
python -m xbsl.mcp_supervisor – та же команда, а xbsl-mcp-supervisor остается ее вторым
именем. Супервизор стоит еще одного небольшого процесса и еще одной пересылки на каждое
сообщение. Клиенту, которому нужен сам сервер, указывают xbsl-mcp --no-supervisor – сервер
работает в том же процессе – или python -m xbsl.mcp_server; тогда обновление движка кончается
отказом с просьбой перезапустить сервер, как описано выше.
self-update заменяет пакет, но не заглушки команд, а заглушка, сделанная прежним выпуском,
запускает сам сервер. Такая заглушка передает свой процесс супервизору до загрузки движка,
поэтому после обновления xbsl-mcp тоже запускает супервизор. Заглушку команды, которая
появилась в новом выпуске, например xbsl-mcp-supervisor, создают только pip install --upgrade
или pipx reinstall.
Когда процессу пора уходить, решает он сам, а супервизор читает это из его ответов:
- отказ со
stale.ran: falseзначит, что инструмент не выполнялся: у движка на диске другая версия, при той же версии изменились файлы его кода или изменился прочитанный процессом файл данных платформы. Супервизор отправляет тот же вызов новому процессу, и агент получает ответ нового кода и новых данных вместо отказа.version_infoспрашивается заново так же: он только читает. Процесс, запущенный после одного изменения файлов, заменяется и после следующего: их различаетfingerprintзаписи; - ответ, в
staleкоторого сказано, что инструмент выполнялся (упал на смеси старого и нового кода или надстройки на диске сменились), доходит до агента как есть: инструмент мог записать файлы. Следующий вызов уходит новому процессу; - процесс, который упал или который остановил
self-update --stop-holders, заменяется при следующем вызове, а не сразу, чтобы новый процесс не загрузил пакет, который обновление еще распаковывает. Вызов, который этот процесс выполнял, возвращается ошибкой: что он успел записать, неизвестно. Запрос, который только читает, уходит новому процессу.
Сам супервизор движок не загружает и файлов пакета открытыми не держит. self-update не
считает его держателем и не останавливает, а старого кода своего он не исполняет; держатель –
только рабочий процесс и сервер, запущенный с --no-supervisor. У xbsl-mcp, в котором еще
работает сам сервер прежнего выпуска, рабочего процесса нет, и self-update останавливает его как
держателя. Старый процесс не убивают посреди вызова: новых вызовов он не получает, заканчивает начатые и завершается.
Когда работу принимает новый процесс, клиенту сообщают, что список инструментов изменился, и
клиент запрашивает его снова, так что инструменты и параметры из обновления видны без
перезапуска. xbsl mcp-log называет каждый замененный процесс и причину замены. Когда клиент
закрывает свой конец, рабочие процессы тоже получают конец ввода, и супервизор завершается вслед
за ними.
Повторное initialize просит ту версию протокола, которую просил клиент, но новый процесс может
согласовать другую, чем клиенту ответили в начале сессии: обновление пакета mcp способно убрать
версию, на которой говорит клиент. Супервизор тогда ничего не меняет – клиент остается при своей
версии, а новый процесс работает дальше, – но записывает обе версии в журнал, и xbsl mcp-log
показывает их рядом с заменой. Если клиент после обновления ведет себя странно, смотреть стоит
на эту строку. Процесс, который согласовал ту же версию, такой строки не оставляет.
Веб-интерфейс
Локальная страница: укажите папку проекта и увидите замечания. Написана она на стандартной
библиотеке, внешних зависимостей нет, и слушает она только 127.0.0.1.
xbsl-web # затем открыть http://127.0.0.1:8771/
На странице есть настройки правил по тирам, выбор версии данных, фильтры по severity и по
тексту, тёмная и светлая темы. Клик по замечанию открывает файл в VS Code (vscode://).
Расширение: свои правила, данные и уровни
Три группы точек расширения (entry points) позволяют отдельному пакету дополнить линтер, не
отводя собственную его копию. Это нужно тем, чьи правила или языковые данные нельзя публиковать:
их держат в закрытом пакете, который зависит от xbsl.
# pyproject.toml вашего пакета
dependencies = ["xbsl>=1.0.0"]
[project.entry-points."xbsl.rules"]
мой-проект = "мой_проект.rules" # импорт модуля выполняет его декораторы @rule
[project.entry-points."xbsl.data"]
мой-проект = "мой_проект:data_root" # путь либо функция, возвращающая путь
[project.entry-points."xbsl.severity"]
мой-проект = "мой_проект:severity_overrides" # {id правила: "error"|"warning"|"info"|"off"}
продолжают работать: старые группы сканируются вслед за новыми.
Словарь уровней – или функция без аргументов, которая его возвращает – поднимает и понижает
уровень любого правила, встроенного или из плагина, для всех запусков этой установки. Проект
может считать, например, style/abbreviation-case предупреждением, пока публичное значение по
умолчанию остаётся info. "off" убирает правило из набора по умолчанию; явный --select или
--enable всё равно включит его, с базовым уровнем.
Пакет установлен – и CLI, и MCP-сервер, и веб-панель видят все расширения. Ключей и файлов
настройки для этого не нужно. Сбой точки расширения роняет запуск, а не ограничивается
предупреждением: линтер, молча потерявший правило, остаётся зелёным в CI и ничего не
гарантирует. Переопределение с неизвестным id правила или уровнем падает по той же причине.
XBSL_NO_PLUGINS=1 игнорирует все внешние пакеты: остаются встроенные правила, вшитые данные и
уровни по умолчанию.