История идеи проекта
Сидел я и делал чанкирование разбора конфигурации 1С в PostgreSQL. Выгрузка конфы мне досталась с 43 расширениями и ноль документации по этим расширениям после 5 команд разработки.
Ну, нужно было её разобрать, обогатить бизнес-терминами и превратить во что-то удобоваримое вроде как для RAG. Было всё это долго, нудно, на Питоне, с помощью локальной LLM. Сам я в настоящий момент 1с-ник, но начинал я в своё время с MSSQL-2000 (если кто-то помнит такой), T-SQL, PowerScript, Pascal и пр. романтики.
Сидел я, думал о светлом будущем, когда с помощью RAG локальная моделька будет мне писать скрипты, и даже без ошибок (до некоторой степени это получилось). Ну как-то слово за слово, и мне подумалось: есть же семантический поиск по RAG, реализуется MCP-сервером. По сути это запросы — точные, но довольно медленные. А почему тогда во всех остальных случаях, работая с выгрузкой напрямую, модель раз за разом лезет в переборщики файлов — пусть правильно написанные и крайне оптимизированные, но всё же переборщики? КОТОРЫЕ ТРАТЯТ МАССУ ТОКЕНОВ ТУПО НА ЧТЕНИЕ ДЛЯ ПОИСКА?! Почему нельзя это делать запросами в базу данных — нормальными, дешёвыми, точными?
И вспомнил я заодно старую идею: когда-то файловую систему в Windows (проект WinFS, времён Vista) хотели сделать похожей на БД, чтобы радикально ускорить работу с файлами. Идею потом свернули, и она как-то забылась. НО я не забыл :):)
Идея-то была интересная. Итого — нужно было дать модели инструмент, который заметно экономил бы токены и ускорял поиск данных (особенно в больших массивах кода). И подумалось мне, что не один я такой — есть целый коллектив разработчиков, которому такой инструмент может пригодиться. Поэтому Бог с ним, с Питоном, будем писать на Rust, потому что Rust — это база! (с)
Сейчас была бы длинная история — как я описывал, преодолевал, раздумывал, отлаживал и т.д. Но вот кому это интересно?
Основные моменты:
-
Обновление базы, и кэша. Зачем базе вообще обновляться? Изначально проект планировался как простая библиотека кода — что-то вроде локальной базы, к которой модель ходит за ответами. Но какой в нём смысл, если та же модель тут же и правит файлы?! Без живого обновления база становится бесполезна сразу после первой правки кода моделью. Поэтому пришлось ловить изменения в реальном времени — отсюда демон с file watcher, отсюда и всё остальное в списке ниже.
-
Один писатель, много читателей. Демон пишет в SQLite, MCP-серверы только читают. PID-локов нет: к одному .code-index/index.db цепляется сколько угодно процессов параллельно(несколько моделей, говоря обычным языком) — supervisor поднимает их по одному на репо.
-
На старте демону надо понять, какие файлы изменились с прошлого запуска, чтобы не пересчитывать индекс целиком. Раньше для этого SHA-256 считался по всем файлам — на 93 000 это ~163 с. Теперь сначала проверяются mtime и file_size, и SHA считается только для тех, у кого метаданные сдвинулись. Получается ~4 с.
-
WAL раздулся до 43 ГБ за сутки. Демон работает долго и постоянно пишет — за сутки на стенде с 13 индексируемыми папками index.db-wal-файлы съели 45 ГБ свободного места. Для одного репо index.db-wal весил 19 ГБ при основной БД в 4.7 ГБ. PRAGMA wal_autocheckpoint переносит страницы из WAL в БД; физический размер файла при этом не меняется. Лечится PRAGMA wal_checkpoint(TRUNCATE) после каждой пачки.
-
Multi-config 1С: base + 43 расширения. Те самые расширения из вступления. Современная выгрузка 1С — это base + дерево из нескольких десятков extension-папок, каждая со своим Configuration.xml. Расширения переопределяют и добавляют объекты базы. Индекс должен пройти по всему дереву целиком. Парсер рекурсивно (глубина ≤ 2) находит все Configuration.xml и сводит объекты в одну таблицу через INSERT OR IGNORE.
-
Содержимое файлов в индексе. Сначала индекс был чисто структурный — функции, классы, граф вызовов, без самого текста файлов. Бывают задачи, где агенту нужен именно текст: прочитать файл целиком или прогнать regex по живому исходнику. Под них появилась таблица file_contents со zstd-сжатием (×5 на BSL) и защитой от zstd-bomb на 256 МБ. Затем таблицы под разбор структуры xml-выгрузки 1с. Затем файл связей объектов 1с. В общем, проект все больше уходил в сторону 1с, поэтому я его разделил на непосредственно code-index и bsl-indexer.
-
Кэш-инвалидация в три эшелона. Перед индексом стоит кэширующий прокси (mcp-cache-ci) — он экономит токены и время на повторных запросах от агентов. Когда файл меняется, ответы, построенные из него, протухают, и нужно точечно их сносить. Прокси при выдаче ответа запоминает, из каких файлов этот ответ собран. Демон после переиндексации шлёт ему POST /invalidate, и кэш сносит зависимые записи. Третий эшелон — TTL: если событие потерялось, запись протухнет сама.
И там еще по мелочи. А, потом уже какая-то сказка началась, с федеративными удаленными репо и пр.
Итого — первоначальная идея соблюдается, модель(даже моделИ!) обращаются и меняют файлы, база это подхватывает моментально, ну в общем — первоначальная идея соблюлась.
Дальше — минусы — пришлось(и иногда приходится:)) модели объяснять, что она работает в проиндексированном проекте, что нечего тут грепать/глобать/ридить и башить(простигосподи:)), что надо использовать правильные инструменты. Несмотря на категорические требования в Claude.md и отдельном файле с правилами, со временем(заполнения контекстного окна) модель иногда(не всегда), но сбивается на Grep/Glob и пр.
Ну и как бы Rust — это блейзинг(с), тут сказать нечего. Механизм работает, экономит время и токены.
Репозиторий: https://github.com/Regsorm/code-index-mcp
ссылка на оригинал статьи https://habr.com/ru/articles/1042686/