| 1 | # ADR 0153: Slash — только каталог (удаление parser shape и legacy loader) |
| 2 | |
| 3 | **Статус:** Accepted · Implemented |
| 4 | **Дата:** 2026-05-28 |
| 5 | |
| 6 | ## Резюме |
| 7 | |
| 8 | Идентичность слэш-команды (**какой путь** `/map type file`, `/intercom topic create …`) задаётся **только** `IntentMelody/intent-catalog.toml` и build-time trie `SlashRouteCatalogPathsGenerated`. Параллельный разбор `head` / `action` / `sub_action` (`ChatSlashParsePipeline`, `IntercomSlashPathBuilder`, `TryResolve(parse)`) **удалён**. Autocomplete, preview, Enter и runner используют один `SlashLineResolver` (ADR 0150). |
| 9 | |
| 10 | ## Связанные ADR |
| 11 | |
| 12 | | ADR | Роль | |
| 13 | |-----|------| |
| 14 | | [0119](0119-chat-slash-commands-intercom-surface.md) | Slash в composer, local execution, `command_id` | |
| 15 | | [0136](0136-intercom-feed-gutter-and-slash-namespace.md) | Канон `/intercom …`; отказ от top-level `/topic` | |
| 16 | | [0150](0150-slash-line-canonical-resolution.md) | `SlashLineResolver`, `arg_tail`, единый резолв строки — **дополняется** этим ADR (источник пути) | |
| 17 | | [0124](0124-slash-parametric-editor-line-commands.md) | Параметрические хвосты (`wire_class`, сегменты) — **без** отдельной стадии парсера пути | |
| 18 | | [0125](0125-slash-workspace-file-commands-and-dynamic-completion.md) | Динамический completion после канонического пути | |
| 19 | | [0154](0154-slash-catalog-domain-object-intent.md) | Семантика `domain` · `object` · `intent`, elision, v1 scope; миграция TOML/loader — deferred | |
| 20 | |
| 21 | ## Проблема |
| 22 | |
| 23 | После [0150](0150-slash-line-canonical-resolution.md) в коде оставались **два контура**: |
| 24 | |
| 25 | | Контур | Как определял путь | |
| 26 | |--------|-------------------| |
| 27 | | Каталог + `SlashLineResolver` | Longest-prefix по `intent-catalog` | |
| 28 | | Legacy parser + `TryResolve(parse)` | `Head` / `Action` / `SubAction` + `IntercomSlashPathBuilder` | |
| 29 | |
| 30 | Они расходились. Пример регрессии: ввод `/map type file` резолвился как путь `/map type`, а `file` уходил в хвост args — команда не переключала уровень карты. |
| 31 | |
| 32 | Дополнительный шум: legacy TOML (`[[slash_route]]`, `[[melody_root]]`), `BuildFromLegacyTables`, ~12 стадий `SlashParse/*`, дублирующие тесты на shape парсера. |
| 33 | |
| 34 | ## Решение |
| 35 | |
| 36 | ### 1. Единственный источник пути |
| 37 | |
| 38 | ```text |
| 39 | intent-catalog.toml |
| 40 | → IntentCatalogLoader (только [[command]] / [[command.form.slash]]) |
| 41 | → IntentSlashCatalog + SlashRouteCatalogIndex (runtime) |
| 42 | → ProtocolDocGen → SlashRouteCatalogPathsGenerated.g.cs (build) |
| 43 | → SlashLineResolver.TryResolveSlashLine |
| 44 | ``` |
| 45 | |
| 46 | Новая команда: **строка `path` в TOML** (+ при необходимости `arg_tail`, handlers). Не новая стадия в C#-парсере. |
| 47 | |
| 48 | ### 2. API резолва (норматив) |
| 49 | |
| 50 | | Метод | Назначение | |
| 51 | |-------|------------| |
| 52 | | `SlashLineResolver.TryResolveSlashLine` | Канонический путь + сырой `ArgTail` + `ArgTailKind` + флаги UI | |
| 53 | | `ChatSlashCommandCatalog.TryResolveInput` | Descriptor для execute/preview: **путь из резолва**, хвост нормализован; **не** требует непустой хвост при `required` (ошибку даёт runner / intercom handler) | |
| 54 | | `ChatSlashCommandCatalog.TryResolveCanonical` | Lookup с политикой `arg_tail` (`required` → хвост обязателен) — runnable, часть autocomplete | |
| 55 | | `ChatSlashCommandParser` | Только `IsSlashLine` и `ShouldAutoExecuteAfterAutocompleteCommit` — **без** `TryParse` | |
| 56 | |
| 57 | ### 3. Удалено из продукта |
| 58 | |
| 59 | | Артефакт | Причина | |
| 60 | |----------|---------| |
| 61 | | `Features/Chat/SlashParse/*` | Дублировал каталог спец-кейсами | |
| 62 | | `IntercomSlashPathBuilder` | Собирал путь из parser shape | |
| 63 | | `ChatSlashCommandCatalog.TryResolve(parse)` | Второй вход в каталог | |
| 64 | | `ChatSlashCommandParseResult`, `ChatSlashCommandShape` | Модель больше не нужна | |
| 65 | | `IntentCatalogLoader.BuildFromLegacyTables`, `LoadLegacySlashRoutes` | Каталог только command-first | |
| 66 | | `requires_arg_tail` в loader/codegen | Заменено явным `arg_tail` в TOML | |
| 67 | |
| 68 | ### 4. Алиасы пути — только в каталоге |
| 69 | |
| 70 | Синонимы вроде `/intercom anchor peek` ↔ `/anchor peek` — **отдельные** `[[command.form.slash]]` с тем же `intercom_handler`, не glue в парсере. |
| 71 | |
| 72 | Спец-формы без пробела (`/anchor peekabcd1234`) **не** поддерживаются: канон `/anchor peek <id>`. |
| 73 | |
| 74 | ### 5. Старые top-level слэши (ADR 0136) |
| 75 | |
| 76 | `/topic list`, `/overview`, `/attach selection` и т.п. **не** в каталоге → `TryResolveInput` = false, preview/runner: «нет такой команды». Отдельные `RejectReason` с текстом миграции **не** восстанавливаем (были частью `SlashParseLegacyRejectStage`). |
| 77 | |
| 78 | ## Последствия |
| 79 | |
| 80 | - **Плюс:** один контур, предсказуемый longest-prefix, проще тесты (`TryResolveInput` + `SlashLineResolver`). |
| 81 | - **Плюс:** codegen trie синхронизирован с bundled catalog на каждой сборке. |
| 82 | - **Минус:** оверлей TOML без `[[command]]` больше не загружается — нужен command-first файл. |
| 83 | - **Минус:** пользователи старых коротких путей должны перейти на `/intercom …` или получить явный alias в TOML. |
| 84 | |
| 85 | ## Non-goals |
| 86 | |
| 87 | - `System.CommandLine` / полноценный CLI-парсер для всей строки composer. |
| 88 | - Автогенерация `arg_tail` из JSON-schema `IdeCommands` без записи в TOML. |
| 89 | - Восстановление parser shape «на всякий случай» для parametric — хвост режет `SlashLineResolver`; валидация сегментов остаётся в `ParametricSegmentListParser` / binders. |
| 90 | |
| 91 | ## Альтернативы (отклонены) |
| 92 | |
| 93 | | Вариант | Почему нет | |
| 94 | |---------|------------| |
| 95 | | Оставить `TryResolve(parse)` как fallback | Снова два источника правды и расхождения | |
| 96 | | Только починить `/map type file` в одной стадии парсера | Следующая команда снова ломается | |
| 97 | | Combinator-библиотека для всех slash | Сложность без выигрыша при полном каталоге путей | |
| 98 | |
| 99 | ## Дополнение к ADR 0150 |
| 100 | |
| 101 | Секции 0150 про «парсер **не меняем** в v1» и «`IntercomSlashPathBuilder` остаётся для `TryResolve(parse)`» **отменены** этим ADR. Актуальны: `SlashLineResolver`, `arg_tail`, правила UI по `ArgTailKind`. |
| 102 | |
| 103 | ## Статус реализации |
| 104 | |
| 105 | | Элемент | Артефакт | |
| 106 | |---------|----------| |
| 107 | | Codegen trie | `tools/CascadeIDE.ProtocolDocGen/IntentCatalogSlashPathCollector.cs` → `Services/Generated/SlashRouteCatalogPathsGenerated.g.cs` | |
| 108 | | Резолв | `SlashLineResolver.cs`, `ChatSlashCommandCatalog.cs` | |
| 109 | | Runner / preview / send | `ChatSlashCommandRunner.cs`, `SlashCommandPreviewRules.cs`, `ChatPanelViewModel.IntercomSend.cs` | |
| 110 | | Каталог | `IntentMelody/intent-catalog.toml` | |
| 111 | | Тесты | `ChatSlashCatalogTestSupport`, `SlashLineResolverTests`, обновлённые `ChatSlash*` / `IntercomAnchor*` | |
| 112 | |