Participating

TLDR

EventWhat to do
Want to contributeOpen a PR
Found a bugFile a ticket on Github Issues
Found a vulnerabilityReport it privately
Feature requestDescribe what you want on Github Discussions
Want to start a discussionStart one on Github Discussions
Be aware of the ongoing developmentTake a look at the Github Project and what is being worked on

Contributing

Feature requests

Since I want DC to be community driven, go to Github discussions, start a discussion about a features you want to see and users will be able to vote for your it. Most requested features will have more attention than others.

Found a bug?

If you find a problem, first search if an issue already exists. If a related issue doesn't exist, you can open a new issue using the issue form.

Found a vulnerability?

Do not open a public issue, discussion or pull request. Report it through GitHub private vulnerability reporting instead; the security policy states the supported branches, the response targets and what is in scope.

General guidelines

You can contribute to the source code with Pull Requests, for example:

  • To fix a typo you found on the documentation.
  • To propose new documentation sections.
  • To fix an existing issue/bug.
    • Make sure to add tests.
  • To add a new feature.
    • Make sure to add tests.
    • Make sure to add documentation if it's relevant.

Setup environment

ROS

Follow the steps to build your workspace and install dependencies in the setup section

Then install the git hook. .pre-commit-config.yaml is run by prek, a single-binary reimplementation of pre-commit:

uv tool install prek   # or: curl -LsSf https://prek.j178.dev/install.sh | sh
prek install

You are now ready to write some code, commit and follow the standards with the git hook. To run every hook over the whole tree the way CI does:

prek run --all-files --skip build-doc   # drop the --skip to check the docs build too

License headers

The repo follows REUSE: every file declares its copyright and license, and the reuse hook fails if one doesn't. Sources say so in a header:

// SPDX-FileCopyrightText: 2022-2026 David Bensoussan
// SPDX-License-Identifier: MPL-2.0

reuse annotate writes it for you, in whatever comment syntax the file uses:

uvx reuse annotate --copyright "2022-2026 David Bensoussan" --license MPL-2.0 path/to/new_file.cpp
uvx reuse lint          # what the hook runs

Files that can't carry a comment — images, meshes, fonts — are covered by path in REUSE.toml, which is also where third-party assets declare their own upstream license. Add a new license there and run uvx reuse download --all to fetch its text into LICENSES/.

Docs

The doc toolchain (mdbook plus its preprocessors — mdbook-admonish, mdbook-mermaid, mdbook-open-on-gh — and mdbook-linkcheck, all pinned) runs inside a Podman image built from containers/doc/Containerfile; mdbook's preprocessor ABI isn't stable across minor versions, so an unpinned cargo install mdbook can silently pull an incompatible set. The same two scripts CI, the build-doc pre-commit hook, and a local editing loop all use:

./tools/ci/pre-commit/build_doc.sh    # one-shot build -> doc/book/html
./tools/ci/pre-commit/serve_doc.sh    # live-reloading dev server at http://127.0.0.1:3000

Open the doc folder of the repository and edit the Markdown files you need — serve_doc.sh reloads the browser on every save. Editing an ADR under docs/adr/ needs one re-run of tools/ci/pre-commit/generate_adr_pages.py first, since that's what mirrors it into doc/src/dc/adr/.

Declaring plugin parameters

Measurement and Condition plugins (dc_measurements/plugins/{measurements,conditions}/) declare their own parameters in onConfigure(). Always do this through dc_util::get_*_type_param() (dc_util/include/dc_util/node_utils.hpp) — never call declare_parameter or nav2_util::declare_parameter_if_not_declared directly. One call both declares and reads the value, and exits with a clear RCLCPP_FATAL if it can't be retrieved, instead of a hand-rolled declare/get/try-catch block per parameter:

// Mandatory (no default; fatal if not overridden):
cam_name_ = dc_util::get_str_type_param(node, measurement_name_, "cam_name");

// Optional, with a default:
polling_interval_ = dc_util::get_int_type_param(node, measurement_name_, "polling_interval", 1000);

plugin_name/measurement_name_/condition_name_ is the namespace prefix — the helper declares and reads "<plugin_name>.<param_name>". Available types: str, str_array, bool, bool_array (mandatory only), int, int_array (mandatory only), double, double_array (mandatory only). measurement_server.cpp/group_server.py-level parameters that have no plugin namespace use the equivalent dc_util::get_str_param() / get_str_array_param() (no plugin_name argument).

This single-source-of-truth convention is deliberate: see ADR-0008 for why nav2_util stays a dependency and dc_util wraps it rather than replacing it.

dc_group (Python) has no plugins and no equivalent wrapper — group_server.py declares each of its parameters exactly once via plain self.declare_parameter(...), which is sufficient there (see the ADR).

Tests

TODO...