Skip to content

feat: dynamic slash command discovery in composer autocomplete#1412

Open
LucaDeLeo wants to merge 1 commit intopingdotgg:mainfrom
LucaDeLeo:feat/slash-command-discovery
Open

feat: dynamic slash command discovery in composer autocomplete#1412
LucaDeLeo wants to merge 1 commit intopingdotgg:mainfrom
LucaDeLeo:feat/slash-command-discovery

Conversation

@LucaDeLeo
Copy link
Copy Markdown

@LucaDeLeo LucaDeLeo commented Mar 25, 2026

Summary

  • Custom slash commands and skills now appear in the composer / autocomplete
  • Scans ~/.claude/commands/, ~/.claude/skills/, and project-level equivalents
  • Includes hardcoded built-in Claude Code skills (/simplify, /batch, /loop, etc.)
  • Fixes arrow-key scrolling in the command menu

Why

The composer autocomplete was hardcoded to 3 commands (/model, /plan, /default). Custom commands from .claude/commands/ worked when typed as plain text but weren't discoverable. This makes them visible and selectable.

What changed

Area Change
packages/contracts/ SlashCommandEntry schema, projects.listCommands WS method
apps/server/src/slashCommandScanner.ts New file — scans commands/ and skills/ directories with 30s cache
apps/server/src/wsServer.ts Route handler for new method
apps/web/src/composer-logic.ts / trigger now matches any command, not just 3 hardcoded ones
apps/web/src/components/ChatView.tsx Fetches + merges custom commands into autocomplete menu
apps/web/src/components/chat/ComposerCommandMenu.tsx scrollIntoView on highlighted item

Before / After

Before: Typing / shows only /model, /plan, /default
After: Typing / shows all custom commands, installed skills, and built-in Claude Code skills


Note

Medium Risk
Adds a new WebSocket RPC and server-side filesystem scanning of user/project .claude directories to power composer autocomplete, which could affect performance or expose unexpected command names if path handling is wrong. UI behavior changes in the composer selection flow and menu navigation, but no auth or persistent data writes are introduced.

Overview
Adds dynamic slash command discovery so the composer / autocomplete can show custom commands and skills, not just hardcoded entries.

The server now implements projects.listCommands via a new slashCommandScanner that scans user and project .claude/commands (markdown files) and .claude/skills (SKILL.md frontmatter + sub-files), merges them with a small built-in list, and caches results briefly.

The web app fetches these commands via a new React Query hook / native WS API method, merges them into the composer menu (keeping /model, /plan, /default special-cased), loosens trigger parsing to allow arbitrary slash commands, and fixes keyboard navigation scrolling by auto-scrolling the active menu item into view.

Written by Cursor Bugbot for commit 874aaf9. This will update automatically on new commits. Configure here.

Note

Add dynamic slash command discovery to composer autocomplete

  • Introduces slashCommandScanner.ts that scans ~/.claude and ./.claude directories for commands/*.md and skills/*/SKILL.md files, building a sorted, deduplicated command list with a 30s TTL cache per cwd.
  • Adds a projects.listCommands WebSocket endpoint in wsServer.ts and exposes it via api.projects.listCommands in wsNativeApi.ts.
  • Updates the composer in ChatView.tsx to fetch and display project/user-defined commands alongside built-ins when typing /; selecting a custom command inserts /<name> into the prompt.
  • Extends trigger detection in composer-logic.ts so any /<text> input opens the slash-command menu, not just known commands; only /plan and /default retain mode-switching behavior.
  • Adds auto-scroll to the active item in ComposerCommandMenu.tsx when navigating the list.
📊 Macroscope summarized 874aaf9. 10 files reviewed, 1 issue evaluated, 0 issues filtered, 1 comment posted

🗂️ Filtered Issues

The composer's `/` autocomplete was hardcoded to only 3 commands (model,
plan, default). This adds server-side scanning of ~/.claude/commands/,
~/.claude/skills/, and their project-level equivalents so that custom
commands and skills appear in the autocomplete popover alongside the
built-in ones. Built-in Claude Code skills (simplify, batch, loop, etc.)
are also included as hardcoded fallbacks.

- Add `projects.listCommands` WS method with schemas in contracts
- Add filesystem scanner for commands/ and skills/ directories
- Frontend fetches commands per-project via React Query
- Open `detectComposerTrigger` to match any `/word` pattern
- Custom command selection inserts `/<name> ` into the composer
- Arrow key navigation now scrolls to keep highlighted item visible

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 25, 2026

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 0c3ea660-232a-4905-9a20-04adbb2037f2

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions bot added size:L 100-499 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Mar 25, 2026
Comment on lines +100 to +107
async function readFirstLine(filePath: string): Promise<string | undefined> {
const head = await readHead(filePath, 256);
if (!head) return undefined;
const firstLine = head.split("\n")[0]?.trim();
if (firstLine && !firstLine.startsWith("$") && !firstLine.startsWith("#") && !firstLine.startsWith("---")) {
return firstLine.slice(0, 120);
}
return undefined;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Low src/slashCommandScanner.ts:100

readFirstLine only checks the first line of the file, so files that start with an empty line, heading, template variable, or YAML frontmatter return undefined even when a valid description exists on a subsequent line. For example, a file starting with # Title followed by A description here returns no description.

+  if (!head) return undefined;
+  const lines = head.split("\n");
+  for (const line of lines) {
+    const trimmed = line.trim();
+    if (trimmed && !trimmed.startsWith("$") && !trimmed.startsWith("#") && !trimmed.startsWith("---")) {
+      return trimmed.slice(0, 120);
+    }
+  }
🤖 Copy this AI Prompt to have your agent fix this:
In file apps/server/src/slashCommandScanner.ts around lines 100-107:

`readFirstLine` only checks the first line of the file, so files that start with an empty line, heading, template variable, or YAML frontmatter return `undefined` even when a valid description exists on a subsequent line. For example, a file starting with `# Title` followed by `A description here` returns no description.

Evidence trail:
apps/server/src/slashCommandScanner.ts lines 99-107 at REVIEWED_COMMIT - The function `readFirstLine` has a docstring claiming it reads 'the first non-empty, non-heading, non-template-variable line' but implementation only checks `head.split("\n")[0]` (line 103), returning undefined if first line doesn't match criteria instead of checking subsequent lines.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:L 100-499 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant