Перейти к содержанию

ADR 0105: Hybrid Codebase Index (ядро + MCP) for C# stacks with Roslyn Truth

Статус: Accepted · Implemented
Дата: 2026-05-06

Связанные ADR

ADR Роль
0039 Навигация по workspace — несколько представлений и «текущий файл + связанные»
0040 LSP (C# / Markdown) — командная строка в settings.toml: пресеты, опциональные ключи, переопределение через окружение
0052 CLI для контракта агента (паритет с MCP) и снапшот-тесты
0053 Карта намерений и поток управления на PFD (control flow)
0056 Semantic Map adoption of Skia composition pipeline
0067 Graph-backed surfaces — общий контракт для семейства графовых экранов
0069 Markdown Preview — инструмент MFD, renderer-first decoupling и отказ от inline preview в документе
0079 IDS vs CDS; AXAML индекс — не IDS
0095 Три уровня Health — Workspace, Solution, IDE (таксономия каналов)
0097 Вычислительные блоки кабины (CCU; аналог LRU Unit) — слой между транспортом, смыслом и каналом
0098 Семантика первична; документ и репозиторий — проекции (Semantic-First)
0099 IDE DataBus — типизированные события и проекции состояния
0100 Конституция проекта
0101 Лицензирование и стратегия коммерциализации
0102 Data Acquisition Layer — граница внешних интерфейсов и адаптеров
0106 Hybrid Codebase Index — интеграция в CascadeIDE, свежесть и Semantic Map
## Резюме
  • Hybrid codebase index: переносимое ядро + MCP; Roslyn — истина для C#.
  • SQLite FTS5 (keyword) + опциональный vec (semantic); fusion α/β.
  • Scope: C#, Razor, AXAML, web-стеки в одном workspace; ADR 0106 — интеграция в CIDE.

Термины и сокращения

Рабочие определения в рамках этого ADR; детали алгоритмов — по документации SQLite / выбранного провайдера эмбеддингов.

Термин Смысл здесь
FTS (full-text search) Полнотекстовый поиск: индекс и запросы по токенам/словам внутри текстов документов (файл или чанк), а не только «точное совпадение поля» или поиск по имени файла.
FTS5 Пятый модуль полнотекстового поиска SQLite: виртуальные таблицы FTS5, инвертированный индекс «термин → вхождения в документах», запросы с учётом релевантности. В этом ADR — основной keyword-backend слоя B.
Инвертированный индекс Структура «слово/термин → список документов (и позиций)», на которой строится быстрый FTS; не путать с графом символов Roslyn.
BM25 (Best Matching 25, семейство Okapi BM25) Класс статистических функций ранжирования полнотекстовых попаданий: баланс «термин часто в этом документе» vs «термин редок в корпусе». В SQLite FTS5 релевантность задаётся через вспомогательные функции ранга (в т.ч. bm25()); в тексте ADR «keyword / BM25» означает полнотекст с таким ранжированием, а не отдельный движок вне SQLite.
Keyword-поиск Поиск по совпадению слов/фраз (через FTS), без обязательного «понимания смысла» запроса разными формулировками.
Эмбеддинг (embedding) Вектор фиксированной размерности, полученный моделью из текста (фрагмент кода, абзац, запрос). Похожие по смыслу тексты в идеале получают близкие векторы в выбранной метрике.
Semantic / векторный поиск Отбор фрагментов по близости эмбеддингов запроса и чанков (косинусная близость и т.п.), а не по совпадению ключевых слов. В ADR обозначается также как vec (vector channel).
Vector store Хранилище векторов и метаданных (идентификатор чанка, путь, диапазон строк), с операциями ближайших соседей (ANN / полный перебор на малых объёмах).
sqlite-vec Расширение SQLite для хранения и запроса векторов; в этом ADR — опциональный локальный vector store рядом с FTS, не заменяя keyword-слой.
Фьюжн (fusion) Объединение списков попаданий из двух каналов (здесь FTS и vec): нормализация скоров, взвешенная сумма или эквивалент, итоговый top‑N. См. § эскиз фьюжна.
Чанк (chunk) Непрерывный фрагмент файла, индексируемый как одна единица FTS/vec (строковое окно, логический блок и т.д.); см. § чанкинг.
MCP Model Context Protocol — транспорт и контракт tools для агентов/IDE; отдельный MCP-сервис индекса описан в § развёртывание.
DAL Data Acquisition Layer — слой получения данных из workspace и внешнего мира по 0102.
CCU Cockpit Compute Unit(s) — упаковка вычислительных результатов в стабильные DTO для каналов по 0097.


Контекст

CascadeIDE — MCP-first IDE: агенту нужно быстро ориентироваться в кодовой базе и собирать контекст в малом окне модели (или при ограниченном бюджете шагов/вызовов).

Для любых .NET/C# решений у нас уже есть “источник истины” для точных семантических операций:

  • Roslyn (через roslyn-mcp и IDE wiring) для: diagnostics, go-to-definition, find-usages, rename, symbol-level navigation.

Но Roslyn не решает полностью задачу:

  • быстрый “обзор по смыслу” и “первую карту” решения без чтения десятков файлов;
  • полнотекст и ориентация по Markdown, конфигам, .csproj / .sln / .slnx, YAML/TOML, веб-слою (Razor/Blazor .razor, HTML/CSS), разметке Avalonia (.axaml) и другим артефактам без семантической модели Roslyn для этих форматов;
  • для обычного C#‑проекта (включая сам CascadeIDE) тот же гибридный слой даёт быстрый keyword/опц. semantic по репозиторию целиком — в том числе по тексту .cs (слой B: только FTS, не символы), пока переименование/impact остаются на Roslyn;
  • устойчивость между сессиями: “карта” должна жить рядом с проектом/профилем IDE и не требовать каждый раз переобучения агента.

Есть внешние решения (например SocratiCode) с hybrid search + graph + impact, но они добавляют инфраструктурную нагрузку (Docker/Qdrant/Ollama), а также риск лицензий (AGPL) для интеграции в продукт.

Дополнительно: CascadeIDE кроссплатформенный (Avalonia). Мы не хотим делать критичный слой навигации завязанным на Windows-only/драйверы/Docker, но на Linux можем разрешать более “тяжёлые” backend-опции.


Решение в одном предложении

Ввести двухслойную модель навигации: Roslyn — источник истины для C# семантики, а рядом — лёгкий гибридный индекс по контуру решения: веб‑артефакты (.razor, MD, HTML/CSS), Avalonia .axaml (и при необходимости эвристика пары с code-behind .cs), конфигурация и сопровождение (в т.ч. опционально полнотекст по .cs как тексту, без подмены symbol-level операций); keyword + опциональная семантика; минимальная ops‑цена и кроссплатформенность.


Цели

  1. Снизить число шагов агента: 1–2 вызова → достаточно релевантного контекста для решения.
  2. Дать “первую карту” без “прочитать 20 файлов”: топ-файлы/узлы/потоки, входные точки — для Blazor/Web, для Avalonia (AXAML + привязки/имена контролов) и для обычного C#, в том числе при разработке самого CascadeIDE на том же стеке инструментов.
  3. Сохранить семантическую корректность: refactor-impact по C# не эвристический, а Roslyn-based.
  4. Работать без обязательного Docker (особенно на Windows), с предсказуемой локальной установкой/обновлением.
  5. Быть кроссплатформенным (Windows/Linux/macOS), с optional backend-ускорителями на Linux.

Не-цели (на первом этапе)

  • Полный “polyglot dependency graph” по 18+ языкам.
  • Замена Roslyn MCP: Roslyn остаётся истиным слоем для C#.
  • Обязательная векторная БД/контейнеры для базовых сценариев.
  • “Один граф, который всегда прав”: граф/impact вне C# допускает эвристику и требует верификации.

Архитектура (в терминах слоёв)

Слой A: Roslyn Truth (C#)

Использовать Roslyn для:

  • diagnostics / code actions;
  • find usages / rename;
  • symbol navigation;
  • (по возможности) call graph / entrypoints в пределах C# проекта.

Этот слой — точный, но “дорогой” по workflow: агенту всё равно нужно знать, что искать.

Слой B: Hybrid Index (артефакты вокруг C#, веб-слой, Avalonia AXAML, опционально текст .cs)

Индекс для файлов и фрагментов вне Roslyn-символики или как текст (не как граф типов):

  • .razor, .razor.cs (включая связь partial / file pairing);
  • .md / .mdx;
  • .html, .css, .scss (включая @import, классы/селекторы);
  • базовые конфиги (appsettings*.json, .editorconfig, *.props, *.targets, *.csproj, *.slnx, pipeline YAML, *.yml, *.toml и т.п.);
  • .axaml (и типичный code-behind *.axaml.cs, если есть): разметка и атрибуты — как текст для FTS и лёгких эвристик (x:Name, {Binding …}, Classes=, пути avares:); не подмена XAML-Avalonia-парсера, не семантика CDS/IDS (см. 0079 — CDS vs IDS);
  • *.cs (опция индекса): только полнотекст/keyword (идентификаторы и строки встречаются как совпадения в тексте файла); rename/find-usages/impact по-прежнему только Roslyn. В ответах инструмента явно помечать hits по .cs как text-ranked, чтобы не смешать с символьной истиной.

Индекс предоставляет:

  • keyword / BM25: строки конфигов, CSS, маршруты Razor, фрагменты .cs/.axaml/доков;
  • опционально semantic: поиск “по смыслу” (через embeddings), но без обязательного Docker.

Данные индекса:

  • хранятся локально (профиль IDE или рядом с проектом);
  • обновляются инкрементально (watcher + hash);
  • имеют явную версионность формата (чтобы migration не ломала UX).

Storage / backend (baseline)

Рекомендуемая конфигурация по умолчанию (без Docker, кроссплатформенно):

  • Keyword/BM25: SQLite FTS5 (локальная БД на диске) как быстрый полнотекстовый индекс.
  • Semantic vectors (optional): SQLite + sqlite-vec как локальный vector store (включается только при включённой семантике).

Движок здесь — классический SQLite (например Microsoft.Data.Sqlite или другой провайдер к той же библиотеке SQLite), не WitDatabase (*.witdb): Wit остаётся для данных приложения CascadeIDE; файл индекса — отдельный SQLite на диске.

Важно: hybrid = FTS (keyword) + vec (semantic) как два независимых подиндекса, объединяемых на уровне сервиса (ранжирование/фьюжн), а не “магия одной БД”.

Слой C: Composition (агентский workflow, переносимо)

Дефолтный сценарий агента (вне конкретной IDE):

  1. Hybrid search (быстро, дешево) → топ-N фрагментов и карта.
  2. Roslyn navigation для точной проверки/рефакторинга в C#.
  3. Точечное чтение файлов/фрагментов только после поиска.

Встраивание этого сценария в CascadeIDE (кнопки, каналы, debounce reindex, CCU/DataBus, Semantic Map) — ADR 0106.

Развёртывание: библиотека + отдельный MCP

Индекс оформлять как общую библиотеку (ядро: индексация, SQLite, форматы запроса/ответа) и отдельный MCP-сервер (тонкий слой stdio + регистрация tools), чтобы:

  • использовать поиск вне контура CascadeIDE (другие IDE/агенты с MCP, CLI, автоматизация);
  • изолировать тяжёлый процесс (watcher, файлы SQLite, опционально embeddings): перезапуск и обновления не смешиваются с Avalonia/UI.

CascadeIDE может подключать то же ядро in-proc или поднимать тот же бинарник MCP как дочерний процесс — идентификаторы и контракт tools сохраняются общими для обоих сценариев (детали размещения по слоям кабины — 0106).


Конфигурация и UX-инварианты

  • Off-by-default по инфраструктуре: если semantic embeddings требуют внешнего провайдера, это должно быть opt-in.
  • Кроссплатформенность: одинаковые tool ids/контракты в MCP, разница только в backend-провайдере.
  • Работа в малом окне: ответы инструментов должны быть “компактными по умолчанию” (top-N, с указанием пути/диапазона/score), с отдельной командой для расширения.

На что смотреть при внедрении

Операционные моменты, без которых dogfood и продакшен быстро разочаруют:

  1. Объём и шум. FTS по всем *.cs раздувает индекс и может засорять топ-N сырьевыми строковыми попаданиями. Нужны явные умолчания и фильтры в settings.toml (или эквивалент): игноры/gitignore-согласование, маски путей, ранжирование (например приоритет документов/конфигов перед «сырым» .cs, или наоборот — режим «сначала код»), возможность временно исключить *.cs из FTS без отключения остального индекса.

  1. Свежесть (freshness) при сохранениях из CascadeIDE. Дёшевый инкремент и UX без лагов — ADR 0106. В MCP/ядре возможен watcher и инкрементальный reindex; продуктовая связка с сессией редактора — в IDE.

  1. Контракт MCP с первого прототипа. В структуре ответа поиска — стабильное поле типа попадания (например hit_kind: text_fts / text_vector / symbol_followup_roslyn или эквивалент), чтобы агент и человек не гадали по свободному тексту. Менять семантику поля позже дороже, чем заложить его в v0.

Альтернативы и почему нет (сейчас)

A) “Только Roslyn + grep”

Плюсы: минимальная инфраструктура, высокая точность для C#.
Минусы: слишком много шагов и чтения файлов для агентных сценариев; плохо покрывает docs/config/web и глобальный “где упомянуто” по репозиторию, если не считать тяжёлый только-Roslyn обход.

B) Встроить SocratiCode целиком

Плюсы: готовый hybrid+graph+impact слой, быстрое “orientation” по большому репо.
Минусы: - ops: Docker/Qdrant/Ollama в baseline; - корректность графа вне C# зависит от эвристик; - лицензия AGPL — нежелательна для встраивания в продукт (см. 0101).

C) LSP для всего (полный polyglot)

Плюсы: потенциальная семантическая точность по языкам.
Минусы: слишком большая операционная и интеграционная цена; не решает проблему “малое окно/мало вызовов” без отдельного индекса/ранжирования.


Последствия

Положительные

  • Агент получает быстрый “первый проход” по решению и может dogfood’ить тот же индекс при разработке самого CascadeIDE и других C#‑репо без ограничения сценарием «только Blazor».
  • Roslyn остаётся “истиной” для опасных операций (rename/impact/diagnostics).
  • Docker становится optional: Windows-friendly baseline, Linux может получать расширенные режимы.

Негативные / риски

  • Появляется новый слой данных (индекс) → нужны версии, миграции, наблюдаемость.
  • Есть риск ложных связей в .razor/CSS/HTML эвристиках → нужен “confidence” и явная маркировка, что это подсказка.
  • Индекс по .cs/.axaml как текст может случайно выглядеть как «семантический find» → см. § на что смотреть при внедрении (hit_kind, ранжирование).
  • Нужно удерживать инструменты компактными, иначе hybrid-индекс может “спамить” контекстом и ухудшить UX.

План внедрения (переносимое ядро + MCP): статус

Шаг Содержание Контур ADR 0105 (реализовано)
1 MCP-контракты (search, status, reindex, explain-result, версия/hit_kind); ядро в библиотеке ✅ репозиторий hybrid-codebase-index
2 Keyword index, инкремент, игноры; FTS по *.cs опционально; watcher tool
3 Razor / AXAML: пары .razor.razor.cs, .axaml.axaml.cs; заголовки эвристик __hci_* (директивы, ресурсы, привязки, теги) ✅ (HybridCodebaseIndex.Core augment)
4 Embeddings opt-in (settings.toml), sqlite-vec optional
5 IDE workflow + свежесть при сохранениях ADR 0106
6 Умолчания области, чанкинг FTS, фьюжн FTS+vec settings.toml + hybrid search

Эскиз: область индекса, чанки, фьюжн (FTS + vec)

Дополнение к плану внедрения: разумные умолчания на спайке, без смены верхнеуровневой архитектуры (слой B, storage).

Область индекса

  • Первичный якорь: активный .sln / главный .csproj профиля CascadeIDE — тот же workspace-контур, что и Roslyn-сессия.
  • Расширение по умолчанию: пути под корнем workspace, за вычетом согласованного .gitignore (при необходимости — .cursorignore против агентского шума) и жёсткого denylist: bin/, obj/, node_modules/, .git/, типовые каталоги кэшей тулов.
  • Монорепо: одна БД индекса на пару (workspace_root, solution_path); другое решение того же дерева — отдельный контур индекса (переключение профилем). Поле extra_include_roots в settings.toml для соседних каталогов (доки, внешний KB и т.п.) — opt-in.

Чанкинг для FTS

Тип Стратегия
Компактные конфиги, небольшие .md, .razor в пределах лимита Один FTS-документ на файл; верхний лимит размера документа (например 256–512 KiB) — конфигурируемо.
Длинные .md, .cs, .axaml Скользящие окна по строкам (ориентир: 80–120 строк, overlap 10–15); стабильный chunk_id: путь + диапазон (start_line / offset).
.razor По возможности логические границы (@code, крупные разметочные блоки); если не вышло дёшево — те же строковые окна.

Свежесть: при правке пересобирать только затронутые чанки; для маленьких файлов допускается пересборка целого документа. В ответе инструмента всегда указывать путь и диапазон строк (или offset), чтобы агент и человек открывали точку без угадываний.

Фьюжн keyword (FTS) и semantic (vec), v0

  1. Независимо получить топ‑K из FTS и из vec (внутренний K, ориентир 20–40; наружу после слияния — компактный top‑N).
  2. Нормализовать скоры внутри каждого канала (min-max или rank-based, например 1/(rank+R)).
  3. Объединить множество уникальных чанков: итоговый score S = α·S_fts + β·S_vec; если чанка нет в канале — вклад этого канала 0.
  4. Дефолт при включённом vec: α ≈ 0.65, β ≈ 0.35; при выключенном vec — только FTS.
  5. Короткий запрос (1–2 токена) или низкий max S_vec: усилить вклад FTS или не смешивать vec (keyword-доминирующий режим).

В DTO по возможности сохранять оба вклада (fts_score, vec_score при наличии) вместе с hit_kind и итоговым рангом — чтобы объяснимость («почему в топе») не терялась. Пороги и веса выносить в settings.toml без ломки формата ответа на следующих итерациях.