| 1 | # ADR 0150: Slash line — канонический путь и единый резолв (autocomplete · Enter · execute) |
| 2 | |
| 3 | **Статус:** Accepted · Implemented |
| 4 | **Дата:** 2026-05-26 |
| 5 | |
| 6 | > **Дополнение (2026-05-28):** источник идентичности пути — **только каталог**; parser shape и `IntercomSlashPathBuilder` удалены — [0153](0153-slash-catalog-only-resolution.md). |
| 7 | |
| 8 | ## Связанные ADR |
| 9 | |
| 10 | | ADR | Роль | |
| 11 | |-----|------| |
| 12 | | [0119](0119-chat-slash-commands-intercom-surface.md) | Грамматика slash, autocomplete, local execution | |
| 13 | | [0136](0136-intercom-feed-gutter-and-slash-namespace.md) | `/intercom <group> <verb>` — пути глубже двух уровней | |
| 14 | | [0125](0125-slash-workspace-file-commands-and-dynamic-completion.md) | Динамический completion после пути | |
| 15 | | [0140](0140-tci-slash-status-glyphs-and-args-counter.md) | Счётчик args в TCI | |
| 16 | |
| 17 | ## Проблема |
| 18 | |
| 19 | Один ввод в composer описывался **тремя моделями**: |
| 20 | |
| 21 | 1. **Каталог** — полный путь (`/intercom server start`). |
| 22 | 2. **Парсер** — `head` + `action` + `ArgsTail` (вложенный `start` оказывается в хвосте). |
| 23 | 3. **Autocomplete** — по сегментам; **Enter** — отдельная эвристика popup / `auto_run`. |
| 24 | |
| 25 | Симптомы повторялись: Enter коммитит подсказку вместо send; аргумент **склеивается** с командой без пробела; опциональный `base_url` не отличался от «команда завершена». |
| 26 | |
| 27 | ## Решение |
| 28 | |
| 29 | ### 1. Единый резолв строки |
| 30 | |
| 31 | `SlashLineResolver` по тексту slash-строки возвращает: |
| 32 | |
| 33 | | Поле | Смысл | |
| 34 | |------|--------| |
| 35 | | `CanonicalPath` | Путь из `intent-catalog` (`/intercom server start`) | |
| 36 | | `ArgTail` | Текст **после** пути (URL, заголовок темы, …) | |
| 37 | | `ArgTailKind` | `none` \| `optional` \| `required` | |
| 38 | | Флаги UI | exact path, пробел после пути, есть ли текст хвоста | |
| 39 | |
| 40 | **Autocomplete**, **Enter (send)**, **Runner (args)** читают только этот резолв, не дублируют эвристики. |
| 41 | |
| 42 | ### 2. Явный `arg_tail` в каталоге |
| 43 | |
| 44 | В `[[command.form.slash]]`: |
| 45 | |
| 46 | ```toml |
| 47 | arg_tail = "none" # по умолчанию после полного пути — send на Enter |
| 48 | arg_tail = "optional" # после commit — пробел; можно дописать args |
| 49 | arg_tail = "required" # без хвоста — не runnable; dynamic completion |
| 50 | ``` |
| 51 | |
| 52 | Legacy `requires_arg_tail = true|false` остаётся; при отсутствии `arg_tail` маппится в `required` / `none`. Эвристики (completion, дочерние пути, `open`) — только если `arg_tail` не задан. |
| 53 | |
| 54 | ### 3. Правила UI (норматив) |
| 55 | |
| 56 | | `arg_tail` | Popup сегментов после полного пути | Insert после Tab/Enter на сегменте | Enter → send | |
| 57 | |------------|-----------------------------------|-------------------------------------|--------------| |
| 58 | | `none` | скрыть | без лишнего пробела | да | |
| 59 | | `optional` | скрыть; режим ввода хвоста | **пробел** после пути | да (хвост может быть пустым) | |
| 60 | | `required` | до хвоста — по completion / сегментам | пробел если есть следующий сегмент каталога | только если `ArgTail` непустой | |
| 61 | |
| 62 | Резолв строит канонический путь по токенам каталога; runner берёт `ArgTail` из резолва. *(Историческая v1 оставляла `ChatSlashCommandParser.TryParse` — снято в [0153](0153-slash-catalog-only-resolution.md).)* |
| 63 | |
| 64 | ## Последствия |
| 65 | |
| 66 | - Новые slash с опциональным хвостом: **`arg_tail = "optional"`** в TOML, без кода в `IntercomSlashArgsTail`. |
| 67 | - Тест-матрица: `SlashLineResolverTests` (путь × ввод × runnable × popup). |
| 68 | - ~~`IntercomSlashPathBuilder` / `TryResolve(parse)`~~ — удалено ([0153](0153-slash-catalog-only-resolution.md)). |
| 69 | |
| 70 | ## Non-goals |
| 71 | |
| 72 | - Переписать парсер на три уровня `SubAction` для всех namespace. |
| 73 | - Автогенерация `arg_tail` из JSON schema `IdeCommands` без явной записи в TOML (допустимо позже как hint, не как источник правды). |
| 74 | |
| 75 | ## Альтернативы (отклонены) |
| 76 | |
| 77 | | Вариант | Почему нет | |
| 78 | |---------|------------| |
| 79 | | Только точечные фиксы в autocomplete | Регрессии на следующей команде | |
| 80 | | Всегда trailing space после commit | Ломает `none` и auto_run | |
| 81 | | Один `requires_arg_tail: bool` | Не различает optional и required | |
| 82 | |
| 83 | --- |
| 84 | |
| 85 | ## Статус реализации (2026-05-27) |
| 86 | |
| 87 | | Элемент | Артефакт | |
| 88 | |---------|----------| |
| 89 | | `SlashLineResolver` | `Features/Chat/SlashLineResolver.cs` | |
| 90 | | Autocomplete / Enter | `ChatSlashAutocomplete`, `ChatPanelViewModel.IntercomComposerKeys` | |
| 91 | | Execute args | `ChatSlashCommandRunner` → `ArgTail` из резолва | |
| 92 | | `arg_tail` в каталоге | `IntentMelody/intent-catalog.toml` — без `requires_arg_tail` | |
| 93 | | Тесты | `SlashLineResolverTests`, `SlashRouteCatalogIndexTests`, `IntentCatalogArgTailPolicyTests` | |
| 94 | |
| 95 | Legacy `requires_arg_tail` в TOML не используется; `SlashRouteCatalogIndex` оставляет эвристики только для оверлеев без `arg_tail`. |
| 96 | |