Participating
TLDR
| Event | What to do |
|---|---|
| Want to contribute | Open a PR |
| Found a bug | File a ticket on Github Issues |
| Found a vulnerability | Report it privately |
| Feature request | Describe what you want on Github Discussions |
| Want to start a discussion | Start one on Github Discussions |
| Be aware of the ongoing development | Take 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...