Forge
markdowndeeb25a2
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
331. **Любая** команда с `melody_shape = parametric` в `intent-catalog.toml` может иметь slash-форму с **тем же** `command_id` и **теми же** JSON-args, что melody и MCP.
342. Разбор хвоста slash (**ArgsTail**) диспетчеризуется по **`wire_class`** из каталога — не по отдельному `switch (command_id)` в Runner.
353. Slash-пути (**`/editor line select`**, **`/portal open`**) задаются **вручную** в TOML (`help`, `group`) для autocomplete; пути **не** выводятся автоматически из `tail_signature`.
364. 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
741. **Разрыв входов:** оператор в Intercom не может выделить/удалить строки или открыть портал по URL без палитры или `c:`.
752. **Дублирование binders:** без ADR каждая slash-команда тянет свой парсер колонок/URL в `ChatPanelViewModel`.
763. **Парсер [0119 §4](0119-chat-slash-commands-intercom-surface.md#adr0119-p4):** путь `/editor line select` — **три** токена namespace, не два.
774. **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
100ChatInput (строка с /)
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
179slashPath = "/" + 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]]
193command_id = "select"
194melody_slug = "els"
195melody_shape = "parametric"
196# … melody_* …
197
198[[command.form.slash]]
199path = "/editor line select"
200help = "Выделить строки …"
201group = "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
2161. **TOML melody:** `melody_shape = parametric`, `tail_signature`, `wire_class`, согласованный с `[[tail_wire_class]]`.
2172. **Binder в коде** ([0109](0109-declarative-parametric-melody-catalog-toml-and-code-binders.md)): melody-путь (`ParametricIntentMelody` / dedicated builder).
2183. **Ветка в `ChatSlashParametricArgsBuilder`** для этого `wire_class` (если новый — ADR-апдейт).
2194. **`[[command.form.slash]]`** с читаемым `path` + `help`.
2205. **Парсер:** при нестандартной форме пути — расширение `ChatSlashCommandParser` (как SubAction для editor).
2216. **Тесты:** `ChatSlashParametricTests` + при необходимости melody tests.
2227. **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
260flowchart 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
3121. **Per-command binder в Runner** (`switch command_id`) — расходится с [0109](0109-declarative-parametric-melody-catalog-toml-and-code-binders.md); отвергнуто.
3132. **`/parametric els 5 10`** — плохой UX в Intercom, дублирует палитру; отвергнуто.
3143. **`/editor select 5`** (два уровня) — слабая группировка «line operations»; отвергнуто в пользу `/editor line select`.
3154. **`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
View only · write via MCP/CIDE