Skip to content

Commit 031db4c

Browse files
BunsDevclaude
andcommitted
feat(tui): sync slash-command autocomplete with the full registry
The /help overlay, command palette, and slash-typeahead all read from `claurst_tui::app::PROMPT_SLASH_COMMANDS`. That list was hand- maintained and had drifted: 47 registered non-hidden commands had no autocomplete entry, and one entry (`/changes`) was orphan (no matching command anywhere in the codebase). Changes: * PROMPT_SLASH_COMMANDS is now `pub` and grows to cover every non- hidden command in the registry (accounts, add-dir, branch, btw, checkpoints, chrome, color, commit, ctx-viz, desktop, extra-usage, files, ide, install-github-app, mobile, passes, permissions, plan, pr-comments, privacy-settings, rate-limit-options, release-notes, reload-plugins, remote-control, remote-env, revert, sandbox-toggle, search, security-review, skills, snapshot, status, statusline, stickers, summary, switch, tag, tasks, teleport, terminal-setup, think-back, thinkback-play, thinking, undo, usage, version, web-setup). Entries are alphabetized. * Orphan `/changes` removed from the autocomplete list and the help category mapping. * New `prompt_slash_commands_covers_registry` test in claurst-commands is the durable safety net: it fails if the autocomplete list and the registry drift apart again. The allow-list is documented for intentional aliases (quit/settings/survey) and TUI-intercepted verbs (handoff). * test_tab_accepts_slash_suggestion: relaxed pinned name -> asserts the UX invariant ("Tab completes /a to a /a* command") so future additions to the list don't break the test. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 877fe08 commit 031db4c

3 files changed

Lines changed: 164 additions & 18 deletions

File tree

src-rust/crates/commands/src/lib.rs

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11400,6 +11400,67 @@ mod tests {
1140011400
assert!(matches!(result, CommandResult::Error(_)));
1140111401
}
1140211402

11403+
/// The TUI's slash-command autocomplete and `/help` overlay both read
11404+
/// from `claurst_tui::app::PROMPT_SLASH_COMMANDS`. That list is hand-
11405+
/// maintained — when a new command is added to the registry but not to
11406+
/// the list, the TUI silently hides it from autocomplete. This test is
11407+
/// the safety net.
11408+
#[test]
11409+
fn prompt_slash_commands_covers_registry() {
11410+
use std::collections::HashSet;
11411+
11412+
// Names that appear in autocomplete on purpose even though they're
11413+
// not (or not primarily) registered SlashCommands:
11414+
// - quit / settings / survey: user-facing aliases of exit / config /
11415+
// feedback.
11416+
// - handoff: intercepted directly by the TUI via
11417+
// `App::intercept_slash_command_with_args` (sends the current
11418+
// session context to a Coven familiar). It is documented in
11419+
// docs/familiars.md and lives in tui/src/handoff.rs.
11420+
const ALLOWED_ALIAS_NAMES: &[&str] = &["quit", "settings", "survey", "handoff"];
11421+
11422+
let prompt_names: HashSet<&str> = claurst_tui::app::PROMPT_SLASH_COMMANDS
11423+
.iter()
11424+
.map(|(name, _)| *name)
11425+
.collect();
11426+
11427+
// Every non-hidden registered command must show up in autocomplete.
11428+
let mut missing: Vec<&str> = all_commands()
11429+
.iter()
11430+
.filter(|c| !c.hidden())
11431+
.map(|c| c.name())
11432+
.filter(|name| !prompt_names.contains(name))
11433+
.collect();
11434+
missing.sort();
11435+
assert!(
11436+
missing.is_empty(),
11437+
"PROMPT_SLASH_COMMANDS is missing entries for non-hidden \
11438+
registered commands: {missing:?}. Add them to \
11439+
crates/tui/src/app.rs::PROMPT_SLASH_COMMANDS."
11440+
);
11441+
11442+
// No orphans in the prompt list (every entry must either match a
11443+
// registered command name/alias or be in the allow-list).
11444+
let known_names_and_aliases: HashSet<&str> = all_commands()
11445+
.iter()
11446+
.flat_map(|c| std::iter::once(c.name()).chain(c.aliases()))
11447+
.collect();
11448+
let mut orphans: Vec<&str> = prompt_names
11449+
.iter()
11450+
.copied()
11451+
.filter(|name| {
11452+
!known_names_and_aliases.contains(name) && !ALLOWED_ALIAS_NAMES.contains(name)
11453+
})
11454+
.collect();
11455+
orphans.sort();
11456+
assert!(
11457+
orphans.is_empty(),
11458+
"PROMPT_SLASH_COMMANDS has orphan entries with no matching \
11459+
command: {orphans:?}. Either register the command or drop \
11460+
the entry."
11461+
);
11462+
}
11463+
1140311464
#[tokio::test]
1140411465
async fn test_coven_help_lists_integration_subcommands() {
1140511466
let mut ctx = make_ctx();

src-rust/crates/tui/src/app.rs

Lines changed: 92 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -45,20 +45,32 @@ use std::io::Stdout;
4545
use std::sync::{Arc, Mutex};
4646
use tracing::debug;
4747

48-
const PROMPT_SLASH_COMMANDS: &[(&str, &str)] = &[
49-
("advisor", "Set or unset the server-side advisor model"),
50-
(
51-
"familiar",
52-
"Set your active familiar — changes the TUI mascot live",
53-
),
48+
/// Pairs of (name, one-line description) shown by the slash-command
49+
/// autocompletion, /help overlay, and the command palette. Must stay in
50+
/// sync with the non-hidden entries returned by
51+
/// `claurst_commands::all_commands()`; the
52+
/// `prompt_slash_commands_covers_registry` test in `claurst-commands`
53+
/// enforces that.
54+
pub const PROMPT_SLASH_COMMANDS: &[(&str, &str)] = &[
55+
("accounts", "List every stored account across providers"),
5456
(
55-
"handoff",
56-
"Hand off current session context to a Coven familiar",
57+
"add-dir",
58+
"Add an extra workspace root to the active session",
5759
),
60+
("advisor", "Set or unset the server-side advisor model"),
5861
("agent", "List available familiars or show familiar details"),
5962
("agents", "Browse familiar definitions and active familiars"),
60-
("changes", "Inspect changes from the current session"),
63+
("branch", "Create or switch session branches"),
64+
("btw", "Send a side-channel note to the model"),
65+
("caveman", "Caveman speech mode — save big token"),
66+
("checkpoints", "Browse session snapshots / restore points"),
67+
("chrome", "Browser automation via Chrome DevTools Protocol"),
6168
("clear", "Clear the conversation transcript"),
69+
("color", "Set the prompt bar color for the current session"),
70+
(
71+
"commit",
72+
"Stage and commit changes (model drafts the message)",
73+
),
6274
("compact", "Compact the conversation context"),
6375
("config", "Open settings"),
6476
("connect", "Connect an AI provider"),
@@ -69,18 +81,31 @@ const PROMPT_SLASH_COMMANDS: &[(&str, &str)] = &[
6981
"coven",
7082
"Drive the local Coven daemon (sessions, harness runs, rituals)",
7183
),
84+
("ctx-viz", "Visualize context-window contents and usage"),
85+
("desktop", "Computer-use desktop control"),
7286
("diff", "Inspect the current git diff"),
7387
("doctor", "Run diagnostics"),
7488
("effort", "Set effort level (low/medium/high/max)"),
7589
("exit", "Quit Coven Code"),
7690
("export", "Export conversation"),
91+
("extra-usage", "Detailed token usage breakdown"),
92+
(
93+
"familiar",
94+
"Set your active familiar — changes the TUI mascot live",
95+
),
7796
("fast", "Toggle fast mode"),
7897
("feedback", "Open session feedback survey"),
98+
("files", "List files read or written this session"),
7999
("fork", "Fork session into a new branch"),
80100
("goal", "Set or view the current session goal"),
101+
(
102+
"handoff",
103+
"Hand off current session context to a Coven familiar",
104+
),
81105
("heapdump", "Show process memory and diagnostic information"),
82106
("help", "Show help"),
83107
("hooks", "Browse configured hooks (read-only)"),
108+
("ide", "Connect to the active IDE integration"),
84109
(
85110
"import-config",
86111
"Import CLAUDE.md and settings.json from ~/.claude",
@@ -90,6 +115,7 @@ const PROMPT_SLASH_COMMANDS: &[(&str, &str)] = &[
90115
"insights",
91116
"Generate a session analysis report with conversation statistics",
92117
),
118+
("install-github-app", "Install the Coven Code GitHub App"),
93119
(
94120
"install-slack-app",
95121
"Install the Coven Code Slack integration",
@@ -104,32 +130,82 @@ const PROMPT_SLASH_COMMANDS: &[(&str, &str)] = &[
104130
),
105131
("mcp", "Browse configured MCP servers"),
106132
("memory", "Browse and open AGENTS.md memory files"),
133+
(
134+
"mobile",
135+
"Show QR code / links for the mobile companion app",
136+
),
107137
("model", "Change the AI model"),
138+
("normal", "Deactivate speech mode"),
108139
("output-style", "Toggle output style (auto/stream/verbose)"),
140+
("passes", "Inspect per-turn pass history"),
141+
("permissions", "Manage tool permission rules"),
142+
("plan", "Enter plan mode (read-only)"),
109143
("plugin", "Manage plugins (list/info/enable/disable/reload)"),
144+
(
145+
"pr-comments",
146+
"Read or post comments on the active GitHub PR",
147+
),
148+
("privacy-settings", "Open Coven Code privacy settings"),
110149
("providers", "List available AI providers and their status"),
111-
("caveman", "Caveman speech mode — save big token"),
112-
("rocky", "Rocky speech mode — amaze amaze amaze"),
113-
("normal", "Deactivate speech mode"),
114150
("quit", "Exit Coven Code"),
151+
("rate-limit-options", "Configure rate-limit handling"),
115152
("refresh", "Clear saved provider auth and model caches"),
153+
("release-notes", "View Coven Code release notes / changelog"),
154+
(
155+
"reload-plugins",
156+
"Reload the active session plugin registry",
157+
),
158+
("remote-control", "Remote-control session via the bridge"),
159+
("remote-env", "Configure the remote-control environment"),
116160
("rename", "Rename this session"),
117161
("resume", "Resume a previous session"),
162+
("revert", "Revert a file to its pre-session state"),
118163
("review", "Review changes (git diff)"),
119164
("rewind", "Rewind to an earlier turn"),
165+
("rocky", "Rocky speech mode — amaze amaze amaze"),
166+
("sandbox-toggle", "Toggle sandboxed shell execution"),
167+
("search", "Search the codebase by natural language or regex"),
168+
("security-review", "Run a security-focused review pass"),
120169
("session", "Browse and manage sessions"),
121170
("settings", "Open settings"),
122171
(
123172
"share",
124173
"Upload the current session as a secret gist and get a shareable URL",
125174
),
175+
("skills", "List and manage skills"),
176+
("snapshot", "Manage filesystem snapshots"),
126177
("stats", "Open token and cost stats"),
178+
("status", "Show the current session status"),
179+
("statusline", "Configure the TUI status line"),
180+
("stickers", "Open the Coven Code sticker page"),
181+
("summary", "Generate a session summary"),
127182
("survey", "Open session feedback survey"),
183+
("switch", "Switch the active account for a provider"),
184+
("tag", "Tag the current session with a label"),
185+
("tasks", "Manage tracked background tasks"),
186+
(
187+
"teleport",
188+
"Bundle session state for teleporting to another machine",
189+
),
190+
(
191+
"terminal-setup",
192+
"Run the terminal capability detection wizard",
193+
),
128194
("theme", "Open the theme picker"),
195+
(
196+
"think-back",
197+
"Show extended-thinking traces from previous responses",
198+
),
199+
(
200+
"thinkback-play",
201+
"Replay a previous thinking trace as a walkthrough",
202+
),
203+
("thinking", "Configure extended thinking for the session"),
129204
(
130205
"ultrareview",
131206
"Run an exhaustive multi-dimensional code review",
132207
),
208+
("undo", "Undo a file change made during this session"),
133209
(
134210
"update",
135211
"Check for updates and upgrade to the latest version",
@@ -138,18 +214,19 @@ const PROMPT_SLASH_COMMANDS: &[(&str, &str)] = &[
138214
"upgrade",
139215
"Check for updates and upgrade to the latest version",
140216
),
217+
("usage", "Detailed per-call token usage breakdown"),
218+
("version", "Display the current Coven Code version"),
141219
("vim", "Toggle vim keybindings"),
142220
("voice", "Toggle voice input mode"),
221+
("web-setup", "Open the web-setup proxy assistant"),
143222
];
144223

145224
fn help_command_category(name: &str) -> &'static str {
146225
match name {
147226
"connect" | "model" | "providers" | "refresh" | "fast" | "effort" | "voice" => {
148227
"Model & Provider"
149228
}
150-
"changes" | "diff" | "review" | "rewind" | "export" | "copy" | "share" | "links" => {
151-
"Review & History"
152-
}
229+
"diff" | "review" | "rewind" | "export" | "copy" | "share" | "links" => "Review & History",
153230
"stats" | "cost" | "context" | "insights" | "heapdump" | "doctor" => "Diagnostics",
154231
"config" | "settings" | "theme" | "keybindings" | "hooks" | "mcp" | "import-config" => {
155232
"Workspace"

src-rust/crates/tui/src/lib.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -752,14 +752,22 @@ mod tests {
752752

753753
#[test]
754754
fn test_tab_accepts_slash_suggestion() {
755+
// The first `/a*` suggestion is whichever command comes alphabetically
756+
// first in PROMPT_SLASH_COMMANDS. We don't pin a specific name here
757+
// because the list grows over time; instead we assert that Tab
758+
// completes to *some* `/a*` command — which is the real UX invariant.
755759
let mut app = make_app();
756760
app.handle_key_event(key(KeyCode::Char('/')));
757761
app.handle_key_event(key(KeyCode::Char('a')));
758762
app.handle_key_event(key(KeyCode::Tab));
759763

760-
assert_eq!(app.input, "/advisor");
761-
assert_eq!(app.prompt_input.text, "/advisor");
762-
assert_eq!(app.cursor_pos, "/advisor".len());
764+
assert!(
765+
app.input.starts_with("/a"),
766+
"expected Tab to complete to a /a* command, got {:?}",
767+
app.input
768+
);
769+
assert_eq!(app.input, app.prompt_input.text);
770+
assert_eq!(app.cursor_pos, app.input.len());
763771
}
764772

765773
#[test]

0 commit comments

Comments
 (0)