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–2 вызова → достаточно релевантного контекста для решения.
- Дать “первую карту” без “прочитать 20 файлов”: топ-файлы/узлы/потоки, входные точки — для Blazor/Web, для Avalonia (AXAML + привязки/имена контролов) и для обычного C#, в том числе при разработке самого CascadeIDE на том же стеке инструментов.
- Сохранить семантическую корректность: refactor-impact по C# не эвристический, а Roslyn-based.
- Работать без обязательного Docker (особенно на Windows), с предсказуемой локальной установкой/обновлением.
- Быть кроссплатформенным (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):
- Hybrid search (быстро, дешево) → топ-N фрагментов и карта.
- Roslyn navigation для точной проверки/рефакторинга в C#.
- Точечное чтение файлов/фрагментов только после поиска.
Встраивание этого сценария в 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 и продакшен быстро разочаруют:
- Объём и шум. FTS по всем
*.csраздувает индекс и может засорять топ-N сырьевыми строковыми попаданиями. Нужны явные умолчания и фильтры вsettings.toml(или эквивалент): игноры/gitignore-согласование, маски путей, ранжирование (например приоритет документов/конфигов перед «сырым».cs, или наоборот — режим «сначала код»), возможность временно исключить*.csиз FTS без отключения остального индекса.
- Свежесть (freshness) при сохранениях из CascadeIDE. Дёшевый инкремент и UX без лагов — ADR 0106. В MCP/ядре возможен watcher и инкрементальный reindex; продуктовая связка с сессией редактора — в IDE.
- Контракт 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¶
- Независимо получить топ‑K из FTS и из vec (внутренний K, ориентир 20–40; наружу после слияния — компактный top‑N).
- Нормализовать скоры внутри каждого канала (min-max или rank-based, например
1/(rank+R)). - Объединить множество уникальных чанков: итоговый score
S = α·S_fts + β·S_vec; если чанка нет в канале — вклад этого канала 0. - Дефолт при включённом vec:
α ≈ 0.65,β ≈ 0.35; при выключенном vec — только FTS. - Короткий запрос (1–2 токена) или низкий max
S_vec: усилить вклад FTS или не смешивать vec (keyword-доминирующий режим).
В DTO по возможности сохранять оба вклада (fts_score, vec_score при наличии) вместе с hit_kind и итоговым рангом — чтобы объяснимость («почему в топе») не терялась. Пороги и веса выносить в settings.toml без ломки формата ответа на следующих итерациях.