Skills DirectorySkills Directory
SkillsLearnSecurityCategoriesDocsCommunityBlog
Sign InSubmit Skill
Skills Directory

Security-tested agent skills for Claude, coding agents, and AI workflows.

Directory

  • Browse Skills
  • All Skills A–Z
  • Claude Skills
  • Claude Code Skills
  • Agent Skills
  • Categories
  • Submit a Skill

Learn

  • Learn Hub
  • Install Claude Skills
  • Write SKILL.md
  • Skills vs MCP
  • Directories Compared

Security

  • Security
  • Methodology
  • Secure Claude Skills
  • Security Badges

Company

  • About
  • Community
  • Blog
  • API Docs
  • Advertise

2026 Skills Directory. All rights reserved.

Back to skills

Ros2 Workspace

ASecurity

Use when creating, structuring, or modifying ROS 2 (Humble/Jazzy) packages and workspaces — package.xml/CMakeLists.txt conventions, Python (rclpy) and C++ (rclcpp) node patterns, executors, launch files, and colcon build/test workflows.

2 stars
0 votes
0 copies
0 views
Added 9/19/2026
ai-agentspythongoc++bashnodetestinggit

Security Analysis

A100/100

Scanned 9/19/2026

Install to Claude Code

$npx -y skills add enesbirlik/claude-code-robotics --skill ros2-workspace --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Ros2 Workspace?

Add the live security badge to your README — it updates automatically with every re-scan.

Security grade badge for Ros2 Workspace
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/enesbirlik-ros2-workspace/badge)](https://www.skillsdirectory.com/skills/enesbirlik-ros2-workspace)

More formats (shields.io, HTML) on the badges page.

Download Zip
Files
SKILL.md
---
name: ros2-workspace
description: Use when creating, structuring, or modifying ROS 2 (Humble/Jazzy) packages and workspaces — package.xml/CMakeLists.txt conventions, Python (rclpy) and C++ (rclcpp) node patterns, executors, launch files, and colcon build/test workflows.
---

# ROS 2 Workspace Engineering

Rules and templates for producing ROS 2 packages that build cleanly with `colcon`, pass
`ament_lint`, and follow the conventions expected in Humble and Jazzy.

## 1. Workspace layout

```
ros2_ws/
├── src/
│   ├── my_robot_bringup/          # launch + top-level config, no code
│   ├── my_robot_description/      # URDF/Xacro + meshes (see urdf-xacro-builder skill)
│   ├── my_robot_msgs/             # custom .msg/.srv/.action, ament_cmake only
│   └── my_robot_controller/       # the actual node(s), ament_cmake or ament_python
├── install/                       # generated by colcon build — never hand-edit, gitignore it
├── build/                         # generated — gitignore it
└── log/                           # generated — gitignore it
```

Rules:
- One package = one clear responsibility (bringup, description, msgs, driver, algorithm).
  Never mix message/service/action definitions into a package that also has other build
  dependents — put custom interfaces in their own `_msgs` or `_interfaces` package to avoid
  circular dependencies.
- Package names are `snake_case`, never start with a digit, and should be prefixed with the
  robot/project name (`my_robot_controller`, not `controller`).
- Never commit `build/`, `install/`, or `log/` — the plugin's `.gitignore` template already
  excludes them.

## 2. `package.xml` (format 3)

```xml
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
  <name>my_robot_controller</name>
  <version>0.1.0</version>
  <description>Velocity controller node for my_robot.</description>
  <maintainer email="you@example.com">Your Name</maintainer>
  <license>Apache-2.0</license>

  <buildtool_depend>ament_cmake</buildtool_depend>

  <depend>rclcpp</depend>
  <depend>geometry_msgs</depend>
  <depend>sensor_msgs</depend>

  <test_depend>ament_lint_auto</test_depend>
  <test_depend>ament_lint_common</test_depend>

  <export>
    <build_type>ament_cmake</build_type>
  </export>
</package>
```

For a Python package, use `<build_type>ament_python</build_type>` and
`<buildtool_depend>ament_python</buildtool_depend>`.

Rules:
- Every runtime import/link dependency is a `<depend>` (or split `<build_depend>` /
  `<exec_depend>` only when build-time and run-time deps genuinely differ — otherwise prefer
  the combined `<depend>` tag for readability).
- Always declare `ament_lint_auto` + `ament_lint_common` as `test_depend` so `colcon test` runs
  static checks (`ament_cpplint`, `ament_flake8`, `ament_xmllint`, ...).
- `<version>` follows SemVer and should be bumped alongside `CHANGELOG.rst` entries.

## 3. C++ package: `CMakeLists.txt`

```cmake
cmake_minimum_required(VERSION 3.8)
project(my_robot_controller)

if(NOT CMAKE_CXX_STANDARD)
  set(CMAKE_CXX_STANDARD 17)
  set(CMAKE_CXX_STANDARD_REQUIRED ON)
endif()
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()

find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(geometry_msgs REQUIRED)
find_package(sensor_msgs REQUIRED)

add_executable(velocity_controller_node
  src/velocity_controller_node.cpp
)
ament_target_dependencies(velocity_controller_node
  rclcpp
  geometry_msgs
  sensor_msgs
)
target_include_directories(velocity_controller_node PRIVATE
  $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
)

install(TARGETS velocity_controller_node
  DESTINATION lib/${PROJECT_NAME}
)
install(DIRECTORY launch config
  DESTINATION share/${PROJECT_NAME}
  OPTIONAL
)

if(BUILD_TESTING)
  find_package(ament_lint_auto REQUIRED)
  ament_lint_auto_find_test_dependencies()
endif()

ament_package()
```

Rules:
- Warnings-as-errors are opt-in per repo, but `-Wall -Wextra -Wpedantic` is always on.
- Every installed executable target gets an explicit `install(TARGETS ...)`. Every shared
  `launch/`, `config/`, `urdf/`, `meshes/` directory gets an `install(DIRECTORY ...)`.
- `ament_package()` must be the last line.

## 4. Python package: `setup.py` / `setup.cfg`

```python
# setup.py
from setuptools import find_packages, setup

package_name = 'my_robot_controller'

setup(
    name=package_name,
    version='0.1.0',
    packages=find_packages(exclude=['test']),
    data_files=[
        ('share/ament_index/resource_index/packages',
            ['resource/' + package_name]),
        ('share/' + package_name, ['package.xml']),
        ('share/' + package_name + '/launch', ['launch/controller.launch.py']),
    ],
    install_requires=['setuptools'],
    zip_safe=True,
    maintainer='Your Name',
    maintainer_email='you@example.com',
    description='Velocity controller node for my_robot.',
    license='Apache-2.0',
    tests_require=['pytest'],
    entry_points={
        'console_scripts': [
            'velocity_controller_node = my_robot_controller.velocity_controller_node:main',
        ],
    },
)
```

```ini
# setup.cfg
[develop]
script_dir=$base/lib/my_robot_controller
[install]
install_scripts=$base/lib/my_robot_controller
```

Rules:
- Every `console_scripts` entry point maps `executable_name = package.module:main`; the
  `main()` function must exist and take no required arguments.
- Don't forget the `resource/<package_name>` marker file (empty) — without it `ros2 pkg list`
  and `ros2 run` won't find the package.

## 5. rclpy node template (Python)

```python
#!/usr/bin/env python3
from rclpy.node import Node
from rclpy.qos import QoSProfile, ReliabilityPolicy, HistoryPolicy
from geometry_msgs.msg import Twist
from sensor_msgs.msg import LaserScan
import rclpy


class VelocityControllerNode(Node):
    def __init__(self) -> None:
        super().__init__('velocity_controller_node')

        self.declare_parameter('max_linear_speed', 0.5)
        self.declare_parameter('scan_topic', '/scan')
        self._max_linear_speed = self.get_parameter('max_linear_speed').value

        sensor_qos = QoSProfile(
            reliability=ReliabilityPolicy.BEST_EFFORT,
            history=HistoryPolicy.KEEP_LAST,
            depth=5,
        )
        self._scan_sub = self.create_subscription(
            LaserScan,
            self.get_parameter('scan_topic').value,
            self._on_scan,
            sensor_qos,
        )
        self._cmd_pub = self.create_publisher(Twist, 'cmd_vel', 10)
        self._timer = self.create_timer(0.1, self._on_timer)

    def _on_scan(self, msg: LaserScan) -> None:
        self._latest_scan = msg

    def _on_timer(self) -> None:
        cmd = Twist()
        cmd.linear.x = self._max_linear_speed
        self._cmd_pub.publish(cmd)


def main(args: list[str] | None = None) -> None:
    rclpy.init(args=args)
    node = VelocityControllerNode()
    try:
        rclpy.spin(node)
    except KeyboardInterrupt:
        pass
    finally:
        node.destroy_node()
        rclpy.shutdown()


if __name__ == '__main__':
    main()
```

Rules:
- Always declare parameters with `declare_parameter` before reading them — never assume a
  default exists implicitly.
- Choose QoS explicitly. Sensor data: `BEST_EFFORT` + shallow `KEEP_LAST` depth. Commands and
  state: `RELIABLE` + `KEEP_LAST` with a depth matched to consumer speed. Never leave QoS at
  an implicit default when publisher and subscriber are in different packages — mismatches
  silently drop all messages.
- Use `rclpy.spin(node)` for single-purpose nodes; use `MultiThreadedExecutor` with explicit
  `ReentrantCallbackGroup`/`MutuallyExclusiveCallbackGroup` assignment only when you have
  callbacks that must run concurrently (e.g. a long-running service alongside a fast timer) —
  don't reach for it by default, it adds real concurrency hazards.
- Always `destroy_node()` then `rclpy.shutdown()` in a `finally` block so Ctrl+C and exceptions
  both clean up the node's middleware resources.

## 6. rclcpp node template (C++)

```cpp
#include <memory>
#include <rclcpp/rclcpp.hpp>
#include <geometry_msgs/msg/twist.hpp>
#include <sensor_msgs/msg/laser_scan.hpp>

class VelocityControllerNode : public rclcpp::Node
{
public:
  VelocityControllerNode()
  : Node("velocity_controller_node")
  {
    max_linear_speed_ = this->declare_parameter("max_linear_speed", 0.5);
    const auto scan_topic = this->declare_parameter("scan_topic", std::string("/scan"));

    auto sensor_qos = rclcpp::SensorDataQoS();
    scan_sub_ = this->create_subscription<sensor_msgs::msg::LaserScan>(
      scan_topic, sensor_qos,
      std::bind(&VelocityControllerNode::onScan, this, std::placeholders::_1));

    cmd_pub_ = this->create_publisher<geometry_msgs::msg::Twist>("cmd_vel", 10);
    timer_ = this->create_wall_timer(
      std::chrono::milliseconds(100),
      std::bind(&VelocityControllerNode::onTimer, this));
  }

private:
  void onScan(const sensor_msgs::msg::LaserScan::SharedPtr msg)
  {
    latest_scan_ = msg;
  }

  void onTimer()
  {
    geometry_msgs::msg::Twist cmd;
    cmd.linear.x = max_linear_speed_;
    cmd_pub_->publish(cmd);
  }

  double max_linear_speed_;
  sensor_msgs::msg::LaserScan::SharedPtr latest_scan_;
  rclcpp::Subscription<sensor_msgs::msg::LaserScan>::SharedPtr scan_sub_;
  rclcpp::Publisher<geometry_msgs::msg::Twist>::SharedPtr cmd_pub_;
  rclcpp::TimerBase::SharedPtr timer_;
};

int main(int argc, char ** argv)
{
  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared<VelocityControllerNode>());
  rclcpp::shutdown();
  return 0;
}
```

Rules:
- Prefer composition-friendly node classes (inherit `rclcpp::Node`, no raw `main`-only logic)
  so the node can later be loaded as a component without rewriting it.
- Use `rclcpp::SensorDataQoS()` / `rclcpp::SystemDefaultsQoS()` helpers instead of hand-rolling
  `rclcpp::QoS` profiles unless you need a genuinely custom depth/durability.
- Never call `shared_from_this()` inside a constructor — the `shared_ptr` doesn't exist yet.
  If you need it (e.g. to register a callback that captures `this` as a `shared_ptr`), do it
  from a separate `init()` method called right after construction.
- Use `rclcpp::executors::MultiThreadedExecutor` with explicit `CallbackGroup`s only when you
  have a proven concurrency need (e.g. a blocking service call must not stall a control-loop
  timer); default to the implicit single-threaded `rclcpp::spin`.

## 7. Launch files

```python
from launch import LaunchDescription
from launch.actions import DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration, PathJoinSubstitution
from launch_ros.actions import Node
from launch_ros.substitutions import FindPackageShare


def generate_launch_description() -> LaunchDescription:
    max_speed_arg = DeclareLaunchArgument(
        'max_linear_speed', default_value='0.5',
        description='Maximum commanded linear speed (m/s).',
    )

    controller_node = Node(
        package='my_robot_controller',
        executable='velocity_controller_node',
        name='velocity_controller_node',
        output='screen',
        parameters=[{
            'max_linear_speed': LaunchConfiguration('max_linear_speed'),
        }],
    )

    return LaunchDescription([max_speed_arg, controller_node])
```

Rules:
- Every tunable value exposed as a `DeclareLaunchArgument`, never hardcoded, so the same
  launch file works in simulation and on hardware.
- Use `FindPackageShare` + `PathJoinSubstitution` to locate config/URDF files — never a
  hardcoded absolute path.
- Prefer one launch file per package that composes into a top-level `bringup` launch file via
  `IncludeLaunchDescription`, instead of one monolithic launch file per robot.

## 8. colcon build & test workflow

```bash
# From the workspace root (parent of src/):
colcon build --symlink-install                     # symlink python/config for fast iteration
colcon build --packages-select my_robot_controller  # scope a build to one package
colcon test --packages-select my_robot_controller
colcon test-result --verbose
source install/setup.bash                            # re-source after every structural change
```

Rules:
- Use `--symlink-install` in development workspaces so Python source and non-compiled
  resources update without a rebuild.
- Scope builds with `--packages-select` while iterating; only build the whole workspace before
  a final integration check.
- Always re-run `colcon test` after touching `package.xml` dependencies or CMake install rules
  — `ament_lint` catches missing/unused dependencies that only surface at test time.
- This plugin's `colcon_build_check` hook automatically runs a scoped `colcon build
  --packages-select <pkg>` whenever a new node source file is created inside a detected colcon
  workspace — no need to trigger it manually during normal editing.

Attribution

enesbirlikenesbirlik
View sourceMore from enesbirlik →
SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments (0)

No comments yet. Be the first to comment!

SSkills DirectorySkills Directory

Ship a skill? Prove it's safe.

Free 120-pattern security scan, letter grade, and an embeddable README badge.

Submit a skill

Related Skills

Caveman

Ultra-compressed communication mode. Cuts token usage ~75% by speaking like caveman while keeping full technical accuracy. Supports intensity levels: lite, full (default), ultra, wenyan-lite, wenyan-full, wenyan-ultra. Use when user says "caveman mode", "talk like caveman", "use caveman", "less tokens", "be brief", or invokes /caveman. Also auto-triggers when token efficiency is requested.

1023331 votes

Hyperplan

Adversarial multi-agent planning skill. Self-orchestrates 5 hostile category members (unspecified-low, unspecified-high, deep, ultrabrain, artistry) via team-mode for ruthless cross-critique debate, distills only the defensible insights, then MANDATORILY hands the distilled insight bundle to the `plan` agent for executable plan formalization. Use when planning needs maximum rigor and surfacing of weak assumptions, blind spots, and over-engineering. Triggers: 'hyperplan', 'hpp', '/hyperplan', ...

686011 votes

Mcp Code Execution

Routes multi-tool workflows through MCP servers for large datasets and pipelines. Use when Bash tool overhead is limiting throughput on data-heavy tasks.

3331 votes

catchup

Recovers prior coding-agent session context by running `catchup <agent> --since-compact`, which extracts a clean summary of a previous Codex, Claude Code, Antigravity, OpenCode, or Pi Agent session. Use when the user says "catch up", "what did the last session do", "get me up to speed", "I switched agents", or asks to recover/summarize a previous session before continuing. Do NOT use for the current conversation, git history, or any non-agent log.

611 votes

math-skill

A comprehensive mathematical reasoning skill for AI assistants — handles arithmetic to research-level problems with rigorous step-by-step reasoning, systematic verification, and transparent uncertainty handling

381 votes
View all in ai-agents →