Skip to content

v1.33.0

Latest

Choose a tag to compare

@github-actions github-actions released this 13 Aug 16:35
· 1 commit to master since this release

Миграция индекса BUILDER_VERSION 14 → 15. Требуется полная пересборка индексов — она запускается автоматически на первом rlm-bsl-index index update и идёт на месте. Схема SQLite не менялась (те же 27 таблиц + FTS5), меняется содержимое methods, register_movements, form_elements и metadata_references: из индекса убраны объявления и движения, которых в коде нет, и добавлено то, что есть, но не индексировалось.

Устаревший индекс продолжает работать и отвечать — при старте сессии приходит предупреждение с точной командой пересборки.

Пересборка идёт in-place: прежние данные сносятся до наполнения новыми, и прерывать её не следует. Прерванная сборка детектируется (rlm_start вернёт index.index_status="incomplete" и предупреждение), лечится повторным запуском rlm-bsl-index index update, а не откатом. Если простой недопустим — соберите копию рядом: укажите RLM_INDEX_DIR на временный каталог, дождитесь сборки и переключите переменную.

Исправлено

  • Индекс содержал процедуры и функции, которых в коде нет, и терял настоящие. Было так: разбор объявлений применял регекс неякорно к сырой строке, поэтому процедурой считались и // Процедура ОпределитьНастройки(Настройки) Экспорт из шапки-примера, и Шаблон = "Процедура Призрак(А) Экспорт";, и строка-продолжение текста запроса |Процедура Призрак(Б) Экспорт. Хуже призраков была вторая половина эффекта: после призрака курсор проматывался за его мнимый КонецПроцедуры, и следующее НАСТОЯЩЕЕ объявление уже не рассматривалось — метод пропадал из индекса целиком. На боевых конфигурациях это давало от 4 до 107 призраков и от 4 до 68 потерянных методов на каждые 4000 модулей. Стало: и билдер, и read-time разбор смотрят на исходник через маску той же длины, в которой погашены комментарии и содержимое строковых литералов, а сам регекс заякорен на начало строки. Значения параметров по умолчанию при этом сохраняются дословно: маска нужна только для сопоставления, а текст params вырезается из оригинала по её смещениям.
  • Список параметров обрывался на скобке из строкового значения по умолчанию. Грань того же дефекта: шаблон сигнатуры берёт содержимое скобок как «всё до первой закрывающей», поэтому параметр вида Знач Текст = "Выполняется проверка (%1%)" заканчивал список на скобке, лежащей внутри литерала. В индекс уходила обрезанная сигнатура — без остатка значения по умолчанию и без всех следующих параметров, — причём молча: объявление при этом находилось, у него просто был неполный params. Стало: скобки внутри литералов и комментариев погашены маской, поэтому граница списка ищется по коду, а текст params по-прежнему вырезается из оригинала дословно.
  • Комментарий внутри многострочного литерала гасил остаток модуля. Отдельный дефект того же семейства, найденный при проверке маски на боевых исходниках. 1С разрешает строку //… между строками-продолжениями многострочного литерала, и разработчики так комментируют куски текста запроса. Если закомментированная строка несёт закрывающую кавычку, посимвольный разбор считает литерал закрытым и рассинхронизируется до конца файла: в одном модуле так терялось 233 настоящих объявления. Правило «строка-комментарий внутри литерала состояние не меняет» применено и к маске, и к общесистемному сканеру _scan_module (он питает read-time перепроверки и обратный код-индекс). Правило меняет разбор 819 модулей из 23 461 на одной конфигурации и 581 из 12 000 на другой.
  • Асинхронные методы не индексировались. Однострочная Асинх Функция X(А) Экспорт в индекс попадала, а многострочная — нет: склейка сигнатуры про префикс Асинх не знала и не срабатывала, а несклеенную сигнатуру регекс не сопоставлял. На одной из боевых конфигураций так терялось 7 объявлений из 117 асинхронных. Префикс Асинх/Async добавлен и в разбор объявлений, и в детектор расширений — у перехвата асинхронной функции больше не пустое имя метода расширения.
  • Разбор модулей расширений расходился с билдером. Основная конфигурация чинилась, а модули расширений продолжали отдавать призраков и терять методы — то есть две половины одного ответа (get_overrides / extract_procedures / find_ext_overrides) противоречили друг другу. Теперь read-time путь использует ту же маску. Заодно исправлены два дефекта чтения тела перехвата: многострочная сигнатура перехвата не находилась вовсе (полный шаблон объявления требует закрывающую скобку, которой на первой строке нет) — на боевых расширениях это 30 перехватов из 799; и КонецПроцедуры внутри строкового литерала обрывал тело раньше времени.
  • Движения регистров попадали в индекс из комментариев и строк. // Движения.Призрак.Записать();, //МеханизмыДокумента.Добавить("X") и закомментированное ИмяРегистра = "X" учитывались как настоящая запись. Экстрактор движений переведён на ту же маску, причём в двух режимах: где имя регистра берётся из КОДА — гасятся и комментарии, и литералы; где имя по замыслу читается ИЗ литерала — гасятся только комментарии, иначе исчез бы сам сигнал.
  • Имя метода расширения не находилось за блоком комментариев. Сканер аннотаций смотрел фиксированное окно в 3 строки после &Вместо/&Перед/&После/&ИзменениеИКонтроль, поэтому объявление, отделённое комментариями или директивами компиляции, в окно не попадало и перехват оставался с пустым extension_method: 14 из 199 на одной боевой конфигурации и 14 из 604 на другой. Стало: окна нет, пропускается всё незначащее (комментарии, пустые строки, директивы # и &) до первого объявления или до следующей аннотации. На обеих конфигурациях перехватов без имени метода теперь ноль.
  • Закомментированная аннотация считалась живым перехватом и приписывала себе следующую настоящую функцию. Детект аннотации тоже переведён на маску. На боевых расширениях таких строк 4 из 803 — ровно на столько уменьшилось общее число перехватов.
  • Основной реквизит формы Конфигуратора не определялся никогда. CF-ветка парсера искала тег <Main>, а реальные формы Конфигуратора пишут <MainAttribute>: на 400 живых формах <MainAttribute> встречается 331 раз, <Main> — ноль раз. Поэтому attribute_is_main=1 не встречался ни разу на девяти боевых индексах и ~700 000 реквизитов форм. Стало: читается <MainAttribute> (<Main> оставлен запасным вариантом). Доля основных реквизитов на конфигурации в формате Конфигуратора — 5.22%, что сходится с 5.64% на конфигурации в формате EDT, где ветка работала корректно.
  • Правка формы Конфигуратора не отражалась в индексе. Инкрементальное обновление отбирало для пересборки form_elements только файлы .form — это формат EDT. Форма Конфигуратора лежит как …/Forms/<Имя>/Ext/Form.xml и в этот отбор не попадала вовсе, поэтому после первой сборки её содержимое замораживалось навсегда. Оговорка, которую надо назвать вслух: ветка пересборки form_elements не селективна и переcобирает формы по ВСЕЙ конфигурации — это поведение существовало и для .form, теперь оно распространено на CF. На боевом проекте это ~70 000 форм.
  • Инкрементальное обновление теряло движения регистров из нетронутого модуля. Дефект пре-существующий, но с появлением записи из модуля менеджера он стал массовым, поэтому чинится здесь. Было так: обновление удаляло строки register_movements по ИМЕНИ ДОКУМЕНТА, то есть сносило записи ОБОИХ его модулей, а восстанавливало только те, что извлечены из переобработанного файла. Правка ObjectModule стирала движения из ManagerModule (механизмы, таблицы движений, адаптированные регистры, а с этой версии и наборы записей) и наоборот; вернуть их могла только полная пересборка. Отдельная грань того же дефекта: при УДАЛЕНИИ модуля блок пересборки движений не выполнялся вовсе, и его строки оставались в таблице сиротами навсегда. Стало: строки удаляются по ФАЙЛУ — переобработанные и удалённые модули чистятся точечно, нетронутый соседний модуль сохраняет свои движения.
  • Правка общей формы не отражалась в индексе. Отбор форм для пересборки требовал промежуточного каталога Forms/, которого у общих форм нет: они лежат как CommonForms/<Имя>/Ext/Form.xml. Поэтому основной реквизит и типы реквизитов общих форм оставались устаревшими до полной пересборки — на боевом проекте это 433 формы. Признак уточнён до */Ext/Form.xml, что покрывает обе раскладки.
  • Номер строки ссылки на объект метаданных указывал не туда. Было два дефекта сразу. Первый: искалось объявление владельца (<Name>Реквизит</Name>), а не сама ссылка, — на выборке 3000 ссылок типа реквизита строка НИ РАЗУ не совпала со строкой тега типа. Второй, более опасный: бралось ПЕРВОЕ вхождение имени по файлу, а 30% имён реквизитов встречаются в одном XML больше одного раза — в 1042 случаях из 1195 это был реквизит табличной части, для которого возвращалась строка совсем другого элемента. Стало: один проход по файлу строит индекс якорей, резолв идёт по ключу владельца (вид, табличная часть, имя) плюс сама ссылка, а не по позиции, — порядок, в котором парсер отдаёт ссылки, роли не играет. Составной тип, у которого до 266 типов у одного реквизита, получает по своей строке на каждый тип. Проверено сплошным прогоном по всем документам, справочникам, регистрам сведений и планам видов характеристик обоих форматов: 33 221 ссылка на 3071 файле в формате Конфигуратора и 35 919 на 2928 файлах в формате EDT — ноль неверных строк и ноль пропусков; реквизиты табличных частей на 100% попадают внутрь границ своей табличной части.
  • Сервер представлялся чужой версией. Конструктор FastMCP 1.x параметра версии не принимает, а низкоуровневый сервер без неё подставляет версию пакета mcp: интегратор видел в serverInfo.version версию транспортной библиотеки и не мог выбрать парсер под нашу. Теперь отдаётся версия rlm-tools-bsl.
  • Опечатка в имени секции профиля давала пустой ответ без единого признака ошибки. get_object_profile(name, sections=["predefined_items"]) возвращал УСПЕШНЫЙ результат с пустым sections: неизвестный ключ отбрасывался молча. Так себя вела любая опечатка. Теперь отброшенные имена перечисляются в _meta.arg_warning вместе со списком принятых.
  • Неизвестный вид ссылки в find_references_to_object(kinds=...) тоже давал пустой ответ молча. kinds=['Subsystem'] — естественная догадка по человекочитаемому имени вида — не совпадает ни с одной строкой (внутренний ключ subsystem_content), и хелпер возвращал total=0, неотличимый от честного «ссылок нет»; на таком ответе строится ложный вывод «объект ни в какие подсистемы не входит». Теперь отвергнутые виды перечисляются в _meta.arg_warning вместе со списком допустимых. Валидные виды продолжают фильтровать как прежде, а ключ _meta появляется ТОЛЬКО при наличии предупреждения — контракт ответа без опечатки не изменился.
  • Имя элемента формы в ответе parse_form не совпадало с именем колонки индекса. В базе колонка называется element_name, а хелпер отдаёт element (у обработчиков) и name (у команд и реквизитов), поэтому обращение по имени колонки возвращало None без ошибки — как будто данных нет. Имена ключей всех трёх списков теперь перечислены в сигнатуре хелпера, документация приведена в соответствие.

Добавлено

  • search_regions(..., group_by='name'|'category') — топ-N областей со счётчиками. Раньше корректного способа ответить «какие области встречаются чаще всего» не было вовсе: count_only=True отдаёт одно число, а списочная ветка при пустом запросе идёт ORDER BY name, поэтому limit возвращает не выборку, а алфавитный префикс — построенный по нему топ-10 на конфигурации из 57 652 областей не пересекался с истинным ни одной позицией. Стало: группировка считается по полному набору средствами SQLite, limit режет уже готовые группы, а ответ несёт groups_total и truncated. Условие отбора и охват те же, что у count_only, поэтому сумма счётчиков по всем группам равна его total. Неизвестное значение group_by даёт пустой ответ с _meta.arg_warning и списком допустимых — так же, как неизвестный вид ссылки в find_references_to_object.
  • Предупреждения о ложном отрицательном выводе — в подписи хелперов. Три случая, где ответ означает не «данных нет», а «спросили не так», и потребитель об этом не знал. У parse_form ключ types — список, а в выгрузке Конфигуратора его элемент несёт префикс пространства имён (cfg:, xs:, v8: и другие; в проекте EDT префикса нет вовсе), поэтому привычная проверка 'DynamicList' in attr['types'] сравнивает со ВСЕМ элементом и на Конфигураторе всегда отвечает «нет» — сверять надо хвост элемента. У search_regions и search_module_headers поиск идёт подстрокой без учёта словоформ, поэтому 'Себестоимость' не находит 'Себестоимости', и ноль совпадений не доказывает отсутствия. У search_methods основной полнотекстовый индекс построен по трёхсимвольным фрагментам: запрос короче трёх символов по нему не ищется вовсе, а на конфигурации с расширениями совпадения, если они есть, придут только из расширений — то есть ни пустой, ни неполный ответ такого запроса ничего не доказывает. Все три теперь названы прямо в подписи — она приходит агенту на каждом старте, в отличие от рецепта, — с указанием, как спросить правильно.
  • Признак усечения списочной выдачи. search_regions, search_module_headers, search_objects и search_methods возвращают простой список, в который нечего дописать, поэтому усечение по limit до сих пор было полностью беззвучным: агент строил агрегаты по срезу, не зная, что видит часть. Теперь, если вызов вернул ровно limit строк, в ответе rlm_execute приходит подсказка list_truncated:<хелпер> — с честной оговоркой, что совпадение с limit усечение не доказывает, и с указанием, чем ответить точно. Возвращаемое значение хелпера и stdout при этом не меняются: подсказка живёт только в метаданных ответа.
  • Движения регистров из модуля менеджера документа — новое значение source="manager_code" в register_movements. Однозначная форма Регистры<Тип>.<Имя>.СоздатьНаборЗаписей() (любого вида: Накопления, Сведений, Бухгалтерии, Расчёта) раньше не индексировалась вообще, хотя встречается 101 раз на одной боевой конфигурации и 109 на другой. Чистый прирост — от 49 до 59 пар «документ ↔ регистр» на 25–30 документах на каждой из четырёх проверенных конфигураций. Имя регистра сверяется с каталогами регистров на диске: наивное расширение прежнего шаблона Движения.X на модуль менеджера давало 31–34% мусора (Движения.Количество, Движения.Отбор, Движения.Колонки — это поля уже полученного набора записей, а не имена регистров). Строки видны в find_register_movements(...)["code_registers"] (провенанс — в ключе source), в сводке секции registers профиля объекта и в обратном поиске find_register_writers. Известное ограничение: фреймворковые схемы, где имя регистра собирается в структуре во время исполнения, статически неразрешимы и сюда не попадают. Второе известное ограничение: если регистр добавлен или удалён, а модуль менеджера при этом не менялся, инкрементальное обновление сверку не переиграет — лечится полным rlm-bsl-index index build.
  • Предупреждение о незавершённой пересборке индекса. Раньше при обрыве сборки выставлялось только машиночитаемое index.index_status="incomplete", а текстовых предупреждений не было — человек об этом не узнавал. Теперь rlm_start говорит об этом прямо и называет команду, которая доводит сборку до конца.

Изменено

  • parse_form(...)["attributes"][i]["types"] — теперь list[str], а не строка "Тип1, Тип2". Ломающее изменение контракта хелпера, сделанное осознанно: списочная форма — то, чего от ключа types ожидают, а прежняя строка требовала ручного split(", ") и в документации сопровождалась отдельной оговоркой. Пустой список вместо пустой строки. Контракт одинаков на живом разборе и на чтении из индекса; формат хранения в базе не изменился.
  • Номер строки у ссылок метаданных сменил смысл — это строка САМОЙ ссылки, а не строка объявления её владельца. Единственная семантика, полезная агенту («открой файл там, где ссылка»). Заполняется у видов attribute_type, owner, based_on, default_object_form, default_list_form. У остальных видов (role_rights, subsystem_content, exchange_plan_content, event_subscription_source, defined_type_content, functional_option_content, characteristic_type, predefined_characteristic_type, command_parameter_type) значение line остаётся None — как и раньше, регресса тут нет. Это заявленный контракт, а не недоделка: у ссылки на объект в файле прав строка <object>Document.X</object> не несёт агенту ничего сверх уже известного, а её вычисление стоило бы часов сборки.
  • find_functional_options с limit отдаёт xml_total и code_total рядом с total. total — это сумма ДВУХ корзин разной точности: xml_options набирается точным отбором по составу опции, code_options — подстрочным поиском по коду. Прочитанная как одно число, сумма кратно завышает ответ на вопрос «сколько функциональных опций у объекта»: на боевом документе это 167 против 34 точных. Теперь обе величины видны прямо в ответе. Ветка без limit не изменилась — её набор ключей прежний.
  • Первое обращение к хелперу, который перепроверяет индекс по диску, ускорено вдвое. Живой каталог модулей строился обходом с разрешением пути (realpath) на КАЖДЫЙ BSL-файл — 23 508 системных вызовов на конфигурации ЕРП-масштаба, из-за чего первый вызов find_register_movements (и любого другого хелпера, читающего модули живьём) занимал около 15 секунд при 0.5 секунды на последующих. Стало около 7 секунд: разрешение пути осталось только там, где каталог или файл является перенаправлением (символьная ссылка, junction), а разбор пути модуля больше не строит объекты Path ради вычисления относительного пути. Защита «файл не уводит за пределы базы» сохранена в полном объёме, содержимое каталога модулей не изменилось — проверено сверкой на боевых конфигурациях и на перенаправлениях всех видов, включая ведущие в скрытые и пропускаемые каталоги.
  • sections=["flow"] и sections=["code_usages"] у get_object_profile теперь работают. Раньше эти секции включались только булевыми include_flow / include_code_usages, а обращение по имени секции — той самой, что видна в ответе, — молча давало пустой профиль.
  • Описания шести самых длинных хелперов сокращены: длинные пояснения перенесены из подписи, которая уходит агенту при каждом старте сессии, в рецепт, который отдаётся по запросу через rlm_help. Имена параметров и все ключи результата в подписи сохранены полностью. Освобождено 1468 символов стартового бюджета — за счёт этого новые контракты этого релиза уместились без роста стоимости старта.
  • Сборка индекса стала быстрее, чем в предыдущей версии — при том, что этот релиз смотрит на исходник через дополнительную маску и делает строго больше работы. Две построчные функции разбора (общесистемный сканер модулей и маскировщик комментариев и литералов) шли посимвольным циклом с накоплением результата по одному символу: на конфигурации из 26 225 модулей это 109 и 105 секунд соответственно. Цена умножалась на то, что сборка распараллелена потоками, а чистый Python под GIL по ядрам не расходится, — секунды ложились прямо на критический путь. Обе переписаны на быстрый путь (строка вне литерала без кавычек и без // отдаётся как есть, без единой промежуточной сборки) и прыжки по строке вместо посимвольного обхода: 109 → 16.6 с и 105 → 10.8 с. Фаза разбора модулей на той же конфигурации — 527 → 397 секунд, то есть на 25% быстрее версии 1.32.1. Машина состояний при этом не менялась: результат обеих функций побайтово совпадает с прежней реализацией на 78 255 модулях четырёх боевых конфигураций, в обоих режимах маски; содержимое собранных индексов — тоже (сверка полного отпечатка всех таблиц, FTS и схемы до и после правки: идентично).
  • Публикация релиза гейтится зелёным CI. Было так: при пуше тега проверки и релиз стартовали параллельно, поэтому и публичный GitHub Release, и публикация на PyPI могли уехать при красных тестах. Теперь цепочка последовательная: проверки → GitHub Release → PyPI → дымовая проверка. Добавлена сверка версии тега с версией пакета (ловит забытый бамп до создания публичного релиза) и post-publish smoke: только что опубликованная версия ставится из PyPI в чистое окружение, проверяются импорт сервера, номер установленной версии и обе точки входа CLI.

Полный список изменений: CHANGELOG.md