| 1 | # ADR 0141: Прогрев (warm-up) при открытии solution — отложенная асинхронная оркестрация |
| 2 | |
| 3 | **Статус:** Accepted · Implemented (v1: оркестратор, P0–P2, TOML, HIS hint) |
| 4 | **Дата:** 2026-05-23 |
| 5 | |
| 6 | ## Связанные ADR |
| 7 | |
| 8 | | ADR | Роль | |
| 9 | |-----|------| |
| 10 | | [0106](0106-hybrid-codebase-index-cascadeide-integration-and-semantic-map.md) | HCI: `auto_reindex_on_solution_open`, watcher, `HybridIndexStateChanged` | |
| 11 | | [0135](0135-intercom-attach-symbol-cache-and-hci-sidecar.md) | L1 parse cache, L2 symbol sidecar после HCI reindex | |
| 12 | | [0128](0128-intercom-attachment-anchors-and-code-references.md) | Attach / bracket re-resolve, chip `resolveOutcome` | |
| 13 | | [0134](0134-intercom-message-prepare-pipeline-v1.md) | Prepare @ send; fast path без Roslyn | |
| 14 | | [0138](0138-cockpit-command-line-and-parametric-ranges.md) | TCI preview debounced, Enter не блокируется | |
| 15 | | [0140](0140-tci-slash-status-glyphs-and-args-counter.md) | Slash preview не блокирует ввод | |
| 16 | |
| 17 | Playbook: `docs/design/playbook-solution-warmup-v1.md`. |
| 18 | |
| 19 | ## Резюме |
| 20 | |
| 21 | При **открытии или смене solution** IDE запускает **единый фоновый конвейер прогрева** (warm-up): тяжёлые, но предсказуемые работы выполняются **асинхронно и без блокировки UI**, чтобы последующие действия оператора (bracket `[M:…]`, reveal attach, slash file completion, re-resolve chips в ленте) были **мгновенными или почти мгновенными**. |
| 22 | |
| 23 | Принцип: **pay once on solution open, benefit on every interaction** — в духе уже существующего HCI reindex + symbol sidecar, но с **явным каталогом задач**, **приоритетами**, **отменой при смене scope** и **сигналом готовности** (DataBus / HIS), а не разрозненными `Task.Run` в отдельных фичах. |
| 24 | |
| 25 | ## Контекст |
| 26 | |
| 27 | ### Что уже есть (фрагментарно) |
| 28 | |
| 29 | | Триггер | Что делает | Ограничение | |
| 30 | |---------|------------|-------------| |
| 31 | | `SolutionPath` → `ApplyHybridCodebaseIndexOrchestrationForCurrentSolution(pokeWhenAutoReindex: true)` | HCI reindex / watcher ([0106](0106-hybrid-codebase-index-cascadeide-integration-and-semantic-map.md)) | Не покрывает bracket-autocomplete, L1 до первого parse | |
| 32 | | После HCI reindex | `IntercomSymbolLineIndexCoordinator.ScheduleRebuild` ([0135](0135-intercom-attach-symbol-cache-and-hci-sidecar.md)) | Стартует **после** HCI; нет связи с «прогреть активный файл» | |
| 33 | | `ReloadIntercomSessionFromDisk` + `RefreshAttachmentAnchorsForCurrentScope` | Re-resolve chips в ленте при смене workspace/solution | Синхронно на UI после reload; не обновляет event log | |
| 34 | | `BracketMemberCompletionProvider` | In-memory индекс членов **по запросу** (lazy) | Первый `[` / `M:` на большом `.cs` ранее **фризил UI** — mitigated debounce + без Roslyn на голый `[` (код 2026-05-23), но **нет** proactive warm активного файла | |
| 35 | | `IntercomAttachmentRoslynWorkspaceCache` (L1) | Parse/model по файлу при resolve | Холодный до первого reveal/send | |
| 36 | |
| 37 | Оператор ожидает: открыл solution → подождал N секунд в фоне → дальше `[`, attach, slash **без лагов**. Сейчас часть работы переносится на **первый ввод**, что воспринимается как зависание. |
| 38 | |
| 39 | ### Проблема |
| 40 | |
| 41 | - Нет **единой точки** «что прогреваем при solution open». |
| 42 | - Нет **отмены** при быстрой смене solution (гонки sidecar vs старый scope). |
| 43 | - Нет **приоритета** «сначала активный `.cs` в редакторе, потом весь репозиторий». |
| 44 | - UI не знает **стадию** прогрева (только косвенно через HCI lamp). |
| 45 | |
| 46 | ## Решение |
| 47 | |
| 48 | ### 1. `SolutionWarmupOrchestrator` (Application) |
| 49 | |
| 50 | Один оркестратор на scope `(workspaceRoot, solutionPath)`: |
| 51 | |
| 52 | | Аспект | Значение | |
| 53 | |--------|----------| |
| 54 | | Жизненный цикл | Создаётся/сбрасывается при смене `SolutionPath`; `CancellationToken` отменяет предыдущий прогон | |
| 55 | | Поток | **Никогда** не блокирует UI; тяжёлое — `Task.Run` / очередь с лимитом параллелизма (напр. 2–4 файла) | |
| 56 | | Публикация | `SolutionWarmupStateChanged` в IDE DataBus ([0099](0099-ide-databus-typed-events-and-projections.md)): `Idle` → `Running` → `Ready` / `Partial` / `Cancelled` | |
| 57 | |
| 58 | Точка входа (v1): из `MainWindowViewModel.HandleSolutionPathChanged` **после** постановки HCI в очередь, **без** ожидания завершения HCI. |
| 59 | |
| 60 | ### 2. Каталог задач прогрева (work items) |
| 61 | |
| 62 | Каждая задача — `ISolutionWarmupWorkItem`: `Id`, `Priority`, `RunAsync(scope, ct)`. |
| 63 | |
| 64 | | Id | Приоритет | Действие | Потребитель | |
| 65 | |----|-----------|----------|-------------| |
| 66 | | `hci.poke` | P0 (уже есть) | Оставить в `HybridIndexOrchestrationPolicy`; оркестратор **не дублирует**, только **подписывается** на `HybridIndexStateChanged` → следующий шаг | Search, orientation | |
| 67 | | `intercom.symbol_sidecar` | P1 | **Integrated:** `ICodebaseIndexReindexObserver` в Core 0.1.2+ во время HCI reindex (не отдельный обход) | Attach reveal, `[M:…]` L2 | |
| 68 | | `intercom.feed_anchors` | P1 | `RefreshAttachmentAnchorsForCurrentScope` на UI **после** `symbol_sidecar` **или** сразу с L1-only (два прохода: быстрый + точный) | Chips в ленте | |
| 69 | | `intercom.bracket.active_file` | **P0** | `BracketMemberCompletionProvider.WarmIndex(absolutePath)` — построить индекс членов для **текущего** `.cs` в редакторе | Composer `[M:…]` | |
| 70 | | `intercom.bracket.recent_files` | P2 | Опционально: N недавних `.cs` из MRU / открытых вкладок | Bracket file axis | |
| 71 | | `intercom.roslyn_l1.open_documents` | P2 | Прогреть L1 cache для открытых `.cs` (без полного solution compile) | Reveal, prepare | |
| 72 | | `slash.workspace_file_index` | P3 | Если есть тяжёлый file index для `/file open` — snapshot в фоне | Slash completion | |
| 73 | |
| 74 | **Правило v1:** UI-интеракции **никогда** не ждут `await orchestrator.Ready`; они используют кэш **если есть**, иначе — текущий debounced/lazy путь (как сейчас). |
| 75 | |
| 76 | ### 3. Порядок и зависимости |
| 77 | |
| 78 | ```mermaid |
| 79 | flowchart LR |
| 80 | subgraph trigger [Solution open] |
| 81 | SP[SolutionPath changed] |
| 82 | end |
| 83 | subgraph p0 [P0 immediate] |
| 84 | AF[bracket.active_file] |
| 85 | end |
| 86 | subgraph hci [HCI] |
| 87 | HCI_Poke[hci.poke] |
| 88 | HCI_Done[HybridIndexStateChanged] |
| 89 | end |
| 90 | subgraph p1 [P1 after HCI] |
| 91 | SYM[intercom.symbol_sidecar] |
| 92 | ANC[intercom.feed_anchors] |
| 93 | end |
| 94 | SP --> AF |
| 95 | SP --> HCI_Poke |
| 96 | HCI_Poke --> HCI_Done |
| 97 | HCI_Done --> SYM |
| 98 | SYM --> ANC |
| 99 | ``` |
| 100 | |
| 101 | - **P0** `bracket.active_file` — не ждёт HCI (только файл на диске). |
| 102 | - **P1** symbol sidecar — **после** HCI (как [0135](0135-intercom-attach-symbol-cache-and-hci-sidecar.md)); feed anchors — после sidecar **или** повторно (cheap merge). |
| 103 | |
| 104 | ### 4. Контракт «готовности» |
| 105 | |
| 106 | | Состояние | Смысл для UI | |
| 107 | |-----------|----------------| |
| 108 | | `Running` | Фоновые задачи; bracket/reveal могут использовать partial cache | |
| 109 | | `Ready` | Все задачи v1 завершены для текущего scope | |
| 110 | | `Partial` | Отмена/таймаут/ошибка части задач; остальное работает lazy | |
| 111 | | `Cancelled` | Смена solution до завершения | |
| 112 | |
| 113 | HIS / compact status (опционально v1): «Warm-up…» / «Index ready» — не блокирует ввод ([0138](0138-cockpit-command-line-and-parametric-ranges.md), [0140](0140-tci-slash-status-glyphs-and-args-counter.md)). |
| 114 | |
| 115 | ### 5. Настройки (TOML, фаза B) |
| 116 | |
| 117 | ```toml |
| 118 | [solution_warmup] |
| 119 | enabled = true |
| 120 | warm_active_file_on_solution_open = true |
| 121 | warm_feed_anchors_after_symbol_sidecar = true |
| 122 | max_parallel_file_jobs = 2 |
| 123 | ``` |
| 124 | |
| 125 | До появления секции — поведение как **enabled = true** только для P0+P1 цепочки, остальное off. |
| 126 | |
| 127 | ## Не-цели |
| 128 | |
| 129 | - Полная загрузка MSBuild/Roslyn **solution workspace** при open (слишком тяжело; отдельно `RevealLoadSolution` по [0128](0128-intercom-attachment-anchors-and-code-references.md)). |
| 130 | - Блокировка Enter / отправки сообщения до `Ready`. |
| 131 | - Переписывание event log attach при feed refresh (in-memory UI достаточно для v1). |
| 132 | - Прогрев **всех** `.cs` в монорепо без лимита (только active + bounded queue). |
| 133 | |
| 134 | ## Альтернативы |
| 135 | |
| 136 | | Вариант | Почему не выбран как единственный | |
| 137 | |---------|-----------------------------------| |
| 138 | | Только lazy + debounce на вводе | Уже делаем; не убирает «первый раз тормозит» | |
| 139 | | Только HCI | Не покрывает bracket L1 и feed chips | |
| 140 | | Синхронный прогрев на UI при open | Регресс UX (зависание главного окна) | |
| 141 | |
| 142 | ## Последствия |
| 143 | |
| 144 | - **Код:** `Features/SolutionWarmup/Application/` (оркестратор, work items), тонкая связка в `MainWindowViewModel` + подписка `ChatPanel` на `feed_anchors` pass. |
| 145 | - **Тесты:** отмена при смене scope; P0 warm вызывается один раз; mock work item order. |
| 146 | - **Документация:** `playbook-solution-warmup-v1.md` — чеклист для агента (что уже прогрето, что ещё lazy). |
| 147 | - **Связь с фиксом 2026-05-23:** debounce bracket + кэш `BracketMemberCompletionProvider` — **первая линия обороны**; ADR 0141 — **стратегическая** линия (proactive). |
| 148 | |
| 149 | ## Фазы внедрения |
| 150 | |
| 151 | | Фаза | Содержание | Критерий | |
| 152 | |------|------------|----------| |
| 153 | | **A** | `SolutionWarmupOrchestrator` + P0 `bracket.active_file` + cancel on scope change | После open solution ввод `[M:` в активном `.cs` без заметного лага | |
| 154 | | **B** | Цепочка HCI → symbol sidecar → `feed_anchors` + `SolutionWarmupStateChanged` | Chips `member_not_found` → `resolved` без клика после warm | |
| 155 | | **C** | TOML, P2 open documents / recent files, HIS hint | Настраиваемо; статус в MFD | |
| 156 | |
| 157 | ## Критерии приёмки (фаза A+B) |
| 158 | |
| 159 | 1. Смена solution отменяет предыдущий прогрев (нет записи в sidecar со старым `scope_key`). |
| 160 | 2. Активный `.cs` в редакторе: bracket autocomplete по `M:` после open **не парсит с нуля на UI** (индекс уже в cache). |
| 161 | 3. Лента Intercom: после завершения P1 якоря с `member_not_found` обновляют chip без reveal (если member есть в workspace). |
| 162 | 4. Ввод в composer **не блокируется** во время прогрева (регресс-тест на `[` и `/`). |
| 163 | |
| 164 | ## Открытые вопросы |
| 165 | |
| 166 | - Нужен ли **второй** проход `feed_anchors` только по видимым сообщениям ленты (viewport-limited) vs вся история сессии? |
| 167 | - Делить ли очередь прогрева с HCI file I/O (общий `SemaphoreSlim`) для SSD/CPU? |
| 168 | |