Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Guide for creating ROS2 nodes following Clean Architecture principles (Python & C++)
.claude/skills/harunkurtdev-ros2-node-creation/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-03 | ✗→✓ | ▲ Improved | 13% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 24% | 0% |
| case-13 | ✗→✓ | ▲ Improved | -32% | 0% |
| case-14 | ✗→✓ | ▲ Improved | 29% | 0% |
| case-15 | ✗→✓ | ▲ Improved | 13% | 0% |
This skill is used to create ROS2 nodes that adhere to Clean Architecture principles. It covers both Python and C++ implementations.
src/
├── domain/ # Business Logic Layer
│ ├── entities/ # Core business objects
│ ├── repositories/ # Repository interfaces (abstract)
│ └── use_cases/ # Business rules
├── application/ # Application Layer
│ ├── services/ # Application services
│ └── interfaces/ # Port interfaces
└── infrastructure/ # Infrastructure Layer
└── ros2/
├── nodes/ # ROS2 Node implementations
├── publishers/ # Publisher adapters
├── subscribers/ # Subscriber adapters
└── services/ # Service adapterspython#!/usr/bin/env python3 """ ROS2 Node: [NodeName] Description: [Node Description] """ import rclpy from rclpy.node import Node from rclpy.qos import QoSProfile, ReliabilityPolicy, HistoryPolicy from typing import Optional, Callable from abc import ABC, abstractmethod class BaseNode(Node, ABC): """Base class for all nodes.""" def __init__(self, node_name: str): super().__init__(node_name) self._setup_parameters() self._setup_publishers() self._setup_subscribers() self._setup_services() self._setup_timers() self.get_logger().info(f'{node_name} initialized') @abstractmethod def _setup_parameters(self) -> None: """Define ROS2 parameters.""" pass @abstractmethod def _setup_publishers(self) -> None: """Create publishers.""" pass @abstractmethod def _setup_subscribers(self) -> None: """Create subscribers.""" pass def _setup_services(self) -> None: """Create services (optional).""" pass def _setup_timers(self) -> None: """Create timers (optional).""" pass def get_default_qos(self) -> QoSProfile: """Default QoS profile.""" return QoSProfile( reliability=ReliabilityPolicy.RELIABLE, history=HistoryPolicy.KEEP_LAST, depth=10 )
python# ... (Same as before, but with English comments) ... # See previous version for logic, just translate comments
cpp// infrastructure/ros2/nodes/base_node.hpp #pragma once #include <rclcpp/rclcpp.hpp> #include <string> #include <memory> namespace infrastructure::ros2::nodes { class BaseNode : public rclcpp::Node { public: explicit BaseNode(const std::string& node_name, const rclcpp::NodeOptions& options = rclcpp::NodeOptions()); virtual ~BaseNode() = default; protected: virtual void setup_parameters() = 0; virtual void setup_publishers() = 0; virtual void setup_subscribers() = 0; virtual void setup_services() {} virtual void setup_timers() {} rclcpp::QoS get_default_qos() const; }; } // namespace infrastructure::ros2::nodes
cpp// infrastructure/ros2/nodes/base_node.cpp #include "infrastructure/ros2/nodes/base_node.hpp" namespace infrastructure::ros2::nodes { BaseNode::BaseNode(const std::string& node_name, const rclcpp::NodeOptions& options) : Node(node_name, options) { // Virtual calls in constructor are dangerous in C++, // but common in ROS2 if careful or using an init() method. // Better pattern: Call these in a separate init() or distinct lifecycle. // For simplicity in this template, we assume derived classes handle initialization // or use the lifecycle node pattern. } rclcpp::QoS BaseNode::get_default_qos() const { return rclcpp::QoS(10) .reliability(rmw_qos_reliability_policy_t::RMW_QOS_POLICY_RELIABILITY_RELIABLE) .history(rmw_qos_history_policy_t::RMW_QOS_POLICY_HISTORY_KEEP_LAST); } } // namespace infrastructure::ros2::nodes
cpp// infrastructure/ros2/nodes/sensor_node.hpp #pragma once #include "infrastructure/ros2/nodes/base_node.hpp" #include "application/services/sensor_service.hpp" #include <std_msgs/msg/float64.hpp> #include <sensor_msgs/msg/temperature.hpp> namespace infrastructure::ros2::nodes { class SensorNode : public BaseNode { public: explicit SensorNode(const rclcpp::NodeOptions& options = rclcpp::NodeOptions()); // Dependency Injection void set_sensor_service(std::shared_ptr<application::services::ISensorService> service); protected: void setup_parameters() override; void setup_publishers() override; void setup_subscribers() override; void setup_timers() override; private: void raw_callback(const std_msgs::msg::Float64::SharedPtr msg); void timer_callback(); std::shared_ptr<application::services::ISensorService> sensor_service_; rclcpp::Publisher<sensor_msgs::msg::Temperature>::SharedPtr temp_pub_; rclcpp::Subscription<std_msgs::msg::Float64>::SharedPtr raw_sub_; rclcpp::TimerBase::SharedPtr timer_; double update_rate_; std::string sensor_topic_; }; } // namespace infrastructure::ros2::nodes
cpp// application/services/sensor_service.hpp #pragma once #include "domain/entities/sensor_data.hpp" namespace application::services { class ISensorService { public: virtual ~ISensorService() = default; virtual domain::entities::SensorData process(double raw_data) = 0; }; } // namespace application::services
cpp// infrastructure/ros2/qos_profiles.hpp #pragma once #include <rclcpp/qos.hpp> namespace infrastructure::ros2 { class QoSProfiles { public: static rclcpp::QoS sensor_data() { return rclcpp::QoS(1).best_effort().durability_volatile(); } static rclcpp::QoS command() { return rclcpp::QoS(10).reliable().transient_local(); } }; } // namespace
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→fail | 22,438 | 14,001 | -38% | 1 | 1 | 0% | 3,881 | 4,307 | +11% | 0 | 0 | — |
case-02 | fail→fail | 16,368 | 13,480 | -18% | 1 | 1 | 0% | 3,268 | 4,609 | +41% | 0 | 0 | — |
case-03 | fail→pass | 18,502 | 12,541 | -32% | 1 | 1 | 0% | 3,684 | 4,170 | +13% | 0 | 0 | — |
case-04 | fail→fail | 14,832 | 12,456 | -16% | 1 | 1 | 0% | 2,675 | 3,914 | +46% | 0 | 0 | — |
case-05 | pass→pass | 15,880 | 8,278 | -48% | 1 | 1 | 0% | 2,851 | 3,260 | +14% | 0 | 0 | — |
case-06 | fail→fail | 16,394 | 8,996 | -45% | 1 | 1 | 0% | 2,968 | 3,316 | +12% | 0 | 0 | — |
case-07 | pass→pass | 15,765 | 12,667 | -20% | 1 | 1 | 0% | 2,864 | 4,012 | +40% | 0 | 0 | — |
case-08 | pass→pass | 10,652 | 6,281 | -41% | 1 | 1 | 0% | 1,861 | 2,744 | +47% | 0 | 0 | — |
case-09 | fail→pass | 17,129 | 11,873 | -31% | 1 | 1 | 0% | 3,148 | 3,898 | +24% | 0 | 0 | — |
case-10 | fail→fail | 14,647 | 13,510 | -8% | 1 | 1 | 0% | 2,607 | 4,220 | +62% | 0 | 0 | — |
case-11 | fail→fail | 14,283 | 9,695 | -32% | 1 | 1 | 0% | 2,384 | 3,352 | +41% | 0 | 0 | — |
case-12 | pass→pass | 9,458 | 3,513 | -63% | 1 | 1 | 0% | 1,476 | 2,316 | +57% | 0 | 0 | — |
case-13 | fail→pass | 21,986 | 3,296 | -85% | 1 | 1 | 0% | 3,409 | 2,325 | -32% | 0 | 0 | — |
case-14 | fail→pass | 12,999 | 5,572 | -57% | 1 | 1 | 0% | 2,065 | 2,660 | +29% | 0 | 0 | — |
case-15 | fail→pass | 15,024 | 7,761 | -48% | 1 | 1 | 0% | 2,686 | 3,036 | +13% | 0 | 0 | — |
case-16 | fail→fail | 16,436 | 12,828 | -22% | 1 | 1 | 0% | 2,758 | 3,788 | +37% | 0 | 0 | — |
case-17 | pass→pass | 12,560 | 6,949 | -45% | 1 | 1 | 0% | 1,864 | 2,939 | +58% | 0 | 0 | — |
case-18 | fail→pass | 12,606 | 3,942 | -69% | 1 | 1 | 0% | 2,381 | 2,331 | -2% | 0 | 0 | — |
case-19 | pass→pass | 13,550 | 2,801 | -79% | 1 | 1 | 0% | 2,205 | 2,153 | -2% | 0 | 0 | — |
case-20 | pass→pass | 12,110 | 9,488 | -22% | 1 | 1 | 0% | 2,327 | 3,515 | +51% | 0 | 0 | — |
case-21 | pass→pass | 9,942 | 6,161 | -38% | 1 | 1 | 0% | 1,747 | 2,785 | +59% | 0 | 0 | — |
case-22 | pass→pass | 10,226 | 14,861 | +45% | 1 | 1 | 0% | 1,963 | 3,045 | +55% | 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. 22 cases were attempted. The headline lift of +27 percentage points is the difference between those two pass rates over the 22 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
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.