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

ADR 0024: SDK для CascadeIDE — стабильные контракты для внутреннего расширения и будущих плагинов

Статус: Proposed
Дата: 2026-04-08

Связанные ADR

ADR Роль
0005 плагины deferred
0006 границы слоёв/срезов
0008 контракты и тестируемая инфраструктура
0013 поверхность команд
0019 общий core как прецедент
0023 внешние инструменты/LSP как контракты
0026 геометрия превью Markdown в TOML
0025 зоны внимания PFD/MFD/… в SDK и capabilities

Резюме

  • CascadeIDE SDK — стабильные контракты для внутреннего расширения и будущих плагинов.
  • Версионирование публичных API; граница с «всё internal».
  • Связь с зонами внимания — 0025.

Контекст

CascadeIDE активно развивается “изнутри”: добавляются новые панели, инструменты, каналы диагностики, режимы UI и интеграции с внешними процессами (LSP/DAP/MCP). При этом:

  • Удобство разработки упирается в ментальную модель: где границы, какие зависимости допустимы, что считается “ядром”, что — “фича”.
  • MainWindowViewModel и UI-shell неизбежно становятся местом, куда “просится всё”, если нет явных контрактов.
  • Плагины (динамическая загрузка DLL/MEF) пока отложены (0005), но когда мы к ним вернёмся, нужен будет “куда вставлять”, иначе plugin-host появится раньше, чем стабилизируется форма слотов/контрактов.

Нужен “SDK” — но в первую очередь для нас же, чтобы:

  • уменьшить связность между фичами,
  • обеспечить расширяемость без хаоса,
  • подготовить базу под будущие плагины без преждевременного plugin-host.

Решение

Принять подход: SDK = стабильные контракты расширения IDE, а не обязательная система плагинов сегодня.

SDK в v1 включает:

  1. Явные публичные интерфейсы/контракты между shell и фичами (и между фичами), оформленные как отдельный слой (папка/проект), с минимальными зависимостями.
  2. Формат поставки: отдельный проект CascadeIDE.Contracts (или близкое имя), а не папка внутри cascade-ide.

  3. Capability-модель (объявление возможностей), чтобы фича могла “подключаться” к shell и друг к другу без прямых ссылок на конкретные реализации.

  4. Capability registry — hybrid (следует из текущей логики IDE):

  5. Code-first регистрация capabilities фичами при старте (без MEF/рефлексии “на вырост”).
  6. Data overlay для презентации: UI-режимы/раскладки (TOML) могут включать/выключать или менять presentation capabilities, но не подменяют их семантику.
  7. Introspection: shell умеет собрать “карту возможностей” для диагностики/телеметрии/агента.

  8. Command surface как контракт: команды (и их discoverability) оформляются не через “познавание внутренностей” фичи, а через общий контракт регистрации/метаданных (в духе 0013).

  9. Out-of-proc как норма для интеграций (LSP/DAP/MCP/CLI): протоколы и DTO — часть “SDK” в смысле стабильных контрактов (см. 0008). In-proc расширения не запрещены, но должны входить в рамки контрактов.

  10. Маркировка контрактов Experimental/Stable — внутренняя ясность, не публичное обещание.
    На стадии active-dev (далеко до alpha) цель — не “совместимость навсегда”, а дисциплина границ: по умолчанию всё Experimental, а Stable появляется только там, где команда осознанно хочет опираться на контракт. Ломать Experimental можно свободно; ломать Stable — осознанно (через ADR/заметку о миграции), SemVer — когда появится продуктовая потребность.

  11. Отложенный plugin-host: динамическая загрузка плагинов остаётся deferred (см. 0005), но будущий plugin-host обязан подключаться через те же контракты SDK, что и “внутренние” фичи.

Модель внимания кокпита (PFD / MFD / Forward / EICAS / HUD) и SDK

Семантика зон описана в 0021. Явная привязка UI‑capabilities к зонам внимания на уровне CascadeIDE.Contracts — отдельное решение и поэтапная реализация: 0025. Так 0024 остаётся про общий SDK, а кокпитная ось не смешивается с registry/plugin‑обсуждением.


Capability registry / capability-map (API и принципы)

Цель: capability-слой должен быть достаточно строгим, чтобы удерживать границы и ментальную модель, и достаточно простым, чтобы им реально пользовались.

Что считаем capability

  • Service capability: “я предоставляю сервис” (контракт/интерфейс + реализация).
  • Command capability: “я предоставляю команду” (discoverability, категория, горячие клавиши, доступность).
  • UI surface capability: “я предоставляю UI‑слот/поверхность” (панель/страница/вкладка), при этом включаемость/раскладка остаются overlay’ем (TOML).

Code-first регистрация модулей (без MEF/рефлексии)

  • Реестр модулей формируется явно кодом (ручной список), без сканирования сборок.
  • Каждая фича имеет одну точку входа регистрации, например ICascadeFeatureModuleRegister(ICapabilityRegistry registry).

Ключи и зависимости

  • Capability идентифицируются строковыми id (константы в SDK/контрактах), например: git.core, docs.markdown.exportExpanded.
  • В capability‑descriptor поддерживаются явные зависимости (Requires) для объяснимости (“почему capability не активна/не видна”).

Capability-map (introspection)

Регистрирующий слой обязан собирать “карту возможностей” (CapabilityMap) как неизменяемое описание:

  • список service/command/ui capabilities с метаданными (id, owner module, stability, tags, requires);
  • состояние “доступно/включено” может вычисляться shell’ом с учётом overlay (например UiMode TOML) и рантайм условий.

Capability-map используется для:

  • диагностики и объяснимости (“почему кнопка/панель отсутствует”),
  • телеметрии и снимков UI/агента (introspection),
  • будущего plugin-host (внешний модуль должен регистрироваться тем же способом).

TOML overlay (presentation)

UI-режимы/раскладки (TOML) должны ссылаться на capabilities по id, управляя presentation (видимость/размещение), но не подменяя семантику capabilities.


Минимальный API (черновик контрактов)

Ниже — минимальная форма контрактов для CascadeIDE.Contracts (без привязки к DI-контейнеру/фреймворку):

  • ICascadeFeatureModule
  • string Id { get; }
  • void Register(ICapabilityRegistry registry)

  • ICapabilityRegistry

  • void RegisterService(ServiceCapabilityDescriptor descriptor)
  • void RegisterCommand(CommandCapabilityDescriptor descriptor)
  • void RegisterUiSurface(UiSurfaceCapabilityDescriptor descriptor) (опционально для MVP)
  • CapabilityMap BuildMap() (для introspection/диагностики)

  • CapabilityMap

  • IReadOnlyList<ServiceCapabilityDescriptor> Services
  • IReadOnlyList<CommandCapabilityDescriptor> Commands
  • IReadOnlyList<UiSurfaceCapabilityDescriptor> UiSurfaces

  • Общие поля дескрипторов:

  • string Id (строгий идентификатор)
  • string OwnerModuleId
  • ApiStability Stability + [ApiStability]
  • string[] Tags
  • string[] Requires

Пояснение: CapabilityMap описывает “что зарегистрировано”, а “включено ли” и “как размещено” вычисляется shell’ом с учётом TOML overlay и рантайм условий.


Что НЕ является целью (v1)

  • Не вводить сейчас обязательный механизм “плагины из папки DLL”.
  • Не обещать бинарную совместимость для сторонних расширений “навсегда”.
  • Не превращать SDK в “бог-API” без границ (наоборот, цель — уменьшить поверхность).

Практические принципы проектирования SDK

  • Минимальная поверхность: контракты должны описывать “что нужно”, а не “как сделано”.
  • Зависимости направлены наружу: фичи зависят от контрактов, а не от конкретных фич.
  • DTO отдельно от UI: переносимые модели данных не зависят от Avalonia-контролов.
  • Тестируемость: контракты легко мокать; выполнение выносится в сервисы.
  • Безопасность по умолчанию: всё, что запускает код/процессы/сетевые запросы — через явные шлюзы и настройки.

Как отражаем Stable/Experimental в коде и документации

Минимальный договор (без бюрократии, полезный уже в active-dev):

  • Namespace-сигнал: в CascadeIDE.Contracts два “корня”:
  • CascadeIDE.Contracts.Experimental.* — по умолчанию всё новое.
  • CascadeIDE.Contracts.Stable.* — то, на что команда осознанно опирается.
  • Атрибут-маркер: единый ApiStabilityAttribute + enum (Experimental, Stable) для быстрых поисков/анализа и расширения в будущем (например Deprecated).
  • Зачем оба: namespace даёт “видимость по умолчанию” (и упрощает правила зависимостей), атрибут даёт точечную метку и основу для будущих анализаторов/отчётов.
  • Документация: в ADR и/или в README проекта контрактов фиксируем правило “по умолчанию Experimental”, критерии перевода в Stable и ожидание миграции при breaking-change.

Последствия

  • Новые фичи добавляются через понятные слоты/контракты, меньше “скрытых” связей.
  • Ускоряется разработка: проще понять куда добавить логику, проще тестировать, меньше регрессий от случайных зависимостей.
  • Появляется ровная база для будущих плагинов: когда plugin-host перестанет быть deferred, “SDK-форма” уже будет существовать.

Отклонённые альтернативы

  • “SDK = сразу plugin-host (MEF/загрузка DLL)” — отклонено как преждевременное усложнение (0005).
  • “Никакого SDK, всё через прямые ссылки” — отклонено: ухудшает ментальную модель, ускоряет рост связности и размеров shell-композитора.

Открытые вопросы

Визуализация и документация capability-map (принятое направление)

Минимальный путь (v1), без отдельного UI:

  • Принцип “тонких снапшотов” (применимо и к capability-map, и к UI snapshot’ам/прочим дампам):
  • по умолчанию возвращаем summary + hash (и, при необходимости, короткий список id/счётчики);
  • “толстые” данные получаем по запросу (фильтры/пагинация) или через dump‑файл.
  • JSON dump‑файл capability-map доступен в diagnostics/логах (для объяснимости и отладки интеграций), с возвратом path + hash.
  • Capability-map может быть включён в MCP/UI snapshot только в виде summary; детали — отдельной командой или через dump‑файл (чтобы не раздувать контекст).

Следующий шаг (когда появится потребность):

  • Страница/таб Capabilities в Debug/Power (или отдельный документ в docs/) с группировкой по OwnerModuleId и фильтром по Stable/Experimental.