Install any skill in seconds. Free to start, no credit card required.
Get Started Free →ROS2 bag recording and analysis utilities with Clean Architecture (Python & C++)
.claude/skills/harunkurtdev-ros2-bag-utility/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | -23% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 30% | 0% |
| case-11 | ✗→✓ | ▲ Improved | 14% | 0% |
| case-22 | ✗→✓ | ▲ Improved | 8% | 0% |
| case-04 | ✓→✓ | = Same ✓ | 26% | 0% |
This skill provides guides for programmatic data recording (rosbag2) and replay mechanism adhering to Clean Architecture.
Defines the interface for recording and playback control.
python# domain/interfaces/data_recorder.py class IDataRecorder(ABC): @abstractmethod def start_recording(self, config: RecordingConfig) -> bool: pass @abstractmethod def stop_recording(self) -> None: pass
See previous Python example using rosbag2_py.
Using rosbag2_cpp library.
cpp// infrastructure/ros2/bag/bag_recorder.hpp #pragma once #include <rclcpp/rclcpp.hpp> #include <rosbag2_cpp/writer.hpp> #include <rosbag2_storage/storage_options.hpp> namespace infrastructure::ros2::bag { class BagRecorder { public: explicit BagRecorder(rclcpp::Node::SharedPtr node) : node_(node) {} bool start_recording(const std::string& bag_name, const std::vector<std::string>& topics) { try { writer_ = std::make_unique<rosbag2_cpp::Writer>(); rosbag2_storage::StorageOptions storage_options; storage_options.uri = bag_name; storage_options.storage_id = "sqlite3"; rosbag2_cpp::ConverterOptions converter_options; converter_options.input_serialization_format = "cdr"; converter_options.output_serialization_format = "cdr"; writer_->open(storage_options, converter_options); // Subscribe to topics and write for (const auto& topic : topics) { // Determine topic type dynamically or use GenericSubscribe (Humble+) create_recorder_subscription(topic); } is_recording_ = true; return true; } catch (const std::exception& e) { RCLCPP_ERROR(node_->get_logger(), "Failed to open bag: %s", e.what()); return false; } } void stop_recording() { writer_.reset(); // Closes the bag subs_.clear(); is_recording_ = false; } template<typename T> void write_message(const std::string& topic, const T& msg) { if (is_recording_ && writer_) { auto timestamp = node_->get_clock()->now(); writer_->write(msg, topic, timestamp); } } private: rclcpp::Node::SharedPtr node_; std::unique_ptr<rosbag2_cpp::Writer> writer_; std::vector<rclcpp::SubscriptionBase::SharedPtr> subs_; bool is_recording_ = false; void create_recorder_subscription(const std::string& topic) { // Implementation for generic subscription... } }; } // namespace
cpp// infrastructure/ros2/bag/bag_reader.hpp #include <rosbag2_cpp/reader.hpp> class BagReader { public: explicit BagReader(const std::string& bag_path) { reader_ = std::make_unique<rosbag2_cpp::Reader>(); reader_->open(bag_path); } void read_all() { while (reader_->has_next()) { auto bag_message = reader_->read_next(); // Deserialization logic... } } private: std::unique_ptr<rosbag2_cpp::Reader> reader_; };
sqlite3 is default, mcap is recommended for performance.cdr.| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-04 | pass→pass | 20,119 | 18,923 | -6% | 1 | 1 | 0% | 3,870 | 4,884 | +26% | 0 | 0 | — |
case-01 | fail→pass | 26,014 | 15,998 | -39% | 1 | 1 | 0% | 5,497 | 4,252 | -23% | 0 | 0 | — |
case-02 | fail→fail | 21,211 | 13,751 | -35% | 1 | 1 | 0% | 4,447 | 3,812 | -14% | 0 | 0 | — |
case-03 | fail→fail | 21,301 | 16,189 | -24% | 1 | 1 | 0% | 4,358 | 4,405 | +1% | 0 | 0 | — |
case-05 | pass→pass | 17,008 | 11,364 | -33% | 1 | 1 | 0% | 3,654 | 3,345 | -8% | 0 | 0 | — |
case-06 | pass→pass | 13,721 | 12,613 | -8% | 1 | 1 | 0% | 2,810 | 3,535 | +26% | 0 | 0 | — |
case-07 | fail→fail | 15,407 | 9,470 | -39% | 1 | 1 | 0% | 2,957 | 2,653 | -10% | 0 | 0 | — |
case-08 | pass→pass | 7,605 | 3,720 | -51% | 1 | 1 | 0% | 1,359 | 1,588 | +17% | 0 | 0 | — |
case-09 | pass→pass | 8,121 | 3,463 | -57% | 1 | 1 | 0% | 1,373 | 1,532 | +12% | 0 | 0 | — |
case-10 | fail→pass | 10,628 | 8,932 | -16% | 1 | 1 | 0% | 2,100 | 2,739 | +30% | 0 | 0 | — |
case-11 | fail→pass | 14,934 | 11,361 | -24% | 1 | 1 | 0% | 2,480 | 2,827 | +14% | 0 | 0 | — |
case-12 | fail→fail | 18,122 | 12,702 | -30% | 1 | 1 | 0% | 3,692 | 3,738 | +1% | 0 | 0 | — |
case-13 | pass→pass | 15,615 | 9,339 | -40% | 1 | 1 | 0% | 2,385 | 2,527 | +6% | 0 | 0 | — |
case-14 | pass→pass | 3,109 | 2,231 | -28% | 1 | 1 | 0% | 526 | 1,281 | +144% | 0 | 0 | — |
case-15 | pass→pass | 2,921 | 2,879 | -1% | 1 | 1 | 0% | 528 | 1,414 | +168% | 0 | 0 | — |
case-16 | pass→pass | 3,425 | 2,018 | -41% | 1 | 1 | 0% | 581 | 1,240 | +113% | 0 | 0 | — |
case-17 | pass→pass | 16,208 | 15,904 | -2% | 1 | 1 | 0% | 2,446 | 3,574 | +46% | 0 | 0 | — |
case-18 | pass→pass | 14,701 | 14,615 | -1% | 1 | 1 | 0% | 2,321 | 3,267 | +41% | 0 | 0 | — |
case-19 | pass→pass | 9,009 | 2,418 | -73% | 1 | 1 | 0% | 1,362 | 1,311 | -4% | 0 | 0 | — |
case-20 | fail→fail | 19,243 | 15,724 | -18% | 1 | 1 | 0% | 3,316 | 3,780 | +14% | 0 | 0 | — |
case-21 | pass→pass | 16,394 | 11,047 | -33% | 1 | 1 | 0% | 2,982 | 2,662 | -11% | 0 | 0 | — |
case-22 | fail→pass | 7,692 | 3,271 | -57% | 1 | 1 | 0% | 1,315 | 1,426 | +8% | 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 +18 percentage points is the difference between those two pass rates over the 22 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.