返回 CodeWhale
command-dispatch.md
根目录 / docs / architecture / command-dispatch.md
1 # Command Dispatch Architecture
2
3 **Target branch:** `main`
4 **Related EPIC:** [#2870](https://github.com/Hmbown/CodeWhale/issues/2870)
5 **Related issue:** [#2791](https://github.com/Hmbown/CodeWhale/issues/2791)
6 **EPIC-002 (Command Single Responsibility Extraction):** Layer 4.x (FEAT-006 through FEAT-008)
7
8 This document records the command-dispatch ownership model after the
9 command-boundary replay landed on `main`, updated through EPIC-002 (command
10 single responsibility extraction). It reflects the final layered ownership:
11 top-level group registration, group-owned command registration, and
12 command-level ownership of metadata and behavior. It is the public reference for the
13 module boundaries, dispatch precedence, and permanent exceptions that remain
14 after the command-boundary refactor.
15
16 ## Dispatch Flow
17
18 `commands::execute()` owns the slash-command dispatch gate. The order is
19 intentional:
20
21 | Step | Source | Behavior |
22 |------|--------|----------|
23 | 0 | `$skill` compatibility | `$name` is resolved as `/skill name` before slash parsing. |
24 | 1 | User commands | `user_registry::try_dispatch()` checks workspace and global markdown commands first, so user commands can shadow built-ins. |
25 | 2 | Permanent mode compatibility aliases | `/jihua` and `/zidong` route through config mode dispatch so each selects its fixed legacy mode. They remain registered aliases for discovery, but bypass normal `/mode` execution. |
26 | 3 | Built-in registry | `CommandRegistry` resolves group-owned built-in commands by canonical name or alias. |
27 | 4 | Legacy migration hints | Retired commands such as `/set` and `/deepseek` return targeted replacement guidance. |
28 | 5 | Skills fallback | If no command matches, a skill with the same name may run before unknown-command suggestions are shown. |
29
30 ## Module Boundaries
31
32 | Module | Responsibility |
33 |--------|----------------|
34 | `crates/tui/src/commands/mod.rs` | Central dispatch gate, registry initialization, public command lookup helpers, and unknown-command suggestions. |
35 | `crates/tui/src/commands/traits.rs` | Built-in command metadata, trait-backed command objects, command groups, and registry lookup. |
36 | `crates/tui/src/commands/groups/` | Group-owned built-in command areas. Each group owns its command metadata and handlers. |
37 | `crates/tui/src/commands/user_registry.rs` | User-command registry boundary: markdown metadata, aliases, hidden entries, validation errors, dispatch state resets, and shadowing behavior. |
38 | `crates/tui/src/commands/user_commands.rs` | Lower-level file scanning, frontmatter parsing, allowed-tools parsing, and template substitution used by the registry. |
39 | `crates/tui/src/tui/command_palette.rs` | Palette entries for built-ins and visible user commands, with user commands shadowing built-ins. |
40 | `crates/tui/src/tui/widgets/mod.rs` | Slash completion, user-command metadata display, and alias-shadowing behavior. |
41
42 ## Built-In Command Groups
43
44 | Group | Scope |
45 |-------|-------|
46 | `core` | Help, model/provider selection, queue, hooks, subagents, links, feedback, voice, and core navigation. |
47 | `config` | Config, settings, status surfaces, mode, theme, trust, logout, and related settings commands. |
48 | `debug` | Token/cost introspection, cache, system/context, diff/edit, undo, and retry. |
49 | `memory` | Persistent memory and notes. |
50 | `plugins` | Read-only bundle discovery/validation plus explicit trust, enable, disable, revoke, and reload lifecycle commands; legacy executable tools remain separate. |
51 | `project` | Project initialization, sharing, LSP, and goal/hunt commands. |
52 | `session` | Rename, save, fork/new/load sessions, compaction, purge, relay, and export. |
53 | `skills` | Skills Manager (`/skills`), text inspect/remote/sync paths, activation (`/skill`), and managed install/update/uninstall/trust. |
54 | `utility` | Attachments, tasks/jobs, MCP, and network. |
55
56 ## User Commands
57
58 User commands are markdown files loaded from these locations in precedence
59 order:
60
61 1. `<workspace>/.codewhale/commands/`
62 2. `<workspace>/.deepseek/commands/`
63 3. `<workspace>/.claude/commands/`
64 4. `<workspace>/.cursor/commands/`
65 5. `~/.codewhale/commands/`
66 6. `~/.deepseek/commands/`
67
68 Supported frontmatter fields:
69
70 | Field | Meaning |
71 |-------|---------|
72 | `name` | Canonical slash-command name. It is normalized without a leading slash and replaces the filename-derived default. |
73 | `description` | Work objective and UI description. |
74 | `usage` | Preferred user-facing invocation syntax shown in the palette and slash completion. |
75 | `arguments` | Argument synopsis and a signal that selection should leave the composer open for input. It does not impose runtime validation. |
76 | `argument-hint` | Backward-compatible palette/completion hint for expected arguments. It remains the display fallback when `usage` is absent. |
77 | `allowed-tools` | Restricts command execution tools. An explicit empty value blocks all tools. |
78 | `pausable` | Marks the command as pause/resume capable. |
79 | `alias` / `aliases` | Additional user-command names that can shadow built-in aliases. |
80 | `hidden` | Hides the command from palette/completion while allowing direct dispatch. |
81
82 The canonical name defaults to the normalized markdown filename. A valid
83 frontmatter `name` replaces that default; the filename is not retained as an
84 implicit alias, so a renamed command must list the old filename under `alias`
85 or `aliases` if both spellings should dispatch. A configured name may include
86 one leading slash for readability, but after normalization it must be one
87 non-empty slash-command token with no whitespace or embedded `/`. An invalid
88 configured name is a recoverable error attached to the filename-derived
89 command, preventing silent fallthrough to a built-in.
90
91 Source precedence is resolved before frontmatter naming: a higher-precedence
92 directory wins when the same filename exists in more than one location. Files
93 inside each directory are ordered by normalized filename. If distinct files
94 then resolve to the same frontmatter `name`, the first file in that stable
95 directory-and-filename order wins and the losing file records a recoverable
96 load error. Aliases cannot replace any canonical user-command name; duplicate
97 aliases also use first-wins order. Errors on a losing duplicate never poison a
98 valid winning definition.
99
100 Presentation metadata has an explicit fallback order: non-empty `usage`, then
101 non-empty legacy `argument-hint`, then non-empty `arguments`. `arguments` and
102 `argument-hint` also cause palette/menu selection to append a space; `usage`
103 does so when it describes more than the bare command name. These fields do not
104 parse, require, or reject invocation arguments. Runtime expansion remains
105 backwards-compatible: `$ARGUMENTS` receives the complete argument tail and
106 `$1`, `$2`, and so on receive whitespace-separated positional values.
107
108 Malformed files remain registered under their resolved name with a
109 dispatch-time error, so they cannot silently fall through to a built-in. Their
110 errors are isolated per file: valid siblings still load, appear, and dispatch.
111 Hidden commands participate in shadowing and remain directly dispatchable, but
112 are removed from palette and slash-completion discovery.
113
114 Explicit `/help <name>` topics and unknown-command typo suggestions resolve
115 through the same user-command precedence as execution. When a user command
116 owns a built-in name or alias, help shows the user command's metadata and typo
117 suggestions point to its canonical name rather than the shadowed built-in.
118
119 Dispatch through `user_registry` resets stale command state before sending the
120 new command body: hunt objective fields, token/time counters, continuation
121 count, allowed tools, pause state, todos, and plan state.
122
123 ## Permanent Exceptions
124
125 | Exception | Rationale |
126 |-----------|-----------|
127 | `/jihua`, `/zidong` | Backward-compatible mode aliases that predate the group-owned registry. They route through config mode dispatch to preserve their fixed mode selection. |
128 | `/set` and `/deepseek` migration hints | Retired commands kept only as direct typed guidance. They are excluded from registry and autocomplete. |
129 | `#[allow(clippy::module_inception)]` in matching group modules | Group directories intentionally contain same-named child modules such as `core/core.rs`. |
130 | `user_commands.rs` lower layer | The registry owns runtime behavior, while this module remains the shared filesystem and parser layer. |
131 | `#[cfg(test)]` helpers in `user_commands.rs` | Deferred test migration compatibility while registry-specific tests are added. |
132
133 ## EPIC-002 Completion Status (closed)
134
135 EPIC-002 (Command Single Responsibility Extraction) extracted commands for
136 all 9 command groups through Layer 4.x sublayers. Layer 4.2 (FEAT-008) landed as
137 PR #3706 (merged) and issue #2870 closed on 2026-08-01.
138
139 | Layer | FEAT | Title | Status |
140 |---|---|---|---|
141 | 4 | FEAT-006 | Core, Config, Session, and Debug Command Extraction | Complete |
142 | 4.1 | FEAT-007 | Project, Memory, Skills, Utility, and Plugins Extraction | Complete |
143 | 4.2 | FEAT-008 | Registry Cleanup, Documentation, and Full Validation | Complete |
144
145 ## Replay Status (EPIC-001)
146
147 FEAT-001's group-owned built-in command direction is represented on `main` by
148 the newer trait-backed registry and nested group tree. FEAT-002 is replayed as
149 the dedicated user-command registry boundary. FEAT-003 is replayed as public
150 architecture and PR/issue evidence documentation, updated for the current
151 `main` target instead of the old `release/v0.8.60` branch.
152
152 lines MARKDOWN