| 1 | # ADR 0124: Параметрические слэш-команды — полный паритет каталога IML в Intercom |
| 2 | |
| 3 | **Статус:** Accepted · Implemented |
| 4 | **Дата:** 2026-05-17 |
| 5 | **Обновлено:** 2026-05-17 — расширение ADR до полного описания фичи (каталог, парсер, binders, TOML, тесты). |
| 6 | |
| 7 | ## Связанные ADR |
| 8 | |
| 9 | | ADR | Роль | |
| 10 | |-----|------| |
| 11 | | [0119](0119-chat-slash-commands-intercom-surface.md) | Базовый контур slash в `ChatInput` (парсер, каталог, autocomplete, local execution) — **этот ADR — надстройка для IML-параметрики** | |
| 12 | | [0109](0109-declarative-parametric-melody-catalog-toml-and-code-binders.md) | `melody_shape`, `tail_signature`, `wire_class`, binders без поля `binder` в TOML | |
| 13 | | [0081](0081-parametric-intent-melodies-editor-line-ranges.md) | Семантика диапазона строк редактора (1-based, inclusive) | |
| 14 | | [0111](0111-editor-linenumber-linerange-value-objects.md) | `LineNumber` / `LineRange` | |
| 15 | | [0030](0030-command-ids-hotkeys-and-ui-registry-layers.md) | Канон `command_id` | |
| 16 | | [0120](0120-primary-work-surface-intercom-or-editor.md) | Intercom как CLI сессии | |
| 17 | | [0060](0060-keyboard-chord-stack-fms-tactical-strategic.md) | Melody `c:` / CascadeChord | |
| 18 | | [0108](0108-web-ai-portal-host-object-tools-bridge.md) | `show_web_ai_portal_page` | |
| 19 | | [0125](0125-slash-workspace-file-commands-and-dynamic-completion.md) | Workspace/file slash и dynamic completion — **ортогонально** параметрике этого ADR | |
| 20 | |
| 21 | ### Вне ADR |
| 22 | |
| 23 | | Документ | Роль | |
| 24 | |----------|------| |
| 25 | | [intent-melody-language-v1.md](../intent-melody-language-v1.md) | IML v1/v2; slash — отдельная грамматика `/` | |
| 26 | | [`IntentMelody/intent-catalog.toml`](../../IntentMelody/intent-catalog.toml) | command-first: melody + slash на одном `command_id` | |
| 27 | | [MCP-PROTOCOL.md](../MCP-PROTOCOL.md) | `ide_execute_command` — тот же wire JSON | |
| 28 | |
| 29 | ## Резюме |
| 30 | |
| 31 | Фича **«параметрический slash»** закрывает разрыв между **IML v2** (палитра `c:`, аккорды) и **unified command line** Intercom ([0119](0119-chat-slash-commands-intercom-surface.md)): |
| 32 | |
| 33 | 1. **Любая** команда с `melody_shape = parametric` в `intent-catalog.toml` может иметь slash-форму с **тем же** `command_id` и **теми же** JSON-args, что melody и MCP. |
| 34 | 2. Разбор хвоста slash (**ArgsTail**) диспетчеризуется по **`wire_class`** из каталога — не по отдельному `switch (command_id)` в Runner. |
| 35 | 3. Slash-пути (**`/editor line select`**, **`/portal open`**) задаются **вручную** в TOML (`help`, `group`) для autocomplete; пути **не** выводятся автоматически из `tail_signature`. |
| 36 | 4. Bundled v1 покрывает **все три** параметрических корня каталога; добавление четвёртого — по чеклисту [§8](#adr0124-p8). |
| 37 | |
| 38 | **Не входит в фичу:** автогенерация slash-путей, короткие мнемоники `/els`, новые `wire_class` без ветки в `ChatSlashParametricArgsBuilder`. |
| 39 | |
| 40 | **Связанный, но другой слой:** slash с **произвольным текстовым хвостом** для команд IML `simple` — см. [§ Контекст](#adr0124-ctx-layers). |
| 41 | |
| 42 | --- |
| 43 | |
| 44 | ## Контекст |
| 45 | |
| 46 | <a id="adr0124-ctx-layers"></a> |
| 47 | |
| 48 | ### Три слоя «параметра» в Intercom ([0119](0119-chat-slash-commands-intercom-surface.md)) |
| 49 | |
| 50 | | Слой | IML в каталоге | Примеры slash | Где удобно | Сборка args | |
| 51 | |------|----------------|---------------|------------|-------------| |
| 52 | | **A. Structured parametric** | `melody_shape = parametric`, `wire_class` | `/editor line select 5 10`, `/portal open url` | Чат; палитра `c:els` / `c:wai` | **`ChatSlashParametricArgsBuilder`** → те же binders, что melody | |
| 53 | | **B. Free-text tail (prose)** | `melody_shape = simple`, тот же `command_id` | `/git commit "feat: test"`, `/search pattern`, `/spine set focus` | **Преимущественно чат** — пробелы, кавычки, длинный текст | `BuildArgs` по `command_id`; хвост = одно поле (`message`, `pattern`, …) | |
| 54 | | **C. Без хвоста** | simple | `/git status`, `/overview` | Чат, autocomplete | только `command_id` | |
| 55 | |
| 56 | **Про `/git commit`:** в каталоге это **`git_commit`** + melody **`c:gc`** ([`intent-catalog.toml`](../../IntentMelody/intent-catalog.toml)) — это **IML-команда**, не «чужая» slash. Но форма хвоста — **слой B**, не A: в TOML нет `wire_class` для произвольного текста коммита, потому что **аккорд/Chord** с многословным сообщением неудобен (пробелы, кавычки, таймаут цепочки). В **чате** слой B как раз «огонь»: `/git commit feat: test` или `/git commit "feat: test"` — весь ArgsTail → `message` (кавычки снимаются, [`ChatSlashArgsTail`](../../Features/Chat/ChatSlashArgsTail.cs)). |
| 57 | |
| 58 | Путаница «почему не полная параметрика» — смешение **A** и **B**. **Полная параметрика (A)** = все записи `melody_shape = parametric` в TOML. **Git commit** намеренно в **B** до появления, например, `text_remainder` в [0109](0109-declarative-parametric-melody-catalog-toml-and-code-binders.md) и осознанного chord UX. |
| 59 | |
| 60 | ### Каталог на сегодня (bundled) |
| 61 | |
| 62 | | `command_id` | Melody | `wire_class` | `tail_signature` (сокращ.) | |
| 63 | |--------------|--------|--------------|---------------------------| |
| 64 | | `select` | `c:els` | `int_chain_colon_space` | `<start:ln>:<end:ln>` | |
| 65 | | `apply_edit` | `c:eld` | `int_chain_colon_space` | то же (удаление через `new_text=""`) | |
| 66 | | `show_web_ai_portal_page` | `c:wai` | `url_remainder` | `<url:url>` | |
| 67 | |
| 68 | Других `parametric` корней в bundled `intent-catalog.toml` **нет** — фича считается **полной** относительно каталога, а не относительно всех возможных будущих wire. |
| 69 | |
| 70 | --- |
| 71 | |
| 72 | ## Проблема |
| 73 | |
| 74 | 1. **Разрыв входов:** оператор в Intercom не может выделить/удалить строки или открыть портал по URL без палитры или `c:`. |
| 75 | 2. **Дублирование binders:** без ADR каждая slash-команда тянет свой парсер колонок/URL в `ChatPanelViewModel`. |
| 76 | 3. **Парсер [0119 §4](0119-chat-slash-commands-intercom-surface.md#adr0119-p4):** путь `/editor line select` — **три** токена namespace, не два. |
| 77 | 4. **Discoverability:** без записи в TOML slash не попадает в autocomplete ([0119 §6](0119-chat-slash-commands-intercom-surface.md#adr0119-p6)). |
| 78 | |
| 79 | --- |
| 80 | |
| 81 | ## Решение |
| 82 | |
| 83 | <a id="adr0124-p1"></a> |
| 84 | |
| 85 | ### 1. Инварианты фичи |
| 86 | |
| 87 | | # | Инвариант | |
| 88 | |---|-----------| |
| 89 | | I1 | Slash-параметрика **только** для `command_id`, у которого в каталоге есть ровно один parametric melody-корень (`TryGetParametricRootByCommandId`). | |
| 90 | | I2 | JSON-args для slash **идентичны** melody/MCP для того же `command_id` (те же binders). | |
| 91 | | I3 | Ошибки валидации (файл не открыт, диапазон за файлом) — **в пузыре слэш-команды**, не silent no-op. | |
| 92 | | I4 | Нераспознанный `/…` **не** уходит агенту ([0119](0119-chat-slash-commands-intercom-surface.md)). | |
| 93 | | I5 | Короткие slash-алиасы (`/els`) — **запрещены**; читаемые пути + autocomplete. | |
| 94 | |
| 95 | <a id="adr0124-p2"></a> |
| 96 | |
| 97 | ### 2. Контур исполнения |
| 98 | |
| 99 | ```text |
| 100 | ChatInput (строка с /) |
| 101 | → ChatSlashCommandParser |
| 102 | flat | namespace+action | namespace+action+subAction (editor line) |
| 103 | → ChatSlashCommandCatalog.TryResolve(slash path → SlashRouteEntry) |
| 104 | → ChatSlashCommandRunner |
| 105 | if TryGetParametricRootByCommandId(command_id): |
| 106 | ChatSlashParametricArgsBuilder(argsTail, wire_class, ChatSlashEditorContext) |
| 107 | else: |
| 108 | BuildArgs (ad-hoc) |
| 109 | → executeIdeCommand(command_id, args) // тот же мост, что MCP |
| 110 | → пузырь slash в ленте: success / DetailText (ошибка binder) |
| 111 | ``` |
| 112 | |
| 113 | **Контекст редактора** (`ChatSlashEditorContext`): `CurrentFilePath`, `EditorText` — из `ChatPanelViewModel` (`_getCurrentFilePath`, `_getEditorText`). Нужен для `int_chain_colon_space`; для `url_remainder` передаётся, но не обязателен. |
| 114 | |
| 115 | <a id="adr0124-p3"></a> |
| 116 | |
| 117 | ### 3. Диспетчеризация по `wire_class` |
| 118 | |
| 119 | Реализация: [`ChatSlashParametricArgsBuilder`](../../Features/Chat/ChatSlashParametricArgsBuilder.cs). |
| 120 | |
| 121 | | `wire_class` | `TailWireKind` | Условие | Slash ArgsTail | Binder / JSON | |
| 122 | |--------------|----------------|---------|----------------|---------------| |
| 123 | | `int_chain_colon_space` | `DelimitedSlots` | 2 numeric slots, line (`:ln`) | см. [§4](#adr0124-p4) | `ParametricLineRangeArgsBuilder` → `file_path`, `start_line`, `start_column`, `end_line`, `end_column` (+ `new_text` для delete) | |
| 124 | | `url_remainder` | `SingleRemainder` | URL slot в signature | весь хвост = URL; **пустой допустим** | `null` args или `{ "url": "…" }` | |
| 125 | |
| 126 | Неподдержанная комбинация `wire_class` + signature → ошибка: *«Форма wire_class … для slash ещё не поддержана»* (явный сигнал разработчику добавить ветку). |
| 127 | |
| 128 | **Связь с melody:** для line-range строится синтетический `ParametricIntentMelody.ParsedLineRange` с `Slug` из каталога (`els`/`eld`), чтобы не дублировать логику колонок. |
| 129 | |
| 130 | <a id="adr0124-p4"></a> |
| 131 | |
| 132 | ### 4. Грамматика ArgsTail: диапазон строк |
| 133 | |
| 134 | После резолва пути (`/editor line select` или `delete`) остаётся **только** числовой хвост (без повторения slug): |
| 135 | |
| 136 | | Ввод | `start` | `end` | |
| 137 | |------|---------|-------| |
| 138 | | `5` | 5 | 5 | |
| 139 | | `5 10` | 5 | 10 | |
| 140 | | `5:10` | 5 | 10 | |
| 141 | | `5;10` | 5 | 10 | |
| 142 | |
| 143 | Правила: |
| 144 | |
| 145 | - Номера **1-based**, **inclusive** ([0081](0081-parametric-intent-melodies-editor-line-ranges.md)). |
| 146 | - `end < start` → ошибка. |
| 147 | - Вне файла / нет `file_path` → ошибка из `ParametricLineRangeArgsBuilder`. |
| 148 | - Пустой хвост → ошибка с подсказкой формата. |
| 149 | |
| 150 | <a id="adr0124-p5"></a> |
| 151 | |
| 152 | ### 5. Грамматика ArgsTail: URL (`url_remainder`) |
| 153 | |
| 154 | Путь: **`/portal open`** → `show_web_ai_portal_page`. |
| 155 | |
| 156 | | Ввод | Args | |
| 157 | |------|------| |
| 158 | | `/portal open` | `null` (страница по умолчанию, как `c:wai`) | |
| 159 | | `/portal open example.com` | `{ "url": "example.com" }` | |
| 160 | | `/portal open https://host/path` | весь хвост после `open ` — одна строка URL | |
| 161 | |
| 162 | Пробелы в URL сохраняются в хвосте (редкий случай); нормализация схемы — как у melody ([0108](0108-web-ai-portal-host-object-tools-bridge.md)). |
| 163 | |
| 164 | <a id="adr0124-p6"></a> |
| 165 | |
| 166 | ### 6. Парсер slash: уровни токенов |
| 167 | |
| 168 | Расширение [`ChatSlashCommandParser`](../../Features/Chat/ChatSlashCommandParser.cs): |
| 169 | |
| 170 | | Форма | `Shape` | Поля parse | Пример | |
| 171 | |-------|---------|------------|--------| |
| 172 | | `/overview` | Flat | `Head=overview` | Intercom | |
| 173 | | `/build run` | NamespaceAction | `Head`, `Action`, `ArgsTail` | IDE | |
| 174 | | `/editor line select 5 10` | NamespaceAction + **SubAction** | `Head=editor`, `Action=line`, `SubAction=select`, `ArgsTail=5 10` | **только editor line** | |
| 175 | |
| 176 | Резолв каталога для SubAction: |
| 177 | |
| 178 | ```text |
| 179 | slashPath = "/" + Head + " " + Action + " " + SubAction |
| 180 | // → "/editor line select" |
| 181 | ``` |
| 182 | |
| 183 | **Ограничение:** `SubAction` для `editor line` — только `select` | `delete` (маппинг на `select` / `apply_edit` через разные slash-пути в TOML, не через один путь с глаголом в хвосте). |
| 184 | |
| 185 | <a id="adr0124-p7"></a> |
| 186 | |
| 187 | ### 7. Каталог TOML (`intent-catalog.toml`) |
| 188 | |
| 189 | На каждый parametric `[[command]]`: |
| 190 | |
| 191 | ```toml |
| 192 | [[command]] |
| 193 | command_id = "select" |
| 194 | melody_slug = "els" |
| 195 | melody_shape = "parametric" |
| 196 | # … melody_* … |
| 197 | |
| 198 | [[command.form.slash]] |
| 199 | path = "/editor line select" |
| 200 | help = "Выделить строки …" |
| 201 | group = "Editor" |
| 202 | ``` |
| 203 | |
| 204 | Правила: |
| 205 | |
| 206 | - **`path`** — ключ в `IntentSlashCatalog.SlashRoutes` (case-insensitive). |
| 207 | - **`help`** / **`group`** — autocomplete и `/help` ([0119](0119-chat-slash-commands-intercom-surface.md)). |
| 208 | - Статические `[command.form.slash.args]` (`page`, `surface`) — для **не-параметрических** slash (MFD); параметрика несёт args в **ArgsTail**, не в TOML-args. |
| 209 | |
| 210 | Обнаружение parametric slash в Runner: **не** флаг в TOML, а `IntentMelodyCatalog.TryGetParametricRootByCommandId(command_id)`. |
| 211 | |
| 212 | <a id="adr0124-p8"></a> |
| 213 | |
| 214 | ### 8. Чеклист: новая parametric-команда |
| 215 | |
| 216 | 1. **TOML melody:** `melody_shape = parametric`, `tail_signature`, `wire_class`, согласованный с `[[tail_wire_class]]`. |
| 217 | 2. **Binder в коде** ([0109](0109-declarative-parametric-melody-catalog-toml-and-code-binders.md)): melody-путь (`ParametricIntentMelody` / dedicated builder). |
| 218 | 3. **Ветка в `ChatSlashParametricArgsBuilder`** для этого `wire_class` (если новый — ADR-апдейт). |
| 219 | 4. **`[[command.form.slash]]`** с читаемым `path` + `help`. |
| 220 | 5. **Парсер:** при нестандартной форме пути — расширение `ChatSlashCommandParser` (как SubAction для editor). |
| 221 | 6. **Тесты:** `ChatSlashParametricTests` + при необходимости melody tests. |
| 222 | 7. **Autocomplete:** `group` / `slash_group` в TOML; сортировка — по группе и `path` без хардкода в C#. |
| 223 | |
| 224 | <a id="adr0124-p9"></a> |
| 225 | |
| 226 | ### 9. UX и ошибки |
| 227 | |
| 228 | - Перед отправкой агенту: пузырь slash-команды в ленте ([0119](0119-chat-slash-commands-intercom-surface.md)); статус success/fail. |
| 229 | - `DetailText` при ошибке binder — текст для оператора (напр. «Диапазон выходит за пределы файла»). |
| 230 | - Успех без содержательного JSON — без лишнего «ok» в ленте (как у других slash). |
| 231 | |
| 232 | <a id="adr0124-p10"></a> |
| 233 | |
| 234 | ### 10. Паритет входов (сводная таблица) |
| 235 | |
| 236 | | Действие | Melody | Slash | MCP | |
| 237 | |----------|--------|-------|-----| |
| 238 | | Выделить строки 5–10 | `c:els:5:10` | `/editor line select 5 10` | `select` + JSON | |
| 239 | | Удалить строки 3–7 | `c:eld:3:7` | `/editor line delete 3 7` | `apply_edit` + JSON | |
| 240 | | Web AI Portal | `c:wai:` | `/portal open` | `show_web_ai_portal_page` | |
| 241 | | Portal + URL | `c:wai:host` | `/portal open host` | `{ "url": "host" }` | |
| 242 | |
| 243 | --- |
| 244 | |
| 245 | ## Ортогональность входов |
| 246 | |
| 247 | | Вход | Параметрика редактора / URL | |
| 248 | |------|-----------------------------| |
| 249 | | Палитра `c:` | ✅ каталог + binders | |
| 250 | | CascadeChord | ✅ тот же каталог | |
| 251 | | **Chat slash** | ✅ **эта фича** | |
| 252 | | MCP `ide_execute_command` | ✅ | |
| 253 | | Hotkeys | ❌ (нет line-range в chord по умолчанию) | |
| 254 | |
| 255 | --- |
| 256 | |
| 257 | ## Диаграмма |
| 258 | |
| 259 | ```mermaid |
| 260 | flowchart TD |
| 261 | input[ChatInput slash line] |
| 262 | input --> parser[ChatSlashCommandParser] |
| 263 | parser --> catalog[ChatSlashCommandCatalog] |
| 264 | catalog --> runner[ChatSlashCommandRunner] |
| 265 | runner --> q{parametric in catalog?} |
| 266 | q -->|yes| param[ChatSlashParametricArgsBuilder] |
| 267 | q -->|no| adhoc[BuildArgs ad-hoc] |
| 268 | param --> wc{wire_class} |
| 269 | wc -->|int_chain_colon_space| line[ParametricLineRangeArgsBuilder] |
| 270 | wc -->|url_remainder| url[JSON url or null] |
| 271 | line --> exec[executeIdeCommand] |
| 272 | url --> exec |
| 273 | adhoc --> exec |
| 274 | exec --> bubble[Slash bubble in feed] |
| 275 | melody[c: palette chord] --> line |
| 276 | melody --> url |
| 277 | mcp[ide_execute_command] --> exec |
| 278 | ``` |
| 279 | |
| 280 | --- |
| 281 | |
| 282 | ## Якоря реализации (Implemented) |
| 283 | |
| 284 | | Компонент | Путь | Роль | |
| 285 | |-----------|------|------| |
| 286 | | Каталог lookup | [`IntentMelodyCatalog.cs`](../../Services/IntentMelodyCatalog.cs) | `TryGetParametricRootByCommandId` | |
| 287 | | Slash args | [`ChatSlashParametricArgsBuilder.cs`](../../Features/Chat/ChatSlashParametricArgsBuilder.cs) | ArgsTail → JSON по `wire_class` | |
| 288 | | Editor context | [`ChatSlashEditorContext.cs`](../../Features/Chat/ChatSlashEditorContext.cs) | file + text для line binder | |
| 289 | | Parser SubAction | [`ChatSlashCommandParser.cs`](../../Features/Chat/ChatSlashCommandParser.cs) | `/editor line select|delete` | |
| 290 | | Catalog resolve | [`ChatSlashCommandCatalog.cs`](../../Features/Chat/ChatSlashCommandCatalog.cs) | path + SubAction; порядок из TOML `group` + path | |
| 291 | | Runner | [`ChatSlashCommandRunner.cs`](../../Features/Chat/ChatSlashCommandRunner.cs) | ветка parametric vs ad-hoc | |
| 292 | | VM wiring | [`ChatPanelViewModel.cs`](../../Features/Chat/ChatPanelViewModel.cs) | `ChatSlashEditorContext` delegate | |
| 293 | | TOML | [`IntentMelody/intent-catalog.toml`](../../IntentMelody/intent-catalog.toml) | slash paths + melody | |
| 294 | | Line binder (shared) | [`ParametricLineRangeArgsBuilder.cs`](../../Services/ParametricLineRangeArgsBuilder.cs) | melody + slash | |
| 295 | | Melody entry | [`ParametricIntentMelody.cs`](../../Services/ParametricIntentMelody.cs) | `TryResolveParametricExecution` | |
| 296 | | Тесты | [`ChatSlashParametricTests.cs`](../../CascadeIDE.Tests/ChatSlashParametricTests.cs) | parser, catalog, binders | |
| 297 | |
| 298 | --- |
| 299 | |
| 300 | ## Non-goals |
| 301 | |
| 302 | - Slash для **всех** `IdeCommands` с любыми args (только каталог `parametric` + ad-hoc список [0119](0119-chat-slash-commands-intercom-surface.md)). |
| 303 | - Автогенерация `[[command.form.slash]]` из `melody_slug` / `tail_signature`. |
| 304 | - Короткие пути `/els`, `/wai` (коллизии, дубль `c:`). |
| 305 | - Универсальный четырёхуровневый парсер `/a b c d` без curated правил. |
| 306 | - Параметрический slash **без** записи в TOML (нет «скрытых» команд). |
| 307 | |
| 308 | --- |
| 309 | |
| 310 | ## Отклонённые альтернативы |
| 311 | |
| 312 | 1. **Per-command binder в Runner** (`switch command_id`) — расходится с [0109](0109-declarative-parametric-melody-catalog-toml-and-code-binders.md); отвергнуто. |
| 313 | 2. **`/parametric els 5 10`** — плохой UX в Intercom, дублирует палитру; отвергнуто. |
| 314 | 3. **`/editor select 5`** (два уровня) — слабая группировка «line operations»; отвергнуто в пользу `/editor line select`. |
| 315 | 4. **`intent_catalog_schema_version = 2`** для раскладки TOML — путаница с **IML v2**; schema файла остаётся **1**. |
| 316 | |
| 317 | --- |
| 318 | |
| 319 | ## Критерии приёмки (Definition of Done) |
| 320 | |
| 321 | - [x] Все `melody_shape = parametric` в bundled TOML имеют хотя бы один `[[command.form.slash]]`. |
| 322 | - [x] Slash вызывает тот же `command_id` и те же args, что melody (тесты на `select`, `apply_edit`, `show_web_ai_portal_page`). |
| 323 | - [x] Ошибки binder отображаются в пузыре slash. |
| 324 | - [x] Autocomplete видит новые пути (`/portal`, `/editor line …`). |
| 325 | - [x] ADR + ссылка из [0119](0119-chat-slash-commands-intercom-surface.md) и [intent-melody-language-v1.md](../intent-melody-language-v1.md). |
| 326 | |
| 327 | --- |
| 328 | |
| 329 | ## История изменений |
| 330 | |
| 331 | | Дата | Изменение | |
| 332 | |------|-----------| |
| 333 | | 2026-05-17 | Proposed: slash-паритет для editor line (`/editor line select|delete`). | |
| 334 | | 2026-05-17 | Implemented: `SubAction`, line binder, TOML, тесты. | |
| 335 | | 2026-05-17 | **Фича «полная параметрика каталога»:** `ChatSlashParametricArgsBuilder`, `/portal open`, `TryGetParametricRootByCommandId`; ADR расширен как норматив фичи. | |
| 336 | |