Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Scaffold a new ROS 2 package (ament_python or ament_cmake) inside a colcon workspace, restructured to follow this template's Clean Architecture layout. Trigger when the user asks to create a new ROS 2 package or to bootstrap a Clean-Architecture-compliant codebase.
.claude/skills/harunkurtdev-new-ros2-package/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 6% | 0% |
| case-02 | ✗→✓ | ▲ Improved | -34% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 16% | 0% |
| case-05 | ✗→✓ | ▲ Improved | 47% | 0% |
| case-09 | ✗→✓ | ▲ Improved | 62% | 0% |
This is the canonical recipe for adding a new package under src/ of a colcon workspace. It assumes the workspace already exists; if it does not, ask the user to run mkdir -p ~/<ws>/src && cd ~/<ws> first.
ament_python or ament_cmake. If the packagecontains heavy computation, hardware drivers, or Nav 2 plugins, choose ament_cmake. If it's an application-level orchestrator (lifecycle launchers, mission scripts, glue code), ament_python is fine.
Architecture layers. The cost of empty domain/ and application/ folders is zero, and it forces correct placement when behaviour grows.
services, or actions, create a separate <name>_msgs package first (ament_cmake with rosidl_default_generators). The implementation package then <depend>s on it.
bash# from <workspace>/src ros2 pkg create \ --build-type ament_python \ --license Apache-2.0 \ --maintainer-name "$(git config user.name)" \ --maintainer-email "$(git config user.email)" \ <package_name>
For C++ replace ament_python with ament_cmake. The default ros2 pkg create output is a starting point, not a destination — we override the structure in the next step.
ament_python)src/<name>/
├── package.xml
├── setup.py
├── setup.cfg
├── resource/<name>
├── <name>/
│ ├── __init__.py
│ ├── domain/ # pure logic
│ │ ├── __init__.py
│ │ ├── entities/
│ │ ├── value_objects/
│ │ └── ports/ # abstract interfaces
│ ├── application/ # use cases
│ │ ├── __init__.py
│ │ └── use_cases/
│ ├── infrastructure/ # ROS 2 adapters
│ │ ├── __init__.py
│ │ ├── nodes/
│ │ ├── publishers/
│ │ ├── subscribers/
│ │ └── tf/
│ └── presentation/ # CLI, launch entrypoints
│ ├── __init__.py
│ └── main.py
├── launch/ # *.launch.py
├── config/ # *.yaml parameter files
└── test/
├── unit/
├── integration/
└── launch/ament_cmake)src/<name>/
├── package.xml
├── CMakeLists.txt
├── include/<name>/
│ ├── domain/
│ ├── application/
│ ├── infrastructure/
│ └── presentation/
├── src/
│ ├── domain/
│ ├── application/
│ ├── infrastructure/
│ └── presentation/
├── launch/
├── config/
└── test/
├── unit/
├── integration/
└── launch/Use .gitkeep files to hold empty directories so the layout survives git operations.
package.xml essentialsAlways include the default ament linters as test deps so colcon test exercises them:
xml<test_depend>ament_lint_auto</test_depend> <test_depend>ament_lint_common</test_depend>
For Python add <exec_depend>rclpy</exec_depend> and <exec_depend>launch_ros</exec_depend>. For C++ add <depend>rclcpp</depend> (and <depend>rclcpp_lifecycle</depend> if any node is lifecycle-managed).
Add only the deps you actually use. The reviewer agent will flag orphans.
setup.py (Python) install rulespythondata_files=[ ('share/ament_index/resource_index/packages', ['resource/' + package_name]), ('share/' + package_name, ['package.xml']), (os.path.join('share', package_name, 'launch'), glob('launch/*.launch.py')), (os.path.join('share', package_name, 'config'), glob('config/*.yaml')), ], entry_points={ 'console_scripts': [ # Filled in by /new-node ], },
CMakeLists.txt (C++) install rulescmakeinstall(DIRECTORY launch config DESTINATION share/${PROJECT_NAME}) ament_export_include_directories(include) # Library targets and rclcpp_components_register_nodes are added by # /new-node when the first node lands.
For pluginlib-exposed code add:
cmakepluginlib_export_plugin_description_file(<base_pkg> plugins.xml)
Drop in a single passing test so colcon test is meaningful from day one:
python# test/unit/test_smoke.py def test_smoke(): assert True
cpp// test/unit/smoke_test.cpp #include <gtest/gtest.h> TEST(Smoke, Compiles) { SUCCEED(); }
Wire them into setup.py (pytest auto-discovers) or CMakeLists.txt:
cmakeif(BUILD_TESTING) find_package(ament_lint_auto REQUIRED) ament_lint_auto_find_test_dependencies() ament_add_gtest(${PROJECT_NAME}_smoke_test test/unit/smoke_test.cpp) endif()
Nothing to do at the workspace level (colcon autodiscovers). Just run /build <name> to verify the skeleton compiles before adding behaviour.
pre-commit run --files <every file you touched> before declaringdone.
it builds — downstream packages will fail loudly otherwise.
CHANGELOG.rst if the projectpublishes one.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 18,071 | 11,386 | -37% | 1 | 1 | 0% | 3,872 | 4,091 | +6% | 0 | 0 | — |
case-02 | fail→pass | 26,699 | 11,058 | -59% | 1 | 1 | 0% | 5,897 | 3,868 | -34% | 0 | 0 | — |
case-03 | fail→pass | 16,077 | 11,686 | -27% | 1 | 1 | 0% | 3,524 | 4,078 | +16% | 0 | 0 | — |
case-04 | pass→pass | 21,450 | 20,328 | -5% | 1 | 1 | 0% | 4,705 | 5,861 | +25% | 0 | 0 | — |
case-05 | fail→pass | 12,158 | 23,069 | +90% | 1 | 1 | 0% | 2,711 | 3,974 | +47% | 0 | 0 | — |
case-06 | pass→pass | 6,110 | 4,111 | -33% | 1 | 1 | 0% | 1,159 | 2,231 | +92% | 0 | 0 | — |
case-07 | pass→pass | 10,999 | 10,855 | -1% | 1 | 1 | 0% | 2,233 | 3,829 | +71% | 0 | 0 | — |
case-08 | pass→pass | 16,845 | 10,773 | -36% | 1 | 1 | 0% | 3,582 | 3,843 | +7% | 0 | 0 | — |
case-09 | fail→pass | 10,944 | 8,341 | -24% | 1 | 1 | 0% | 2,037 | 3,302 | +62% | 0 | 0 | — |
case-10 | fail→pass | 13,280 | 11,429 | -14% | 1 | 1 | 0% | 2,563 | 3,830 | +49% | 0 | 0 | — |
case-11 | fail→pass | 6,113 | 5,553 | -9% | 1 | 1 | 0% | 1,235 | 2,605 | +111% | 0 | 0 | — |
case-12 | pass→pass | 7,876 | 6,462 | -18% | 1 | 1 | 0% | 1,555 | 2,840 | +83% | 0 | 0 | — |
case-13 | pass→pass | 7,768 | 6,083 | -22% | 1 | 1 | 0% | 1,582 | 2,767 | +75% | 0 | 0 | — |
case-14 | fail→pass | 11,640 | 5,489 | -53% | 1 | 1 | 0% | 2,349 | 2,549 | +9% | 0 | 0 | — |
case-15 | pass→pass | 9,030 | 4,807 | -47% | 1 | 1 | 0% | 1,794 | 2,366 | +32% | 0 | 0 | — |
case-16 | fail→pass | 25,613 | 6,884 | -73% | 1 | 1 | 0% | 1,326 | 2,847 | +115% | 0 | 0 | — |
case-17 | pass→pass | 9,745 | 5,457 | -44% | 1 | 1 | 0% | 1,824 | 2,507 | +37% | 0 | 0 | — |
case-18 | pass→pass | 12,424 | 4,774 | -62% | 1 | 1 | 0% | 2,101 | 2,369 | +13% | 0 | 0 | — |
case-19 | fail→pass | 3,221 | 2,336 | -27% | 1 | 1 | 0% | 556 | 1,902 | +242% | 0 | 0 | — |
case-20 | pass→pass | 2,608 | 1,958 | -25% | 1 | 1 | 0% | 504 | 1,889 | +275% | 0 | 0 | — |
case-21 | fail→pass | 17,320 | 3,935 | -77% | 1 | 1 | 0% | 2,914 | 2,177 | -25% | 0 | 0 | — |
case-22 | pass→pass | 2,926 | 2,683 | -8% | 1 | 1 | 0% | 415 | 1,917 | +362% | 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, and 21 counted toward the lift figure. The other 1 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +50 percentage points is the difference between those two pass rates over the 21 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.