| 1 | # ADR 0138: Cockpit Command Line — параметрический CLI для Commander и Pilot |
| 2 | |
| 3 | **Статус:** Accepted · In progress (парсер, slash, CCL IntercomHost фаза A, anchor CLI) |
| 4 | **Дата:** 2026-05-20 |
| 5 | |
| 6 | ## Связанные ADR |
| 7 | |
| 8 | | ADR | Роль | |
| 9 | |-----|------| |
| 10 | | [0133](0133-commander-cockpit-shared-attention-model-and-instrument-deck.md) | Роли Commander (Intercom-first) и Pilot (редактор-first) | |
| 11 | | [0120](0120-primary-work-surface-intercom-or-editor.md) | Forward = Intercom **или** Editor | |
| 12 | | [0119](0119-chat-slash-commands-intercom-surface.md) | Слэш → `command_id`; каталог; autocomplete | |
| 13 | | [0060](0060-keyboard-chord-stack-fms-tactical-strategic.md) | CascadeChord (Ctrl+K): тактика, короткий таймаут — **не** длинный CLI | |
| 14 | | [0124](0124-slash-parametric-editor-line-commands.md) | `/editor line select` — contiguous `L`, `L R`, `L:R` | |
| 15 | | [0136](0136-intercom-feed-gutter-and-slash-namespace.md) | Gutter ordinals; `/intercom message select` | |
| 16 | | [0137](0137-intercom-message-code-correspondence.md) | Contiguous relate/find; disjoint relate — не MVP | |
| 17 | | [0072](0072-chat-topic-cards-intent-melody-keyboard-contract.md) | Chat navigation intents; chord ≠ CLI | |
| 18 | | [0013](0013-command-surface-and-discoverability.md) | Палитра, `command_id`, discoverability | |
| 19 | | [0030](0030-command-ids-hotkeys-and-ui-registry-layers.md) | Реестр команд | |
| 20 | |
| 21 | ## Резюме |
| 22 | |
| 23 | **Cockpit Command Line (CCL)** — **общая** для всего primary workplace полоса полноценного CLI (slash + autocomplete + сложный синтаксис), **не привязанная только к Intercom**. |
| 24 | |
| 25 | | Роль ([0133](0133-commander-cockpit-shared-attention-model-and-instrument-deck.md)) | Primary Forward | Сложные команды | |
| 26 | |--------|-----------------|-----------------| |
| 27 | | **Commander / Lead** | Intercom | Уже «дома» в ленте + composer; CCL усиливает: длинный `/intercom …`, превью, без путаницы с репликой агенту | |
| 28 | | **Pilot** | Редактор | Аккорды для тактики; **сложный** параметрический ввод (напр. несколько диапазонов строк) — **в CCL**, не в chord overlay | |
| 29 | |
| 30 | **Единый вход:** **CascadeChord → `/`** (Ctrl+K, затем `/`) переводит фокус в **CCL** независимо от того, открыт Intercom или Editor. Дальше — полный ввод, в т.ч. `[3;5] [8;15] [20]` и превью эффекта **до** применения. |
| 31 | |
| 32 | **Не цель:** заменить палитру, Melody `c:` или тактические буквы chord; дублировать MCP. |
| 33 | |
| 34 | --- |
| 35 | |
| 36 | ## Контекст (формулировка автора) |
| 37 | |
| 38 | ### Две роли — одна IDE, разный фокус Forward |
| 39 | |
| 40 | **Commander** сидит в **Intercom**: ему нужна богатая, легко расширяемая **командная поверхность** — autocomplete, иерархия `/intercom …`, сложные хвосты (`relate`, `find`, multi-select сообщений). Смешивать это с полем «сообщение агенту» неудобно ([0119](0119-chat-slash-commands-intercom-surface.md)). |
| 41 | |
| 42 | **Pilot** сидит в **редакторе**: основной поток — код и **короткие аккорды** ([0060](0060-keyboard-chord-stack-fms-tactical-strategic.md)). Но часто нужна **сложная** команда, которую физически не ввести в chord window, например выделить в файле: |
| 43 | |
| 44 | ```text |
| 45 | [3;5] [8;15] [20] |
| 46 | ``` |
| 47 | |
| 48 | → строки **3–5** включительно **и** **8–15** включительно **и** строка **20** (disjoint multi-range). |
| 49 | |
| 50 | Тот же класс задач у Commander для **gutter** сообщений: |
| 51 | |
| 52 | ```text |
| 53 | /intercom message select [3;5] [8;15] [20] |
| 54 | ``` |
| 55 | |
| 56 | → сообщения #3–#5, #8–#15 и #20. |
| 57 | |
| 58 | **Вывод:** Command Line — **слой кокпита**, а не «фича Intercom». Intercom получает **экземпляр** CCL у composer; Editor — **экземпляр** у нижнего края Forward (или dock-adjacent). **Ctrl+K `/`** открывает **тот же режим** везде. |
| 59 | |
| 60 | ### Почему не Chord и не только Composer |
| 61 | |
| 62 | | Поверхность | Ограничение | |
| 63 | |-------------|-------------| |
| 64 | | **Chord overlay** | Короткий таймаут второй клавиши; Melody — короткие токены, не `[3;5] [8;15] [20]` | |
| 65 | | **Composer** (Intercom) | Enter = отправить агенту; черновик портится длинным slash | |
| 66 | | **Палитра** | Fuzzy discoverability, не замена параметрического REPL у активной зоны | |
| 67 | |
| 68 | --- |
| 69 | |
| 70 | ## Решение 1: Cockpit Command Line (CCL) |
| 71 | |
| 72 | ### Термины |
| 73 | |
| 74 | | Термин | Значение | |
| 75 | |--------|----------| |
| 76 | | **CCL** | Cockpit Command Line — режим полноценного slash-CLI в Forward | |
| 77 | | **Host** | Конкретная полоса UI: `IntercomHost` (над/под Skia composer) · `EditorHost` (над статусной зоной редактора / dock) | |
| 78 | | **Chord handoff** | Ctrl+K → `/` завершает chord-сессию и фокусирует CCL активного host | |
| 79 | |
| 80 | ### Инварианты |
| 81 | |
| 82 | 1. **Один parser, один runner** — `ChatSlashCommandParser` + существующие runners для Intercom; для Editor — те же `command_id` ([0124](0124-slash-parametric-editor-line-commands.md), [0119](0119-chat-slash-commands-intercom-surface.md), палитра). |
| 83 | 2. **Контекст host** задаёт **подсказки по умолчанию** (префикс `/intercom` vs `/editor`), но **не запрещает** команды другой зоны, если оператор явно набрал полный путь. |
| 84 | 3. **Отдельный буфер** — не `ComposerText`, не `CascadeChordOverlayInputText`. |
| 85 | 4. **Enter** = выполнить (после опционального preview); **Esc** = закрыть CCL без побочных эффектов на черновик сообщения / выделение редактора до Apply. |
| 86 | |
| 87 | ### Размещение host (предложение) |
| 88 | |
| 89 | | Host | Когда виден | Позиция | |
| 90 | |------|-------------|----------| |
| 91 | | **IntercomHost** | `PrimaryWorkSurface == Intercom` (Commander preset) | Над composer ленты (по умолчанию): команда → результат в ленте ниже | |
| 92 | | **EditorHost** | `PrimaryWorkSurface == Editor` (Pilot preset) | Над нижним краем Forward editor / под HUD — уточнить в макете [0120](0120-primary-work-surface-intercom-or-editor.md) | |
| 93 | |
| 94 | Оба host могут существовать в layout, но **активен** один; chord `/` фокусирует host, соответствующий текущему Forward. |
| 95 | |
| 96 | ### IntercomHost: Command Deck (2026-05-21) |
| 97 | |
| 98 | На Skia-ленте Intercom нижний chrome — **один Command Deck**, а не «popup поверх сообщений»: |
| 99 | |
| 100 | | Слой (сверху вниз внутри deck) | Содержимое | |
| 101 | |--------------------------------|------------| |
| 102 | | Верх deck | Slash autocomplete (`SkiaPopupList`), max 6 строк, скролл внутри deck | |
| 103 | | Середина | CCL (`SkiaCommandLineStrip`), если открыт | |
| 104 | | Низ deck | Composer (отправка агенту) | |
| 105 | |
| 106 | **Инварианты:** лента сообщений отступает на `SkiaIntercomCommandDeckLayout.TotalHeight`; подсказки **не рисуются** выше верхней границы deck. Реализация: `Views/Chat/SkiaIntercomCommandDeckLayout.cs`. |
| 107 | |
| 108 | #### Aviation alignment (CDU / PFD, не HUD-menu) |
| 109 | |
| 110 | В кабине **не** кладут выпадающий список FMS поверх **PFD** (attitude / flight path). Длинный ввод идёт в **CDU scratchpad** + **EXEC**; тактика — **glareshield / LSK** ([0060](0060-keyboard-chord-stack-fms-tactical-strategic.md): префикс → вторая клавиша). **MFD** — отдельная *страница* (карта, план), не полупрозрачный слой на лобовом. |
| 111 | |
| 112 | | Авиация | Cascade | |
| 113 | |---------|---------| |
| 114 | | PFD — «куда смотрю» | Forward: лента Intercom / редактор | |
| 115 | | CDU scratchpad + line select | **Command Deck**: popup → CCL → composer | |
| 116 | | EXEC / CLR | Enter commit / Esc в CCL | |
| 117 | | Прозрачное меню на PFD | **Отклонено** — непрозрачный deck, верхняя граница, inset ленты ([0021](0021-pfd-mfd-cockpit-attention-model.md)) | |
| 118 | | Длинный route на MFD-странице | **EditorHost** / MFD Terminal — не overlay на Commander-ленту | |
| 119 | |
| 120 | **Вывод:** Command Deck — не «косметика», а перенос **CDU-панели** под PFD: командный ввод **вне** зоны первичного внимания, но **в том же** Forward, без перекрытия сообщений. |
| 121 | |
| 122 | ```mermaid |
| 123 | flowchart TB |
| 124 | subgraph cockpit [Cascade IDE Cockpit] |
| 125 | chord[Ctrl+K then slash] |
| 126 | chord --> ccl[CCL active host] |
| 127 | ccl --> intercomHost[IntercomHost near composer] |
| 128 | ccl --> editorHost[EditorHost near editor] |
| 129 | intercomHost --> runner[Slash command runner] |
| 130 | editorHost --> runner |
| 131 | runner --> cmd[command_id handlers] |
| 132 | end |
| 133 | commander[Commander role] -.-> intercomHost |
| 134 | pilot[Pilot role] -.-> editorHost |
| 135 | ``` |
| 136 | |
| 137 | --- |
| 138 | |
| 139 | ## Решение 2: Chord `Ctrl+K` → `/` (глобальный handoff) |
| 140 | |
| 141 | | Шаг | Поведение | |
| 142 | |-----|-----------| |
| 143 | | 1 | **CascadeChord** (Ctrl+K) | |
| 144 | | 2 | Вторая клавиша **`/`** | |
| 145 | | 3 | Chord overlay **закрывается** (таймаут снят) | |
| 146 | | 4 | Открывается **CCL** активного Forward host: `/`, фокус, autocomplete | |
| 147 | | 5 | Пользователь вводит произвольную команду (в т.ч. multi-range) | |
| 148 | |
| 149 | `command_id` (рабочее имя): **`cockpit.open_command_line`** — не `intercom.*`-only. |
| 150 | |
| 151 | **Отклонено:** набор `[3;5] [8;15] [20]` внутри chord HUD ([0060](0060-keyboard-chord-stack-fms-tactical-strategic.md)). |
| 152 | |
| 153 | --- |
| 154 | |
| 155 | ## Решение 3: Параметрические multi-range (`[L;R] …`) |
| 156 | |
| 157 | ### Единый микросинтаксис сегментов (предложение) |
| 158 | |
| 159 | Сегмент в **квадратных скобках**; внутри — contiguous диапазон с **`;`** между границами (включительно): |
| 160 | |
| 161 | | Сегмент | Разбор | |
| 162 | |---------|--------| |
| 163 | | `[3;5]` | 3, 4, 5 | |
| 164 | | `[20]` | только 20 | |
| 165 | | `[8;15]` | 8…15 | |
| 166 | |
| 167 | Список сегментов — **через пробел** (как перечисление интервалов): |
| 168 | |
| 169 | ```text |
| 170 | [3;5] [8;15] [20] |
| 171 | ``` |
| 172 | |
| 173 | ### Применение по домену |
| 174 | |
| 175 | | Домен | Пример CCL | Эффект | |
| 176 | |-------|------------|--------| |
| 177 | | **Editor lines** | `/editor line select [3;5] [8;15] [20]` | Multi-range selection / highlight в активном файле ([0124](0124-slash-parametric-editor-line-commands.md) — расширение) | |
| 178 | | **Intercom gutter** | `/intercom message select [3;5] [8;15] [20]` | Multi-select сообщений в detail-ветке ([0136](0136-intercom-feed-gutter-and-slash-namespace.md) — расширение) | |
| 179 | |
| 180 | **Паритет с [0124](0124-slash-parametric-editor-line-commands.md):** legacy формы `5 10`, `5:10` остаются для одного contiguous сегмента; **`[a;b]`** — канон для явного сегмента; несколько сегментов — повтор скобок. |
| 181 | |
| 182 | **Связь с [0137](0137-intercom-message-code-correspondence.md):** **relate** / event log в MVP — **один contiguous** диапазон (`3:5 relate …`). Multi-segment `[3;5] [8;15]` для **select/highlight** — этот ADR; **relate** на disjoint — отдельная фаза / отдельное решение. |
| 183 | |
| 184 | --- |
| 185 | |
| 186 | ## Решение 4: Preview перед применением |
| 187 | |
| 188 | Для сложных команд CCL показывает **превью ожидаемого эффекта** до commit (Enter второй раз или явная кнопка Apply — деталь UX в фазе B). |
| 189 | |
| 190 | | Пример команды | Превью (иллюстрация) | |
| 191 | |----------------|----------------------| |
| 192 | | `/editor line select [3;5] [8;15] [20]` | Подсветка ghost/рамкой диапазонов в редакторе; текст: «Строки: 3–5, 8–15, 20 (17 строк)» | |
| 193 | | `/intercom message select [3;5] [8;15] [20]` | Подсветка строк ленты #3…#5, #8…#15, #20; «Активно: #20» | |
| 194 | |
| 195 | **Режимы (фазы):** |
| 196 | |
| 197 | | Фаза | Preview | |
| 198 | |------|---------| |
| 199 | | **A** | Только текстовый summary под строкой CCL (низкая стоимость) | |
| 200 | | **B** | Визуальный ghost highlight в editor / feed | |
| 201 | | **C** | «Dry-run» без мутации + Apply / Enter | |
| 202 | |
| 203 | Инвариант: preview **не** меняет selection buffer / не пишет event log до Apply. |
| 204 | |
| 205 | --- |
| 206 | |
| 207 | ## Ортогональность входов (обновлённая) |
| 208 | |
| 209 | | Вход | Commander | Pilot | Сложный синтаксис | |
| 210 | |------|-----------|-------|-------------------| |
| 211 | | **Chord** (буква) | next/prev topic, тактика | save, debug, … | **Нет** | |
| 212 | | **Chord `/`** | → CCL IntercomHost | → CCL EditorHost | Handoff | |
| 213 | | **CCL** | `/intercom …` | `/editor …` | **Да** | |
| 214 | | **Composer** | prose агенту | — | Короткий slash опционально | |
| 215 | | **Палитра / `c:`** | discoverability | discoverability | Средний | |
| 216 | | **MCP** | полные args | полные args | **Да** | |
| 217 | |
| 218 | --- |
| 219 | |
| 220 | ## Принятые решения (по уточнению автора) |
| 221 | |
| 222 | | # | Решение | |
| 223 | |---|---------| |
| 224 | | **D1** | CCL — **кокпит-wide**, не только Intercom | |
| 225 | | **D2** | **Ctrl+K `/`** — единый вход в CCL для Commander и Pilot | |
| 226 | | **D3** | Сложные disjoint диапазоны — синтаксис **`[a;b]`** сегментов, несколько сегментов через пробел | |
| 227 | | **D4** | Pilot получает тот же CLI у редактора; chord остаётся для короткой тактики | |
| 228 | | **D5** | Preview перед apply — **желательная** часть дизайна CCL (фаза A текст → B визуал) | |
| 229 | |
| 230 | --- |
| 231 | |
| 232 | ## Позиция и обоснование |
| 233 | |
| 234 | CCL — не «ещё одна фича Intercom», а **слой между chord и палитрой**: локальный REPL **активной зоны внимания** (Forward). Три типа ввода остаются ортогональными: |
| 235 | |
| 236 | | Ввод | Намерение | Договорённость | Реплика | |
| 237 | |------|-----------|----------------|---------| |
| 238 | | **Chord** (буква) | тактика «сейчас» | короткий `command_id` | — | |
| 239 | | **CCL** (`Ctrl+K` → `/`) | параметрика, сложный хвост | полный slash + preview | — | |
| 240 | | **Composer** | — | короткий slash опционально | prose агенту | |
| 241 | |
| 242 | **Почему согласовано с кокпитом** |
| 243 | |
| 244 | - [0133](0133-commander-cockpit-shared-attention-model-and-instrument-deck.md): Commander и Pilot — preset’ы **одного** workplace; им нужны разные дефолты Forward, но **одна** механика сложных команд и **один** muscle memory (`Ctrl+K` → `/`). |
| 245 | - [0060](0060-keyboard-chord-stack-fms-tactical-strategic.md): аккорд не масштабируется на микро-язык `[3;5] [8;15] [20]` — таймаут и Melody нечитаемы; handoff в CCL сохраняет chord «коротким». |
| 246 | - [0119](0119-chat-slash-commands-intercom-surface.md): composer + Enter = агент; длинный slash портит черновик — CCL даёт **второй буфер** без дублирования parser/runner. |
| 247 | - [0137](0137-intercom-message-code-correspondence.md): multi-segment **select/highlight** (этот ADR) отделён от disjoint **relate** в event log (не MVP). |
| 248 | |
| 249 | **Риски (принять осознанно)** |
| 250 | |
| 251 | | Риск | Смягчение | |
| 252 | |------|-----------| |
| 253 | | Два host UI (Skia + Avalonia) | Общая VM/сессия; один parser; UI только host-specific chrome | |
| 254 | | Третий буфер в голове (chord / CCL / composer / палитра) | Chord help: «`/` → command line»; autocomplete в CCL | |
| 255 | | Preview на больших файлах | Лимит строк в ghost; фаза A — только текст | |
| 256 | | Relate на disjoint ranges | Не смешивать с CCL v1; отдельное решение после 0137 MVP | |
| 257 | | Четвёртая «Shell»-поверхность в Forward | См. [§ Инварианты внимания](#adr0138-attention-invariants) | |
| 258 | |
| 259 | <a id="adr0138-attention-invariants"></a> |
| 260 | |
| 261 | ### Инварианты внимания (Forward vs Shell) |
| 262 | |
| 263 | **Не смешивать** зону внимания ([0021](0021-pfd-mfd-cockpit-attention-model.md)) и режим ввода в Forward (таблица выше). |
| 264 | |
| 265 | | Инвариант | Смысл | |
| 266 | |-----------|--------| |
| 267 | | **CCL — единственный REPL Forward** | Сложный slash, preview, multi-range, `anchor peek` — только в **CCL** (или handoff из composer в CCL). Один parser + один runner ([0119](0119-chat-slash-commands-intercom-surface.md)). | |
| 268 | | **Отдельной Shell-страницы в Forward нет** | Не вводим вторую постоянную REPL-панель в лобовом стеке (лента + shell + composer). «Shell» в коде/доках = **хром полосы CCL** (`CCL shell`), не новая primary surface. | |
| 269 | | **MFD Terminal — OS/process** | `dotnet build`, `git`, пайпы, scrollback — страница **MFD** ([0063](0063-instrument-deck-named-composition-one-anchor.md) `MfdShellPage`), **не** дубль `command_id` slash из CCL. | |
| 270 | | **CCL — modal по Esc** | Пока CCL открыт, Forward в режиме «команда»; `Close()` не трогает composer, selection, event log (см. **Инварианты сессии** ниже). | |
| 271 | | **Один активный CCL host** | `IntercomHost` **или** `EditorHost` — не оба видимы ([0120](0120-primary-work-surface-intercom-or-editor.md)). | |
| 272 | |
| 273 | **Валидация в стиле attach ([0128](0128-intercom-attachment-anchors-and-code-references.md) §9.1):** debounced preview в CCL показывает исход (`resolved` / `degraded` / `failed`) **без блокировки** Enter, как chip в ленте — не отдельный «терминал ошибок». |
| 274 | |
| 275 | **Preview severity ≠ канал EICAS ([0021](0021-pfd-mfd-cockpit-attention-model.md) W/C/A):** TCI slash-preview — `SlashCommandPreviewService` / `SlashCommandPreviewKind` (CCL + composer). Таблица — [`playbook-tci-v1.md`](../design/playbook-tci-v1.md) § Preview severity. Глифы ✓ / ✕ / **P(n)** — [0140](0140-tci-slash-status-glyphs-and-args-counter.md). |
| 276 | |
| 277 | **Рекомендуемый порядок внедрения** |
| 278 | |
| 279 | 1. CCL shell + `cockpit.open_command_line` + chord `/` (хотя бы **EditorHost** для Pilot). |
| 280 | 2. `ParametricSegmentListParser` + `/editor line select […]` + текстовый preview. |
| 281 | 3. IntercomHost + `/intercom message select […]` + multi-highlight ленты. |
| 282 | 4. Ghost preview (фаза B); Commander polish; опционально disjoint relate. |
| 283 | |
| 284 | --- |
| 285 | |
| 286 | ## Набросок API (реализация) |
| 287 | |
| 288 | Черновик контрактов для фазы A. Имена и namespace — ориентир; точное размещение файлов — при имплементации. **Не** дублировать `ChatSlashCommandParser` / `ChatSlashCommandRunner` — CCL **переиспользует** их. |
| 289 | |
| 290 | ### Размещение (предложение) |
| 291 | |
| 292 | | Слой | Файл / тип | Роль | |
| 293 | |------|------------|------| |
| 294 | | Domain | `Features/Cockpit/CockpitCommandLineHostKind.cs` | `Intercom` \| `Editor` | |
| 295 | | Session | `Features/Cockpit/ICockpitCommandLineSession.cs` | буфер, preview, commit | |
| 296 | | VM | `ViewModels/CockpitCommandLineViewModel.cs` | binding для обоих host | |
| 297 | | Parse | `Features/Chat/ParametricSegmentListParser.cs` | `[a;b]` … → `IReadOnlyList<IntRange>` | |
| 298 | | Wiring | `MainWindowViewModel` + chord handler | `cockpit.open_command_line` | |
| 299 | |
| 300 | ### `CockpitCommandLineHostKind` |
| 301 | |
| 302 | ```csharp |
| 303 | public enum CockpitCommandLineHostKind |
| 304 | { |
| 305 | Intercom, |
| 306 | Editor, |
| 307 | } |
| 308 | ``` |
| 309 | |
| 310 | ### `ICockpitCommandLineSession` |
| 311 | |
| 312 | Единая сессия на главное окно; **активный host** следует за `PrimaryWorkSurface` ([0120](0120-primary-work-surface-intercom-or-editor.md)), пока CCL открыт. |
| 313 | |
| 314 | ```csharp |
| 315 | public interface ICockpitCommandLineSession |
| 316 | { |
| 317 | bool IsOpen { get; } |
| 318 | CockpitCommandLineHostKind ActiveHost { get; } |
| 319 | |
| 320 | string BufferText { get; set; } |
| 321 | int CaretIndex { get; set; } |
| 322 | |
| 323 | /// <summary>Текстовое превью (фаза A); null если команда не распознана или preview выключен.</summary> |
| 324 | string? PreviewSummary { get; } |
| 325 | |
| 326 | /// <summary>Опционально: сегменты для ghost highlight (фаза B).</summary> |
| 327 | IReadOnlyList<IntRange>? PreviewSegments { get; } |
| 328 | |
| 329 | /// <summary>Ctrl+K → / или palette: cockpit.open_command_line.</summary> |
| 330 | void Open(CockpitCommandLineHostKind? host = null, string initialText = "/"); |
| 331 | |
| 332 | void Close(); // Esc — без commit |
| 333 | |
| 334 | /// <summary>Пересчитать preview при изменении BufferText (debounced).</summary> |
| 335 | void RefreshPreview(); |
| 336 | |
| 337 | /// <summary>Enter: parse → runner → side effects; затем Close или очистка буфера.</summary> |
| 338 | Task<CockpitCommandLineCommitResult> TryCommitAsync(CancellationToken cancellationToken = default); |
| 339 | } |
| 340 | |
| 341 | public readonly record struct CockpitCommandLineCommitResult( |
| 342 | bool Handled, |
| 343 | bool Success, |
| 344 | string? UserMessage); |
| 345 | ``` |
| 346 | |
| 347 | **Инварианты сессии** |
| 348 | |
| 349 | - `BufferText` **≠** `ComposerText` / `ChatInput` / `CascadeChordOverlayInputText`. |
| 350 | - `Close()` не вызывает handlers; не трогает selection/editor/event log. |
| 351 | - `TryCommitAsync` делегирует в существующий `ChatSlashCommandRunner.TryRunAsync(BufferText)` ([0119](0119-chat-slash-commands-intercom-surface.md)); host влияет только на **default autocomplete prefix**, не на whitelist команд. |
| 352 | - Не дублирует **MFD Terminal** и не заменяет его; workplace-команды (`/intercom`, `/editor`, `/anchor`, `/file`) — только здесь ([§ Инварианты внимания](#adr0138-attention-invariants)). |
| 353 | |
| 354 | ### Chord handoff |
| 355 | |
| 356 | ```csharp |
| 357 | // IdeCommands / chord registry |
| 358 | public const string OpenCommandLine = "cockpit.open_command_line"; |
| 359 | |
| 360 | // MainWindowViewModel (псевдокод) |
| 361 | void OnChordSlash() |
| 362 | { |
| 363 | CascadeChord.EndSession(); |
| 364 | var host = PrimaryWorkSurface == PrimaryWorkSurfaceKind.Intercom |
| 365 | ? CockpitCommandLineHostKind.Intercom |
| 366 | : CockpitCommandLineHostKind.Editor; |
| 367 | CommandLineSession.Open(host, initialText: "/"); |
| 368 | } |
| 369 | ``` |
| 370 | |
| 371 | ### `ParametricSegmentListParser` |
| 372 | |
| 373 | Расширение `ChatSlashParametricArgsBuilder.TryParseLineRangeTail` (один contiguous сегмент) → список сегментов в скобках. |
| 374 | |
| 375 | ```csharp |
| 376 | public readonly record struct IntRange(int Start, int End); // inclusive, 1-based |
| 377 | |
| 378 | public static class ParametricSegmentListParser |
| 379 | { |
| 380 | /// <summary> |
| 381 | /// Разбор хвоста args: <c>[3;5] [8;15] [20]</c> или legacy один сегмент |
| 382 | /// (<c>3:5</c>, <c>3 5</c>, <c>5</c>) как один <see cref="IntRange"/>. |
| 383 | /// </summary> |
| 384 | public static bool TryParse( |
| 385 | string? argsTail, |
| 386 | out IReadOnlyList<IntRange> segments, |
| 387 | out string error); |
| 388 | |
| 389 | public static string FormatSummary(IReadOnlyList<IntRange> segments, string unitLabel); |
| 390 | // unitLabel: "строки" | "сообщения" |
| 391 | } |
| 392 | ``` |
| 393 | |
| 394 | **Потребители** |
| 395 | |
| 396 | | Команда | После parse | |
| 397 | |---------|-------------| |
| 398 | | `/editor line select …` | `EditorLineSelectHandler` — multi-range selection | |
| 399 | | `/intercom message select …` | `ChatSlashIntercomHandlers` — multi-highlight + active ordinal = последний сегмент | |
| 400 | |
| 401 | Preview: `FormatSummary` → `PreviewSummary`; `PreviewSegments` → ghost (фаза B). |
| 402 | |
| 403 | ### Preview pipeline (фаза A) |
| 404 | |
| 405 | ```csharp |
| 406 | public sealed class SlashCommandPreviewService |
| 407 | { |
| 408 | public SlashCommandPreviewResult Evaluate(string? slashBuffer); |
| 409 | public SlashCommandPreviewResult EvaluateComposerAtCaret(string? chatInput, int caretIndex); |
| 410 | } |
| 411 | // правила: internal SlashCommandPreviewEvaluator |
| 412 | ``` |
| 413 | |
| 414 | Логика: `ChatSlashCommandParser.TryParse` → если action известен и tail содержит segment syntax → summary без мутации VM. |
| 415 | |
| 416 | ### TOML ([cockpit.command_line]) — черновик |
| 417 | |
| 418 | ```toml |
| 419 | [cockpit.command_line] |
| 420 | preview_enabled = true |
| 421 | preview_mode = "text" # "text" | "ghost" (фаза B) |
| 422 | intercom_host = "above_composer" # Q1 |
| 423 | ``` |
| 424 | |
| 425 | ### Критерии «API готов к фазе A» |
| 426 | |
| 427 | 1. `ICockpitCommandLineSession` подключён к IntercomHost и EditorHost (хотя бы один видимый). |
| 428 | 2. `ParametricSegmentListParser.TryParse` покрыт unit-тестами (наследие форм из [0124](0124-slash-parametric-editor-line-commands.md) + bracket segments). |
| 429 | 3. `TryCommitAsync` → `ChatSlashCommandRunner` без форка parser. |
| 430 | 4. `RefreshPreview` не меняет editor/feed до commit. |
| 431 | |
| 432 | --- |
| 433 | |
| 434 | <a id="adr0138-open-questions"></a> |
| 435 | |
| 436 | ## Принятые решения по открытым вопросам (Q1–Q5) |
| 437 | |
| 438 | | # | Решение | |
| 439 | |---|---------| |
| 440 | | **Q1** | **IntercomHost над composer** — CLI между лентой и полем prose; граница «дискурс / команда» видна при взгляде сверху вниз | |
| 441 | | **Q2** | **Slash в composer** — да, для коротких команд (`/help`, `/clear`, короткий `/overview`). Любая параметрика со скобками `[…]` или несколькими сегментами → handoff в CCL (`Ctrl+K` → `/`) | |
| 442 | | **Q3** | **Preview MVP — фаза A:** только текстовый summary под строкой CCL. Ghost (фаза B) — после стабилизации парсера | |
| 443 | | **Q4** | **Debounced preview при наборе + один Enter = Apply.** Не два Enter и не отдельный Ctrl+Enter | |
| 444 | | **Q5** | **`3:5` / `3 5` / `5`** остаются alias **одного** contiguous сегмента ([0124](0124-slash-parametric-editor-line-commands.md)). **Disjoint** — только `[3;5] [8;15] [20]` | |
| 445 | |
| 446 | ### Остаются открытыми |
| 447 | |
| 448 | | # | Вопрос | |
| 449 | |---|--------| |
| 450 | | **Q6** | TOML: `[cockpit.command_line]` — height, preview_enabled | |
| 451 | | **Q7** | Disjoint **relate** на `[3;5] [8;15]` — отдельно от MVP 0137 (contiguous relate only) | |
| 452 | |
| 453 | --- |
| 454 | |
| 455 | ## Альтернативы |
| 456 | |
| 457 | | Альтернатива | Почему отклонена | |
| 458 | |--------------|------------------| |
| 459 | | Только Intercom CLI | Не закрывает Pilot multi-line select | |
| 460 | | Только палитра | Нет параметрического REPL у зоны внимания | |
| 461 | | Chord Melody для ranges | Нечитаемо; таймаут | |
| 462 | | Два разных parser | Нарушает [0119](0119-chat-slash-commands-intercom-surface.md) / [0030](0030-command-ids-hotkeys-and-ui-registry-layers.md) | |
| 463 | |
| 464 | --- |
| 465 | |
| 466 | ## Последствия |
| 467 | |
| 468 | - [0060](0060-keyboard-chord-stack-fms-tactical-strategic.md): `/` = handoff в CCL; обновить chord help. |
| 469 | - [0119](0119-chat-slash-commands-intercom-surface.md): CCL — второй host буфера; composer остаётся для prose. |
| 470 | - [0124](0124-slash-parametric-editor-line-commands.md): парсер multi-segment `[;]`; preview для line select. |
| 471 | - [0136](0136-intercom-feed-gutter-and-slash-namespace.md): multi-highlight + парсер для message select. |
| 472 | - [0133](0133-commander-cockpit-shared-attention-model-and-instrument-deck.md): CCL как shared instrument для обеих ролей. |
| 473 | - [0137](0137-intercom-message-code-correspondence.md): явная граница select multi-range vs relate contiguous. |
| 474 | - Реализация: `MainWindowViewModel` + Skia Intercom host + Avalonia/editor host; `cockpit.open_command_line`. |
| 475 | |
| 476 | ## Критерии приёмки |
| 477 | |
| 478 | ### Фаза A (CCL + chord + текстовый preview) |
| 479 | |
| 480 | 1. Ctrl+K → `/` в Intercom → IntercomHost CCL с `/` и autocomplete. |
| 481 | 2. Ctrl+K → `/` в Editor → EditorHost CCL с `/` и autocomplete. |
| 482 | 3. `/editor line select [3;5] [8;15] [20]` — текстовое превью диапазонов; Enter — selection в файле. |
| 483 | 4. `/intercom message select [3;5] [8;15] [20]` — превью ordinals; Enter — multi-highlight в ленте. |
| 484 | 5. Esc не портит composer и не меняет editor до Apply. |
| 485 | |
| 486 | ### Фаза B (визуальный preview) |
| 487 | |
| 488 | 6. Ghost highlight в редакторе и ленте при наборе в CCL (до Apply). |
| 489 | |
| 490 | --- |
| 491 | |
| 492 | ## Статус реализации |
| 493 | |
| 494 | | Компонент | Состояние | |
| 495 | |-----------|-----------| |
| 496 | | ADR | **Accepted** — D1–D5, Q1–Q5 | |
| 497 | | `ParametricSegmentListParser` | **Implemented** — legacy + `[a;b]`; unit-тесты | |
| 498 | | `/intercom message select […]` multi-highlight | **Implemented** — slash/composer; Skia feed | |
| 499 | | `SlashCommandPreviewService` / `SlashCommandPreviewEvaluator` | **Implemented** — текстовый summary для slash (CCL + composer) | |
| 500 | | `/editor line select` multi-segment | **Partial** — один contiguous; disjoint → ошибка до CCL/editor union | |
| 501 | | CCL **IntercomHost** (полоса над composer, preview, Enter/Esc) | **Partial** — фаза A; `ICockpitCommandLineSession` + `CommandLineSession` | |
| 502 | | CCL **EditorHost** | — | |
| 503 | | `cockpit.open_command_line` + chord Ctrl+K `/` | **Partial** — палитра/MCP; chord — | |
| 504 | | Preview в CCL UI (текст при наборе) | **Implemented** — `SlashCommandPreviewService` (CCL + composer) | |
| 505 | | Ghost preview (фаза B) | — | |
| 506 | | `/intercom message anchors list`, `/anchor peek` | **Implemented** — slash + CCL; chip `a:…` ([0128 §10.1](0128-intercom-attachment-anchors-and-code-references.md#adr0128-p10b)) | |
| 507 | |
| 508 | --- |
| 509 | |
| 510 | ## История |
| 511 | |
| 512 | | Дата | Изменение | |
| 513 | |------|-----------| |
| 514 | | 2026-05-20 | Черновик (Intercom-only ICL) | |
| 515 | | 2026-05-20 | **Пересмотр:** CCL кокпит-wide; Commander/Pilot; `[3;5] [8;15] [20]`; preview; Ctrl+K `/` глобально | |
| 516 | | 2026-05-20 | § «Позиция и обоснование»; § «Набросок API» (`ICockpitCommandLineSession`, `ParametricSegmentListParser`) | |
| 517 | | 2026-05-21 | **Accepted**; Q1–Q5 закрыты; `ParametricSegmentListParser` + multi message select + preview builder | |
| 518 | | 2026-05-21 | § [Инварианты внимания](#adr0138-attention-invariants): CCL = единственный REPL Forward; MFD Terminal для OS/process | |
| 519 | | 2026-05-20 | Фаза A: IntercomHost CCL, `/intercom message anchors list`, `/anchor peek`, chip `a:…` | |
| 520 | |