Миграция индекса 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