Install any skill in seconds. Free to start, no credit card required.
Get Started Free →ROS2 messaging patterns and best practices with Clean Architecture (Python & C++)
.claude/skills/harunkurtdev-ros2-messaging-patterns/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-13 | ✗→✓ | ▲ Improved | 19% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 22% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 5% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 20% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 41% | 0% |
This skill covers ROS2 messaging patterns adhering to Clean Architecture principles, protecting the domain layer from ROS2 dependencies.
See previous Python example.
cpp// infrastructure/ros2/publishers/state_publisher.hpp #pragma once #include <rclcpp/rclcpp.hpp> #include "domain/entities/robot_state.hpp" #include "robot_interfaces/msg/robot_state.hpp" namespace infrastructure::ros2::publishers { template<typename T> class IStatePublisher { public: virtual void publish(const T& state) = 0; virtual ~IStatePublisher() = default; }; class RobotStatePublisher : public IStatePublisher<domain::entities::RobotState> { public: RobotStatePublisher(rclcpp::Node::SharedPtr node, const std::string& topic, const rclcpp::QoS& qos) : node_(node) { publisher_ = node_->create_publisher<robot_interfaces::msg::RobotState>(topic, qos); } void publish(const domain::entities::RobotState& state) override { robot_interfaces::msg::RobotState msg; msg.mode = static_cast<int>(state.mode); // ... mapping ... publisher_->publish(msg); } private: rclcpp::Node::SharedPtr node_; rclcpp::Publisher<robot_interfaces::msg::RobotState>::SharedPtr publisher_; }; } // namespace
cpp// infrastructure/ros2/subscribers/base_subscriber.hpp #pragma once #include <rclcpp/rclcpp.hpp> #include <functional> namespace infrastructure::ros2::subscribers { template<typename MsgT, typename EntityT> class BaseSubscriber { public: BaseSubscriber(rclcpp::Node::SharedPtr node, const std::string& topic, const rclcpp::QoS& qos, std::function<void(const EntityT&)> callback) : callback_(callback) { subscription_ = node->create_subscription<MsgT>( topic, qos, [this](const typename MsgT::SharedPtr msg) { this->handle_message(msg); } ); } virtual ~BaseSubscriber() = default; protected: virtual EntityT convert_to_entity(const typename MsgT::SharedPtr msg) = 0; private: void handle_message(const typename MsgT::SharedPtr msg) { auto entity = convert_to_entity(msg); callback_(entity); } rclcpp::Subscription<MsgT>::SharedPtr subscription_; std::function<void(const EntityT&)> callback_; }; } // namespace
Using message_filters in C++.
cpp// infrastructure/ros2/sync/time_sync.hpp #pragma once #include <rclcpp/rclcpp.hpp> #include <message_filters/subscriber.h> #include <message_filters/sync_policies/approximate_time.h> #include <message_filters/synchronizer.h> #include <sensor_msgs/msg/image.hpp> #include <sensor_msgs/msg/camera_info.hpp> namespace infrastructure::ros2::sync { class CameraSync { public: CameraSync(rclcpp::Node::SharedPtr node) { image_sub_.subscribe(node, "image_raw"); info_sub_.subscribe(node, "camera_info"); sync_ = std::make_shared<Sync>(MySyncPolicy(10), image_sub_, info_sub_); sync_->registerCallback(&CameraSync::callback, this); } private: void callback(const sensor_msgs::msg::Image::ConstSharedPtr& image, const sensor_msgs::msg::CameraInfo::ConstSharedPtr& info) { // Handle synchronized messages } message_filters::Subscriber<sensor_msgs::msg::Image> image_sub_; message_filters::Subscriber<sensor_msgs::msg::CameraInfo> info_sub_; typedef message_filters::sync_policies::ApproximateTime< sensor_msgs::msg::Image, sensor_msgs::msg::CameraInfo> MySyncPolicy; typedef message_filters::Synchronizer<MySyncPolicy> Sync; std::shared_ptr<Sync> sync_; }; } // namespace
cpp// infrastructure/ros2/events/event_bus.hpp #pragma once #include <rclcpp/rclcpp.hpp> #include <std_msgs/msg/string.hpp> #include <nlohmann/json.hpp> // External dependency class EventBus { public: EventBus(rclcpp::Node::SharedPtr node) { pub_ = node->create_publisher<std_msgs::msg::String>("/events/all", 10); // ... subscriber setup ... } void emit(const std::string& type, const nlohmann::json& payload) { std_msgs::msg::String msg; nlohmann::json data; data["type"] = type; data["payload"] = payload; msg.data = data.dump(); pub_->publish(msg); } private: rclcpp::Publisher<std_msgs::msg::String>::SharedPtr pub_; };
std::mutex for shared resources in C++ callbacks.| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-12 | pass→pass | 11,432 | 10,213 | -11% | 1 | 1 | 0% | 1,864 | 3,157 | +69% | 0 | 0 | — |
case-13 | fail→pass | 13,186 | 7,802 | -41% | 1 | 1 | 0% | 2,343 | 2,780 | +19% | 0 | 0 | — |
case-01 | fail→fail | 18,018 | 14,687 | -18% | 1 | 1 | 0% | 3,466 | 4,338 | +25% | 0 | 0 | — |
case-02 | fail→pass | 13,895 | 9,963 | -28% | 1 | 1 | 0% | 2,911 | 3,546 | +22% | 0 | 0 | — |
case-03 | fail→fail | 12,888 | 11,386 | -12% | 1 | 1 | 0% | 2,693 | 3,770 | +40% | 0 | 0 | — |
case-04 | fail→fail | 20,192 | 13,685 | -32% | 1 | 1 | 0% | 4,275 | 4,242 | -1% | 0 | 0 | — |
case-05 | fail→pass | 20,799 | 12,402 | -40% | 1 | 1 | 0% | 3,644 | 3,824 | +5% | 0 | 0 | — |
case-06 | fail→pass | 14,468 | 9,411 | -35% | 1 | 1 | 0% | 2,587 | 3,101 | +20% | 0 | 0 | — |
case-07 | fail→pass | 9,142 | 5,473 | -40% | 1 | 1 | 0% | 1,799 | 2,536 | +41% | 0 | 0 | — |
case-08 | pass→pass | 12,607 | 9,888 | -22% | 1 | 1 | 0% | 2,631 | 3,490 | +33% | 0 | 0 | — |
case-09 | pass→pass | 13,016 | 8,585 | -34% | 1 | 1 | 0% | 2,527 | 3,062 | +21% | 0 | 0 | — |
case-10 | pass→pass | 16,067 | 14,426 | -10% | 1 | 1 | 0% | 2,859 | 4,085 | +43% | 0 | 0 | — |
case-11 | pass→pass | 11,379 | 7,531 | -34% | 1 | 1 | 0% | 2,106 | 2,704 | +28% | 0 | 0 | — |
case-14 | fail→pass | 31,529 | 7,452 | -76% | 1 | 1 | 0% | 2,763 | 2,701 | -2% | 0 | 0 | — |
case-15 | fail→fail | 17,629 | 10,407 | -41% | 1 | 1 | 0% | 3,216 | 3,218 | +0% | 0 | 0 | — |
case-16 | fail→pass | 9,387 | 3,125 | -67% | 1 | 1 | 0% | 1,564 | 1,874 | +20% | 0 | 0 | — |
case-17 | fail→fail | 13,390 | 6,674 | -50% | 1 | 1 | 0% | 2,257 | 2,517 | +12% | 0 | 0 | — |
case-18 | pass→pass | 16,619 | 14,548 | -12% | 1 | 1 | 0% | 2,736 | 3,767 | +38% | 0 | 0 | — |
case-19 | pass→pass | 6,916 | 2,480 | -64% | 1 | 1 | 0% | 1,230 | 1,811 | +47% | 0 | 0 | — |
case-20 | pass→pass | 17,186 | 13,604 | -21% | 1 | 1 | 0% | 3,429 | 3,995 | +17% | 0 | 0 | — |
case-21 | pass→pass | 13,692 | 14,996 | +10% | 1 | 1 | 0% | 2,634 | 4,220 | +60% | 0 | 0 | — |
case-22 | pass→pass | 16,920 | 18,339 | +8% | 1 | 1 | 0% | 3,117 | 4,857 | +56% | 0 | 0 | — |
case-23 | pass→pass | 12,227 | 8,014 | -34% | 1 | 1 | 0% | 2,411 | 2,908 | +21% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted. The headline lift of +30 percentage points is the difference between those two pass rates over the 23 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.