Skip to content

added commands register#87

Merged
igorkorsukov merged 1 commit into
musescore:mainfrom
igorkorsukov:w/rcmd/rcmd_step3
Jun 16, 2026
Merged

added commands register#87
igorkorsukov merged 1 commit into
musescore:mainfrom
igorkorsukov:w/rcmd/rcmd_step3

Conversation

@igorkorsukov

@igorkorsukov igorkorsukov commented Jun 15, 2026

Copy link
Copy Markdown
Member

Summary by CodeRabbit

Release Notes

  • New Features

    • Commands now include metadata (title, description, input specifications, and visual decorations) for enhanced user experience.
    • Module-based command registration system for improved extensibility.
  • Refactor

    • Enhanced command dispatcher and registration architecture.

@coderabbitai

coderabbitai Bot commented Jun 15, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@igorkorsukov, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 37 minutes and 8 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0c99d99b-9e7b-4d9b-9429-835942193f69

📥 Commits

Reviewing files that changed from the base of the PR and between cc57642 and 1b2136f.

📒 Files selected for processing (15)
  • framework/rcommand/CMakeLists.txt
  • framework/rcommand/commandable.h
  • framework/rcommand/commandtypes.h
  • framework/rcommand/icommanddispatcher.h
  • framework/rcommand/icommandsregister.h
  • framework/rcommand/imodulecommands.h
  • framework/rcommand/internal/commanddispatcher.cpp
  • framework/rcommand/internal/commanddispatcher.h
  • framework/rcommand/internal/commandsregister.cpp
  • framework/rcommand/internal/commandsregister.h
  • framework/rcommand/rcommandmodule.cpp
  • framework/rcommand/rcommandmodule.h
  • framework/rcontrol/mcp/mcpcontroller.cpp
  • framework/rcontrol/mcp/mcpcontroller.h
  • framework/rcontrol/mcp/mcptypes.h
📝 Walkthrough

Walkthrough

The pull request renames the rcommand framework's public types: RCommandable becomes Commandable, IRCommandDispatcher becomes ICommandDispatcher, with all method signatures and internal struct members updated accordingly. commandtypes.h gains a CommandInfo model comprising DataType, Arg, InputSchema, Checkable, Decoration, and CommandInfo structs. Two new interfaces are introduced—IModuleCommands (per-module command metadata provider) and ICommandsRegister (global registry)—with a concrete CommandsRegister implementation storing modules in a string-keyed map. RCommandModule adds a registerExports() override that publishes ICommandsRegister globally and ICommandDispatcher per-context via IoC. McpController is rewired to inject these services, builds its tool list dynamically from the registry using command path/name conversion helpers, and dispatches tool calls through CommandQuery.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 3

❌ Failed checks (2 warnings, 1 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning No pull request description was provided; the author did not fill in the template or provide any explanation of changes, objectives, testing, or CLA attestation. Add a comprehensive PR description following the template, including issue resolution, motivation, checklist completion, and testing details.
Docstring Coverage ⚠️ Warning Docstring coverage is 2.63% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'added commands register' is partially related to the changeset but is vague and incomplete, omitting significant structural changes like dispatcher renaming and interface refactoring. Use a more descriptive title that captures the main changes, such as 'Refactor command dispatcher and add commands register' to better reflect the scope of work.
✅ Passed checks (2 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
framework/rcommand/internal/commanddispatcher.cpp (1)

44-53: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate client and callback before registering a command.

Registration currently dereferences client and later assumes callback is callable. A null client or empty callback causes runtime failure paths.

Proposed fix
 void CommandDispatcher::onRequest(Commandable* client, const Command& command, const CallBack& callback)
 {
+    if (!client || !callback) {
+        LOGW() << "invalid command registration";
+        return;
+    }
+
     IF_ASSERT_FAILED(m_clients.find(command) == m_clients.end()) {
         LOGW() << "command already registered: " << command;
         return;
     }

     m_clients[command] = { client, callback };
     client->setDispatcher(this);
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@framework/rcommand/internal/commanddispatcher.cpp` around lines 44 - 53, The
CommandDispatcher::onRequest method does not validate that the client parameter
is non-null before using it in client->setDispatcher(this), and does not
validate that the callback parameter is valid before storing it in m_clients.
Add null checks for the client pointer and validate that callback is not empty
before proceeding with the registration. If either validation fails, log an
appropriate error message and return early to prevent runtime failures from
dereferencing invalid pointers or storing invalid callables.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@framework/rcommand/CMakeLists.txt`:
- Around line 23-35: The public interface header `icommandsregister.h` is
missing from the target_sources list for the muse_rcommand target in the
CMakeLists.txt file. Add `icommandsregister.h` to the target_sources PRIVATE
section alongside the other public interface headers like `icommanddispatcher.h`
and `imodulecommands.h` to ensure consistency with other public headers and to
properly include it in any header export or install metadata.

In `@framework/rcommand/commandtypes.h`:
- Around line 52-56: The DataType type member variable in the Arg struct lacks a
deterministic default initialization, causing undefined behavior when Arg
objects are default-constructed (such as when std::map constructs values
implicitly). Initialize the DataType type field to a sensible default value
(typically the first enum value in the DataType enum or an explicitly defined
default enum value) by adding an initializer in the struct member declaration to
ensure predictable behavior during default initialization and serialization.

In `@framework/rcommand/internal/commandsregister.cpp`:
- Around line 25-33: Add null pointer checks to both the reg() and unreg()
methods in the CommandsRegister class to prevent crashes when a null module
pointer is passed. In the reg() method, verify that the module pointer is not
null before calling moduleName() and inserting into the m_modules map.
Similarly, in the unreg() method, add a null check before calling moduleName()
and erasing from the map. Return early or skip the operation if the module is
null to prevent undefined behavior.
- Around line 38-40: In the for loop iterating over m_modules, the
commandInfos() method is being called twice on the same module.second object
within the insert statement. To optimize this, extract the return value of
module.second->commandInfos() into a local const reference variable before the
insert call, and then use that reference's begin() and end() methods in the
insert statement instead of calling commandInfos() twice.

In `@framework/rcontrol/mcp/mcpcontroller.cpp`:
- Around line 97-106: The tool's inputSchema is being initialized as empty with
InputSchema() instead of being populated with the actual argument schema from
CommandInfo::inputSchema. Uncomment and properly implement the schema mapping
loop that iterates through info.inputSchema.args, creating Property objects for
each argument with the name, type, description, minimum, and maximum fields
populated from the corresponding CommandInfo argument metadata, then add these
properties to the tool's inputSchema before pushing the tool to the tools
collection.
- Around line 71-74: The code at the dispatch call site fire-and-forgets the
commandsDispatcher()->dispatch(commandQuery(...)) call and immediately invokes
onResult(ToolResult()) without waiting for completion or capturing any errors.
Modify this code to wait for the dispatcher to complete execution, capture any
errors or failures from the dispatch operation (including unknown command
paths), and pass an appropriate ToolResult to onResult that reflects success or
failure based on the dispatcher's actual result rather than always returning a
successful ToolResult.
- Around line 42-54: The commandToToolName and commandQuery functions use a
non-bijective encoding scheme where converting "/" to "_" causes collisions (for
example, "a/b_c" and "a/b/c" both map to the same string), and the args
parameter in commandQuery is unused, causing tool-call arguments to be dropped.
Implement a proper bijective encoding scheme in both commandToToolName and
commandQuery functions that can reliably convert between "/" and "_" without
collisions (consider escaping or using an alternative delimiter encoding), and
pass the args parameter into the CommandQuery constructor when constructing the
return value in commandQuery.

---

Outside diff comments:
In `@framework/rcommand/internal/commanddispatcher.cpp`:
- Around line 44-53: The CommandDispatcher::onRequest method does not validate
that the client parameter is non-null before using it in
client->setDispatcher(this), and does not validate that the callback parameter
is valid before storing it in m_clients. Add null checks for the client pointer
and validate that callback is not empty before proceeding with the registration.
If either validation fails, log an appropriate error message and return early to
prevent runtime failures from dereferencing invalid pointers or storing invalid
callables.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 067bc26f-0a43-45bb-b50b-fca48c73f226

📥 Commits

Reviewing files that changed from the base of the PR and between bbb399b and af635de.

📒 Files selected for processing (15)
  • framework/rcommand/CMakeLists.txt
  • framework/rcommand/commandable.h
  • framework/rcommand/commandtypes.h
  • framework/rcommand/icommanddispatcher.h
  • framework/rcommand/icommandsregister.h
  • framework/rcommand/imodulecommands.h
  • framework/rcommand/internal/commanddispatcher.cpp
  • framework/rcommand/internal/commanddispatcher.h
  • framework/rcommand/internal/commandsregister.cpp
  • framework/rcommand/internal/commandsregister.h
  • framework/rcommand/rcommandmodule.cpp
  • framework/rcommand/rcommandmodule.h
  • framework/rcontrol/mcp/mcpcontroller.cpp
  • framework/rcontrol/mcp/mcpcontroller.h
  • framework/rcontrol/mcp/mcptypes.h

Comment on lines 23 to 35
target_sources(muse_rcommand PRIVATE
rcommandmodule.cpp
rcommandmodule.h
ircommanddispatcher.h
rcommandable.h
rcommandtypes.h
icommanddispatcher.h
imodulecommands.h
commandable.h
commandtypes.h

internal/rcommanddispatcher.cpp
internal/rcommanddispatcher.h
internal/commanddispatcher.cpp
internal/commanddispatcher.h
internal/commandsregister.cpp
internal/commandsregister.h
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check how muse_rcommand headers are surfaced/installed and whether icommandsregister.h is covered.
rg -n "target_sources\\(muse_rcommand|icommandsregister\\.h|FILE_SET|install\\(TARGETS\\s+muse_rcommand" -C3

Repository: musescore/muse_framework

Length of output: 1455


🏁 Script executed:

cat -n framework/rcommand/CMakeLists.txt

Repository: musescore/muse_framework

Length of output: 1519


🏁 Script executed:

# Search for install rules and header export patterns in this module
rg -n "install|FILE_SET|PUBLIC|INTERFACE" framework/rcommand/CMakeLists.txt -A 2

Repository: musescore/muse_framework

Length of output: 50


🏁 Script executed:

# Check the muse_create_module function to understand how headers are handled
rg -n "muse_create_module" --type cmake -A 5 | head -50

Repository: musescore/muse_framework

Length of output: 2696


🏁 Script executed:

# Find muse_create_module definition
rg -n "macro|function" --type cmake | rg "muse_create_module" -B 2 -A 10

Repository: musescore/muse_framework

Length of output: 2094


🏁 Script executed:

# Check if icommandsregister.h exists
ls -la framework/rcommand/ | grep -i icommands

Repository: musescore/muse_framework

Length of output: 138


🏁 Script executed:

# Look for cmake function definitions that might handle module creation
fd -name "*.cmake" -type f | head -20

Repository: musescore/muse_framework

Length of output: 299


🏁 Script executed:

sed -n '45,129p' framework/cmake/MuseCreateModule.cmake

Repository: musescore/muse_framework

Length of output: 2908


🏁 Script executed:

# Also check content of icommandsregister.h to confirm it's a public interface
cat -n framework/rcommand/icommandsregister.h

Repository: musescore/muse_framework

Length of output: 1488


🏁 Script executed:

# Search for install rules in parent CMakeLists or related cmake files
rg -n "install.*DIRECTORY|FILE_SET.*HEADERS|PUBLIC_HEADER" framework/CMakeLists.txt buildscripts/cmake/ -A 3 | head -100

Repository: musescore/muse_framework

Length of output: 50


🏁 Script executed:

# Check if there's a parent CMakeLists that handles module installation
cat -n framework/CMakeLists.txt | head -60

Repository: musescore/muse_framework

Length of output: 2048


🏁 Script executed:

# Verify if icommandsregister.h is accessible as a public include without being in target_sources
rg -n "icommandsregister" -A 2 -B 2 | head -80

Repository: musescore/muse_framework

Length of output: 751


🏁 Script executed:

# Search for install rules that might use target_sources or install headers
rg -n "install.*FILES|install.*DIRECTORY" buildscripts/ --type cmake | head -40

Repository: musescore/muse_framework

Length of output: 774


🏁 Script executed:

# Check if there's a top-level CMakeLists with install rules
find . -maxdepth 2 -name CMakeLists.txt -exec grep -l "install" {} \;

Repository: musescore/muse_framework

Length of output: 50


🏁 Script executed:

# Look for any cmake that might derive install from target_sources
rg -n "PUBLIC_HEADER|FILE_SET" buildscripts/cmake/ --type cmake

Repository: musescore/muse_framework

Length of output: 50


🏁 Script executed:

# Check if other interface headers (.h files not paired with .cpp) are in target_sources
rg -n "\.h" framework/rcommand/CMakeLists.txt | grep "target_sources" -A 20

Repository: musescore/muse_framework

Length of output: 50


🏁 Script executed:

# Look for patterns: do interface-only headers get added to target_sources elsewhere?
rg -l "\.h" framework/*/CMakeLists.txt | xargs grep -l "icommand\|idispatcher\|imodule" | head -5

Repository: musescore/muse_framework

Length of output: 192


🏁 Script executed:

# Check rcommand module structure - count .h vs .cpp files
find framework/rcommand -name "*.h" -o -name "*.cpp" | sort

Repository: musescore/muse_framework

Length of output: 519


Add icommandsregister.h to the module's public header set.

This is a public interface header (marked MODULE_GLOBAL_INTERFACE) and is imported by downstream modules (rcontrol/mcp/mcpcontroller.h). Other public interface headers in this module (icommanddispatcher.h, imodulecommands.h) are explicitly listed in target_sources, so icommandsregister.h should be added for consistency and to ensure it's included in any header export/install metadata.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@framework/rcommand/CMakeLists.txt` around lines 23 - 35, The public interface
header `icommandsregister.h` is missing from the target_sources list for the
muse_rcommand target in the CMakeLists.txt file. Add `icommandsregister.h` to
the target_sources PRIVATE section alongside the other public interface headers
like `icommanddispatcher.h` and `imodulecommands.h` to ensure consistency with
other public headers and to properly include it in any header export or install
metadata.

Comment thread framework/rcommand/commandtypes.h
Comment thread framework/rcommand/internal/commandsregister.cpp
Comment thread framework/rcommand/internal/commandsregister.cpp
Comment on lines +42 to +54
static std::string commandToToolName(const Command& command)
{
std::string path = command.path();
muse::strings::replace(path, "/", "_");
return path;
}

static CommandQuery commandQuery(const std::string& name, const JsonObject& args)
{
std::string path = name;
muse::strings::replace(path, "_", "/");
return CommandQuery(path);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Make tool-name conversion reversible and forward MCP arguments into CommandQuery.

The /_ replacement is not bijective (a/b_c and a/b/c collide), and the args parameter (Line 49) is currently unused. This can dispatch the wrong command and silently drop tool-call parameters.

🧰 Tools
🪛 Clang (14.0.6)

[warning] 42-42: use a trailing return type for this function

(modernize-use-trailing-return-type)


[warning] 49-49: use a trailing return type for this function

(modernize-use-trailing-return-type)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@framework/rcontrol/mcp/mcpcontroller.cpp` around lines 42 - 54, The
commandToToolName and commandQuery functions use a non-bijective encoding scheme
where converting "/" to "_" causes collisions (for example, "a/b_c" and "a/b/c"
both map to the same string), and the args parameter in commandQuery is unused,
causing tool-call arguments to be dropped. Implement a proper bijective encoding
scheme in both commandToToolName and commandQuery functions that can reliably
convert between "/" and "_" without collisions (consider escaping or using an
alternative delimiter encoding), and pass the args parameter into the
CommandQuery constructor when constructing the return value in commandQuery.

Comment on lines +71 to 74
commandsDispatcher()->dispatch(commandQuery(name, args));

onResult(ToolResult());
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Wait for dispatcher completion before replying to MCP tool calls.

This path fire-and-forgets dispatch(...) and immediately returns ToolResult() (isError=false). Dispatcher failures (including unknown command paths) are never propagated to MCP callers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@framework/rcontrol/mcp/mcpcontroller.cpp` around lines 71 - 74, The code at
the dispatch call site fire-and-forgets the
commandsDispatcher()->dispatch(commandQuery(...)) call and immediately invokes
onResult(ToolResult()) without waiting for completion or capturing any errors.
Modify this code to wait for the dispatcher to complete execution, capture any
errors or failures from the dispatch operation (including unknown command
paths), and pass an appropriate ToolResult to onResult that reflects success or
failure based on the dispatcher's actual result rather than always returning a
successful ToolResult.

Comment on lines +97 to +106
tool.inputSchema = InputSchema();
// for (const auto& arg : info.inputSchema.args) {
// Property property;
// property.name = String::fromStdString(arg.first);
// property.type = String::fromStdString(arg.second.type);
// property.description = String::fromStdString(arg.second.description);
// property.minimum = String::fromStdString(arg.second.minimum);
// property.maximum = String::fromStdString(arg.second.maximum);
// }
tools.push_back(std::move(tool));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Publish real argument schema from CommandInfo::inputSchema instead of an empty schema.

Each tool currently exposes InputSchema() regardless of command metadata, while schema mapping code is left commented out. MCP clients cannot reliably infer required arguments/types for registered commands.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@framework/rcontrol/mcp/mcpcontroller.cpp` around lines 97 - 106, The tool's
inputSchema is being initialized as empty with InputSchema() instead of being
populated with the actual argument schema from CommandInfo::inputSchema.
Uncomment and properly implement the schema mapping loop that iterates through
info.inputSchema.args, creating Property objects for each argument with the
name, type, description, minimum, and maximum fields populated from the
corresponding CommandInfo argument metadata, then add these properties to the
tool's inputSchema before pushing the tool to the tools collection.

@igorkorsukov igorkorsukov merged commit e852c05 into musescore:main Jun 16, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants