| 1 | # ADR 0119: Slash commands in chat — unified command line (Intercom + IDE) |
| 2 | |
| 3 | **Status:** Accepted · Implemented |
| 4 | **Date:** 2026-05-17 |
| 5 | **Updated:** 2026-05-17 — extended to IDE verbs (`/build run`, `/test run`, `/debug launch`); autocomplete required. [§ History](#adr0119-history) |
| 6 | |
| 7 | ## Related ADRs |
| 8 | |
| 9 | | ADR | Role | |
| 10 | |-----|------| |
| 11 | | [0080](0080-intercom-naming-and-multi-party-channel-model.md) | Chat as **Intercom** — central channel, not “bot window” | |
| 12 | | [0072](0072-chat-topic-cards-intent-melody-keyboard-contract.md) | Topic cards, overview/detail, **intent-first** navigation (Melody/Chords) | |
| 13 | | [0096](0096-intercom-topic-card-summary-and-product-spine.md) | Card content, spine, carry-forward into thread | |
| 14 | | [0013](0013-command-surface-and-discoverability.md) | Palette and discoverability — slashes **complement**, do not replace | |
| 15 | | [0030](0030-command-ids-hotkeys-and-ui-registry-layers.md) | Canonical `command_id`, registry, MCP parity | |
| 16 | | [0060](0060-keyboard-chord-stack-fms-tactical-strategic.md) | CascadeChord, Command Melody `c:` — **orthogonal** input | |
| 17 | | [0008](0008-mcp-contracts-and-testable-infrastructure.md) | Agent parity: same effects via `ide_execute_command` | |
| 18 | | [0048](0048-cursor-acp-chat-ide-parity-and-mcp-tool-surface.md) | What goes to the agent vs local IDE action | |
| 19 | | [0057](0057-chat-surface-pipeline-adoption.md) | Snapshot/layout after VM state change | |
| 20 | | [0116](0116-intercom-session-tree-and-agent-message-steering.md) | Session tree, steer — do not mix with slash parser | |
| 21 | | [0002](0002-debug-human-agent-parity.md) | `/build`, `/test`, `/debug` — same `command_id` as agent via MCP | |
| 22 | | [0018](0018-ide-commands-canonical-xml-documentation.md) | Canonical `IdeCommands` names for catalog projection | |
| 23 | | [0120](0120-primary-work-surface-intercom-or-editor.md) | Intercom in Forward — slashes as primary session CLI | |
| 24 | |
| 25 | ### Outside ADR |
| 26 | |
| 27 | | Document | Role | |
| 28 | |----------|------| |
| 29 | | [MCP-PROTOCOL.md](../../MCP-PROTOCOL.md) | `ide_execute_command`, `send_chat`, chat_* MCP | |
| 30 | | [intent-melody-language-v1.md](../../intent-melody-language-v1.md) | `c:` grammar — **not** `/` grammar in chat | |
| 31 | |
| 32 | ## Summary |
| 33 | |
| 34 | - **`ChatInput`** is an **alternative IDE command line**: stay in chat for Intercom *and* frequent actions (`/build run`, `/test run`, `/debug launch`, `/card …`). |
| 35 | - Slash → **`command_id`** ([0030](0030-command-ids-hotkeys-and-ui-registry-layers.md)); catalog is a **projection** of the registry onto **readable** slash paths (`/build run`, `/overview`), not a second executor in the VM. |
| 36 | - **Discoverability via autocomplete** (namespace → action hierarchy, hints, `/help`), **not** via short mnemonics like `/br` (compressed forms stay in **`c:`** Melody and chords, [0060](0060-keyboard-chord-stack-fms-tactical-strategic.md)). |
| 37 | - **Autocomplete is required** — extended catalog is not accepted without it. |
| 38 | - Palette, Melody `c:`, and chords remain; slash is a **peer input** for those already in the message field ([0013](0013-command-surface-and-discoverability.md)). |
| 39 | - Rollout **in phases**: Intercom verbs → IDE namespaces → extension from palette. |
| 40 | |
| 41 | --- |
| 42 | |
| 43 | ## Context |
| 44 | |
| 45 | Intercom in CIDE ([0080](0080-intercom-naming-and-multi-party-channel-model.md)) is increasingly the **central surface**: agent dialogue, topic catalog ([0072](0072-chat-topic-cards-intent-melody-keyboard-contract.md)), product spine ([0096](0096-intercom-topic-card-summary-and-product-spine.md)), clarifications ([0031](0031-agent-chat-clarification-batches-and-threading.md)). |
| 46 | |
| 47 | Power users already have: |
| 48 | |
| 49 | - **palette** and fuzzy search ([0013](0013-command-surface-and-discoverability.md)); |
| 50 | - **global hotkeys** and **CascadeChord** ([0060](0060-keyboard-chord-stack-fms-tactical-strategic.md)); |
| 51 | - **Command Melody** `c:` in the palette ([0112](0112-command-palette-query-modes-strategy.md)); |
| 52 | - **chat navigation intents** with MCP parity (`chat_show_thread_overview`, `chat_open_selected_thread`, … — [0072 §4](0072-chat-topic-cards-intent-melody-keyboard-contract.md)). |
| 53 | |
| 54 | That is **not enough** when the operator **is already typing in the message field** and expects a Slack/Discord-style model or **CLI inside chat**: |
| 55 | |
| 56 | - Intercom: `/card New topic`, `/overview`, `/spine focus=…`; |
| 57 | - IDE: `/build run`, `/test run`, `/debug launch` — **without** switching to palette, toolbar, or mandatory chords. |
| 58 | |
| 59 | Product hypothesis: if Intercom is the **central surface**, chat becomes the **single session control point**, not only a channel to the agent. |
| 60 | |
| 61 | --- |
| 62 | |
| 63 | ## Problem |
| 64 | |
| 65 | 1. **Discoverability gap:** chat commands exist in the registry and MCP but are **not visible** in the input context where the main thought stream lives. |
| 66 | 2. **Duplication risk:** ad-hoc `/card` parsing in `SendChatAsync` bypasses the intent layer [0072 §5](0072-chat-topic-cards-intent-melody-keyboard-contract.md) and diverges from pointer/MCP. |
| 67 | 3. **Mixing with the agent:** without a “slash = local” rule, `/export` may go to the LLM as plain text. |
| 68 | 4. **Prefix conflict:** `c:` is reserved for palette/Melody ([0112](0112-command-palette-query-modes-strategy.md)); **`/`** is a separate space **only in ChatInput**. |
| 69 | |
| 70 | --- |
| 71 | |
| 72 | ## Decision |
| 73 | |
| 74 | <a id="adr0119-p1"></a> |
| 75 | |
| 76 | ### 1. ChatInput as **unified command line** (slash prefix) |
| 77 | |
| 78 | - A line starting with **`/`** (after trim) is a **slash command** (one- or two-level; see [§4](#adr0119-p4)). |
| 79 | - Parse **on send** (Enter) and **incrementally** for autocomplete ([§6](#adr0119-p6)) — autocomplete is **not optional** for the accepted catalog size. |
| 80 | - Do **not** intercept `/` in the middle of a normal message; ordinary agent dialogue has **no** leading slash. |
| 81 | |
| 82 | <a id="adr0119-p2"></a> |
| 83 | |
| 84 | ### 2. Intent-first: slash → `command_id` → VM → snapshot |
| 85 | |
| 86 | **Invariant** (extension of [0072 §5](0072-chat-topic-cards-intent-melody-keyboard-contract.md)): |
| 87 | |
| 88 | ```text |
| 89 | ChatInput (/verb args…) → ChatSlashCommandParser → command_id (+ args) |
| 90 | → IdeMcpCommandExecutor / ChatPanel handlers (as palette/MCP) |
| 91 | → ChatPanelViewModel state → ChatSurfaceCompositor → Skia render |
| 92 | ``` |
| 93 | |
| 94 | - Slash commands do **not** change Skia directly or read hit-target geometry. |
| 95 | - Pointer, Melody, Chords, palette, MCP, and slash **converge** on the same `command_id` where possible. |
| 96 | |
| 97 | <a id="adr0119-p3"></a> |
| 98 | |
| 99 | ### 3. Execution modes |
| 100 | |
| 101 | | Mode | Behavior | Example | |
| 102 | |------|----------|---------| |
| 103 | | **Local** | Message **not** sent to agent; `command_id` runs; input cleared (or one-line status). | `/overview`, `/export` | |
| 104 | | **Local + echo** | Local + short system line in feed (optional, v1+). | `/card` with “topic created …” | |
| 105 | | **Reject** | Unknown verb — UI error, **no** agent send. | `/foo` | |
| 106 | | **Pass-through** *(forbidden by default)* | Send text to agent as-is. | **Do not** use for unrecognized `/` | |
| 107 | |
| 108 | **v1 rule:** unrecognized line with leading `/` → **Reject** with hint “unknown command, Tab — list”. |
| 109 | |
| 110 | <a id="adr0119-p4"></a> |
| 111 | |
| 112 | ### 4. v1 grammar |
| 113 | |
| 114 | **Two levels** (like “namespace / action”): |
| 115 | |
| 116 | ```ebnf |
| 117 | slash_line = "/" head (WS tail)? WS? ; |
| 118 | head = flat_verb | namespace ; |
| 119 | flat_verb = letter { letter | digit | "-" } ; (* overview, card, help, export *) |
| 120 | namespace = letter { letter | digit } ; (* build, test, debug, git, chat *) |
| 121 | tail = action (WS arg_token)* | arg_tail ; (* run | launch | … OR rest for flat *) |
| 122 | action = letter { letter | digit | "-" } ; |
| 123 | arg_tail = { arg_token } ; (* /card Topic name — all after head *) |
| 124 | arg_token = quoted_string | bare_token ; |
| 125 | ``` |
| 126 | |
| 127 | **Examples:** |
| 128 | |
| 129 | | Input | Parse | |
| 130 | |-------|--------| |
| 131 | | `/overview` | flat: `overview` | |
| 132 | | `/card ADR 0119` | flat: `card`, args: `ADR 0119` | |
| 133 | | `/build run` | namespace: `build`, action: `run` | |
| 134 | | `/test run` | namespace: `test`, action: `run` | |
| 135 | | `/debug launch` | namespace: `debug`, action: `launch` | |
| 136 | |
| 137 | - **Case:** case-insensitive. |
| 138 | - **Named args** (`configuration=Release`) — v2; v1 uses positional tail where needed. |
| 139 | |
| 140 | <a id="adr0119-p5"></a> |
| 141 | |
| 142 | ### 5. Catalog: projection onto `command_id`, not a second registry |
| 143 | |
| 144 | <a id="adr0119-p5a"></a> |
| 145 | |
| 146 | #### 5a. Source of truth |
| 147 | |
| 148 | - **Execution** only through existing `ide_execute_command` / `IdeMcpCommandExecutor` ([0030](0030-command-ids-hotkeys-and-ui-registry-layers.md), [0008](0008-mcp-contracts-and-testable-infrastructure.md)). |
| 149 | - **Slash catalog** (`ChatSlashCommandCatalog`) is a **mapping** (slash path → `command_id` + arg template), built from: |
| 150 | 1. **Curated** table in code (v1); |
| 151 | 2. v2+ — **projection** of a subset of `IdeCommandPaletteCatalog` / `IdeCommands` metadata (palette title need not match slash; slash path is explicit). |
| 152 | - **Not Melody:** the catalog has **no** separate “2–3 letter” entries (`/br`, `/tr`) as shortcuts to `namespace action` — the operator picks **`/build` → `run`** from autocomplete or types the full form. |
| 153 | - **Forbidden:** duplicate `dotnet build` / test / debug logic in `ChatPanelViewModel`. |
| 154 | |
| 155 | <a id="adr0119-p5b"></a> |
| 156 | |
| 157 | #### 5b. Intercom (flat verbs) — phase A |
| 158 | |
| 159 | | Slash | `command_id` | Note | |
| 160 | |-------|----------------|------| |
| 161 | | `/overview` | `chat_show_thread_overview` | | |
| 162 | | `/open` | `chat_open_selected_thread` | | |
| 163 | | `/card <title>` | *new* or `fork_chat_thread` + title | product | |
| 164 | | `/spine …` | `chat_set_product_spine` | tail → focus / milestones | |
| 165 | | `/spine-toggle` | `chat_toggle_product_spine_in_agent_context` | | |
| 166 | | `/export` | `chat_export_readable` | | |
| 167 | | `/help` | local catalog | no MCP | |
| 168 | |
| 169 | <a id="adr0119-p5c"></a> |
| 170 | |
| 171 | #### 5c. IDE namespaces — phase B (without leaving chat) |
| 172 | |
| 173 | | Slash | `command_id` | Note | |
| 174 | |-------|----------------|------| |
| 175 | | `/build run` | `build` or `build_structured` | structured JSON to feed/panel — UI policy | |
| 176 | | `/build ui` | `build_solution_ui` | toolbar path, text to output | |
| 177 | | `/test run` | `run_tests` | | |
| 178 | | `/test affected` | `run_affected_tests` | optional `changed_paths` from git | |
| 179 | | `/debug launch` | `debug_launch` | target from launch profile / current | |
| 180 | | `/debug continue` | *debug_start_or_continue* (UI id) | add `IdeCommands` constant if missing | |
| 181 | | `/git status` | `git_status` | phase C when in registry | |
| 182 | |
| 183 | Further namespaces (`nav`, `index`, `palette`) — **as discoverability allows**, not “all of IdeCommands at once”. |
| 184 | |
| 185 | **Parity:** agent calls the same `build` / `run_tests` / `debug_launch` via MCP; operator uses `/build run` in chat. |
| 186 | |
| 187 | <a id="adr0119-p6"></a> |
| 188 | |
| 189 | ### 6. Discoverability: autocomplete and help (required) |
| 190 | |
| 191 | Without autocomplete, extension to `/build`, `/test`, … is **not accepted** — the operator **must not** memorize namespaces and actions and **must not** rely on compressed slash mnemonics (unlike `c:` in the palette). |
| 192 | |
| 193 | **UI behavior (v1 minimum):** |
| 194 | |
| 195 | | Input step | Popup shows | |
| 196 | |------------|-------------| |
| 197 | | `/` | top-level: flat verbs + namespaces (`build`, `test`, `debug`, `card`, …) | |
| 198 | | `/build ` | actions: `run`, `ui`, … + one-line description | |
| 199 | | `/build r` | prefix filter (`run`) | |
| 200 | | unknown prefix | “no matches” + link to `/help` | |
| 201 | |
| 202 | - **Tab** — complete token / select highlighted; **↑↓** — navigate; **Esc** — close popup without clearing line. |
| 203 | - Under each item — **short help** (from `IdeCommands` doc / curated catalog) and optional **hotkey** from TOML ([0030](0030-command-ids-hotkeys-and-ui-registry-layers.md)). |
| 204 | - **`/help`** and **`/help build`** — text/interactive list in feed or overlay (local). |
| 205 | - v1 source: `ChatSlashCommandCatalog` in code; v2 — TOML `chat-slash-aliases.toml` analogous to [0109](0109-declarative-parametric-melody-catalog-toml-and-code-binders.md). |
| 206 | |
| 207 | <a id="adr0119-p7"></a> |
| 208 | |
| 209 | ### 7. Relation to agent and spine ([0096](0096-intercom-topic-card-summary-and-product-spine.md)) |
| 210 | |
| 211 | - Slash commands are **local by default** — they do **not** expand the agent prompt. |
| 212 | - `/spine-toggle` and `/spine` change session metadata; including spine in agent context is **explicit** ([0096 §4](0096-intercom-topic-card-summary-and-product-spine.md#adr0096-p4)), not a side effect of any slash. |
| 213 | - **Carry-forward** into the thread remains a **normal message** or separate intent; slash need **not** generate agent text. |
| 214 | |
| 215 | <a id="adr0119-p8"></a> |
| 216 | |
| 217 | ### 8. Non-goals and boundaries |
| 218 | |
| 219 | **Non-goals:** |
| 220 | |
| 221 | - **Full replacement** of Command Palette: fuzzy search over *all* commands without namespace structure stays in the palette. |
| 222 | - Automatic **1:1** “every palette row = slash” without curated aliases and UX filter (too noisy). |
| 223 | - Slash commands in **other** fields (terminal, editor, palette) — only `ChatInput`. |
| 224 | - Plugins with arbitrary verbs **without** catalog / `command_id` entry. |
| 225 | - Pass-through of unrecognized `/…` to the agent. |
| 226 | - **Short slash aliases** (2–3 characters, “melody after `/”): `/br` instead of `/build run`, auto-generation from [0109](0109-declarative-parametric-melody-catalog-toml-and-code-binders.md) without a dedicated slash path — discoverability is **hierarchical autocomplete** and readable `namespace` / `action` / flat verbs only. |
| 227 | |
| 228 | **In scope (deliberately):** |
| 229 | |
| 230 | - **Alternative input** to the same IDE actions as palette/chords/MCP — including `/build run`, `/test run`, `/debug launch`. |
| 231 | - Operator **may stay in chat** for the frequent loop “ask agent → build → run tests → debug”. |
| 232 | |
| 233 | --- |
| 234 | |
| 235 | ## Input orthogonality (summary) |
| 236 | |
| 237 | | Input | Where | Prefix / form | |
| 238 | |-------|-------|----------------| |
| 239 | | Palette | overlay | fuzzy, `c:` Melody | |
| 240 | | Hotkeys / Chord | global | TOML → `command_id` | |
| 241 | | Chat Melody aliases | palette / intents | `ato`, `atb`, … ([0072](0072-chat-topic-cards-intent-melody-keyboard-contract.md)) | |
| 242 | | **Chat slash** | `ChatInput` | `/verb` or `/namespace action` (**this ADR**) | |
| 243 | | MCP | agent | `ide_execute_command` | |
| 244 | |
| 245 | --- |
| 246 | |
| 247 | ## Diagram |
| 248 | |
| 249 | ```mermaid |
| 250 | flowchart TD |
| 251 | input[ChatInput] |
| 252 | input -->|starts with /| parser[ChatSlashCommandParser] |
| 253 | input -->|normal text| agent[SendToAgent] |
| 254 | parser -->|known verb| cmd[command_id handler] |
| 255 | parser -->|unknown| err[Inline error + help] |
| 256 | cmd --> vm[ChatPanelViewModel] |
| 257 | vm --> snap[ChatSurfaceSnapshot] |
| 258 | snap --> skia[SkiaChatSurfaceControl] |
| 259 | palette[Palette Melody Chords MCP] --> cmd |
| 260 | ``` |
| 261 | |
| 262 | --- |
| 263 | |
| 264 | ## Implementation anchors (plan) |
| 265 | |
| 266 | | Component | Role | |
| 267 | |-----------|------| |
| 268 | | `Features/Chat/ChatSlashCommandCatalog.cs` *(new)* | verb → descriptor (`command_id`, help, arg hint) | |
| 269 | | `Features/Chat/ChatSlashCommandParser.cs` *(new)* | parse line, validate | |
| 270 | | [`ChatPanelViewModel`](../../Features/Chat/ChatPanelViewModel.cs) | call parser in `SendChatAsync` **before** `ApplyProductSpineToOutboundMessage` / ACP | |
| 271 | | [`IdeMcpCommandExecutor`](../../ViewModels/IdeMcpCommandExecutor.cs) | same `command_id` as MCP | |
| 272 | | [`ChatPanelView.axaml`](../../Views/ChatPanelView.axaml) | autocomplete popup (**required** before phase B) | |
| 273 | | `ChatSlashAutocompleteControl` *(new)* | hierarchical popup bound to `ChatInput` | |
| 274 | | [`IdeCommands`](../../Services/IdeCommands.SolutionWorkspace.cs) | new `command_id` only if uncovered (`chat_create_or_rename_topic`) | |
| 275 | |
| 276 | **Rollout order:** |
| 277 | |
| 278 | | Phase | Content | Done when | |
| 279 | |-------|---------|-----------| |
| 280 | | **A** | Parser (flat + namespace/action), Intercom catalog, local execution | `/overview`, `/export` do not go to agent | |
| 281 | | **A′** | **Autocomplete** for flat + namespace list | hints after `/` and `/build ` | |
| 282 | | **B** | IDE: `/build run`, `/test run`, `/debug launch` → `command_id` | parity with MCP `build` / `run_tests` / `debug_launch` | |
| 283 | | **C** | Extend catalog (`/git status`, `/nav`, palette projection) | by discoverability + autocomplete, not big-bang | |
| 284 | |
| 285 | Tests: parser unit tests; “slash local” integration; catalog help snapshots. |
| 286 | |
| 287 | --- |
| 288 | |
| 289 | ## Rejected alternatives |
| 290 | |
| 291 | 1. **Parse slashes in the palette** (`/card` in Command Palette) — mixes overlay and Intercom; rejected. |
| 292 | 2. **MCP-only commands without `command_id`** — breaks [0030](0030-command-ids-hotkeys-and-ui-registry-layers.md); rejected. |
| 293 | 3. **Send unrecognized `/` to the agent** — noise and leaks; rejected. |
| 294 | 4. **Short slashes like Melody** (`/br` = build run) — duplicates `c:` / chords, collisions and a second parser; Intercom discoverability is **autocomplete**, not mnemonics; rejected. |
| 295 | |
| 296 | --- |
| 297 | |
| 298 | ## Change history |
| 299 | |
| 300 | <a id="adr0119-history"></a> |
| 301 | |
| 302 | | Date | Change | |
| 303 | |------|--------| |
| 304 | | 2026-05-17 | Proposed: slash commands in ChatInput, v1 catalog, intent-first, non-goals. | |
| 305 | | 2026-05-17 | Extended: unified command line — IDE namespaces (`/build run`, `/test run`, `/debug launch`); autocomplete required; phases A–C. | |
| 306 | | 2026-05-17 | Clarified: slash discoverability is **autocomplete**; short aliases (`/br`) and “melody after `/`” are **non-goals** (compression — `c:` Melody). | |
| 307 | | 2026-05-17 | Accepted · Implemented: phases **A**, **A′**, **B** (`ChatSlashCommand*`, autocomplete, IDE namespaces); phase **C** — backlog. | |
| 308 | |