ros2inspector
Static ROS 2 architecture analysis, visualization, and policy checks.
ros2inspector scans supported Python, C++, interface, and launch-file patterns in a ROS 2 workspace. It builds an evidence-backed architecture graph, renders that graph in several formats, and enforces architecture policies in CI.
The current alpha ships eleven commands: scan, packages, nodes, graph, viz, audit, validate, docs, watch, doctor, and design.
Website and documentation: ros2-inspector-theta.vercel.app
Features
- Static node and endpoint discovery with Python AST and Tree-sitter C++ parsers
- Separate source-node definitions and launch deployment instances in the architecture graph
- Python
console_scripts/entry-point mapping for evidence-based launch executable matching .msg,.srv, and.actioninterface parsing and type resolution- Python, XML, and YAML launch-file analysis with per-node remapping
- NetworkX Unified Architecture Model for packages, nodes, topics, services, actions, and interfaces
- Content-fingerprinted incremental analysis cache backed by diskcache
- Rich table, JSON, YAML, Mermaid, DOT, and interactive HTML output
- Built-in connectivity audit and YAML policy enforcement
Analysis scope
ROS 2 Inspector analyzes source files without executing or building the workspace. Unsupported or ambiguous constructs are reported through evidence, confidence, dynamic-name flags, or diagnostics rather than presented as complete architectural truth. Launch substitutions and unresolved interface types remain explicitly unresolved instead of being guessed from similar names.
Python source decoding follows encoding cookies and BOMs. Parser resource limits and invalid source produce per-file source_parse_failed diagnostics; other files remain available, including on cached runs. Nodes prints these diagnostics on stderr and audit JSON includes them. A publisher, subscription, service, client or action endpoint that cannot be attributed to a node hides only the missing-peer findings it could explain: same kind and direction, and the same name when the name is a literal. Files that fail to parse do not switch connectivity checks off; each missing-peer finding instead notes how many files could not be read. Package-local node inheritance and literal procedural Node()/rclpy.create_node() assignments are supported; unresolved receivers or bases carry incomplete-analysis notes.
C++ source scanning, cache invalidation and watch share these extensions: .c++, .cpp, .cxx, .cc, .C, .hpp, .hh, .hxx, .h++, .h, .ipp, .tpp.
Installation
Requires Python 3.10 or newer. A ROS 2 installation is not required for source analysis.
For a globally available CLI in an isolated environment, use pipx:
pipx install ros2inspector
Run it once without keeping it installed:
pipx run ros2inspector --help
Inside an existing Python virtual environment, use pip:
pip install ros2inspector
Install from source for development:
git clone https://github.com/aminebensaid66/ros2_inspector
cd ros2_inspector
pip install -e ".[dev]"
Quickstart
# Workspace summary
ros2inspector scan
# Package metadata and dependencies
ros2inspector packages --show-deps
# Nodes and their connections
ros2inspector nodes --show-connections
# Communication graph as Mermaid
ros2inspector graph comms --format mermaid
# Interactive HTML graph
ros2inspector viz --no-open
# Built-in architecture checks
ros2inspector audit
# Policy enforcement
ros2inspector validate --policy ros2inspector_policy.yaml
Commands
scan
ros2inspector scan [PATH] [--format table|json|yaml]
Discovers ROS 2 packages and displays a health-scored workspace summary.
packages
ros2inspector packages [-C PATH] [--filter cpp|python|meta] [--sort name|score|version] [--show-deps] [--format table|json|yaml]
Shows package metadata, type, license, maintainers, health score, and optional dependency details.
nodes
ros2inspector nodes [-C PATH] [--format table|json|yaml] [-p PACKAGE] [-o FILE] [--show-connections] [--no-cache]
Lists discovered nodes and their publishers, subscriptions, services, clients, action servers, and action clients. --show-connections includes resolved peer connections and interface types.
graph
ros2inspector graph [deps|comms|full] [-C PATH] [--format mermaid|dot|json] [-p PACKAGE] [-o FILE] [--no-cache]
Graph views:
| View | Contents |
|---|---|
deps |
Package dependencies |
comms |
Node-to-topic, service, and action communication |
full |
Dependency and communication relationships together |
In Mermaid, DOT and viz output, arrows follow the data or request: publisher → topic → subscriber, and client → service/action → provider. JSON output keeps the model's orientation, where every endpoint edge points from the node to the topic, service or action.
Examples:
ros2inspector graph deps --format json
ros2inspector graph comms --format mermaid -o architecture.mmd
ros2inspector graph full --format dot | dot -Tsvg -o architecture.svg
viz
ros2inspector viz [-C PATH] [-o FILE] [--open|--no-open] [--no-cache]
Generates a self-contained interactive Cytoscape.js HTML graph.
audit
ros2inspector audit [PATH] [--format table|json|yaml] [--fail-on error|warning|info] [--strict] [--exclude NAME] [--no-cache]
Runs built-in connectivity checks for orphan topics, dead outputs, isolated nodes, and unmatched service or action endpoints.
validate
ros2inspector validate [PATH] --policy FILE [--fail-on error|warning|info] [--format table|json|yaml] [--no-cache]
Validates the workspace against a YAML policy file.
docs
ros2inspector docs [PATH | -C PATH] [-o DIR] [--format markdown|html] [--force] [--no-cache]
Writes architecture documentation: an index with summary, package and node tables, and one page per package with dependencies and endpoints. Markdown output includes Mermaid dependency and communication diagrams. Output defaults to <workspace>/ros2inspector_docs. Pages for removed packages are deleted on the next run, Markdown and HTML pages can share one directory, and the command refuses to write into a non-empty directory it did not create unless --force is given. Set SOURCE_DATE_EPOCH for reproducible output.
watch
ros2inspector watch [PATH | -C PATH] [--policy FILE | --audit] [-i SECONDS] [--fail-on error|warning|info] [--once] [--verbose] [--no-cache]
Re-runs policy validation (or the built-in audit with --audit) whenever a source, interface, launch, config or build file changes, or a README or test file that affects a package's health score. After the first run it prints only new and resolved findings. Editing the policy file reloads it. The default policy is <workspace>/ros2inspector_policy.yaml. --once runs a single validation for scripts. Exit codes: 0 clean, 1 findings at or above --fail-on, 2 invalid policy, 3 workspace or analysis error, 130 stopped before the first run finished.
doctor
ros2inspector doctor [PATH | -C PATH] [--json]
Read-only diagnostics: versions, workspace and source roots, package count, unparsable or duplicate package.xml files, ROS_DISTRO, required modules, the bundled visualization asset and whether the cache is writable. Exits 1 when any check fails; warnings are informational.
design
ros2inspector design [-o DIR] [-p PORT] [--open|--no-open]
Opens a local browser-based package designer (bound to 127.0.0.1, protected by a per-session token). Add packages and nodes, give them publishers, subscribers, services, clients and action servers or clients, wire publishers to subscribers, then Generate to write ready-to-build ament_python packages into DIR (use your workspace's src/). The design is saved in DIR/.ros2inspector_design.json.
Renaming an edited node updates only its actual constructor name literal and the relevant registration and launch entries. Other strings, comments and docstrings stay intact. If a constructor or edited metadata cannot be identified safely, Generate refuses with HTTP 409 before moving or writing files; its message explains what to update by hand.
Regenerating is safe: a file is rewritten only if you have not edited it since the last generation, files you added are never touched, and renaming a package moves its directory. Directories that were not created by the designer are never modified.
Missing-peer findings affected by a failed or incomplete source analysis have
confidence: "uncertain" and severity info. They describe an unproved absence and do
not fail --fail-on warning or --fail-on error. A file that could not be inspected
can contain peers for any workspace package, so its uncertainty is workspace-wide;
attributed unresolved endpoints remain restricted to names they could supply.
Proven interface conflicts, naming, dependencies and health checks remain enabled.
SARIF represents uncertain findings as notes with a confidence property; a baseline
for an uncertain finding cannot hide its later confirmed counterpart.
Python node inheritance is resolved across workspace packages, including inherited endpoints and deployment entry points. The cache records workspace source dependencies and invalidates inheritance results when a base package changes. Unknown imported bases used by an entry point remain incomplete definitions rather than disappearing.
Global options
ros2inspector --version
ros2inspector --quiet scan
ros2inspector --include-tests nodes
ros2inspector --preset nav2 scan
ros2inspector --factory-patterns factories.yaml scan
--version prints the package version. --quiet suppresses diagnostic headers and progress output.
Test source nodes are excluded by default; --include-tests adds them to architecture results.
The nav2 preset recognizes TwistPublisher, TwistSubscriber, and SimpleActionServer helpers.
Custom C++ endpoint factories can be declared in YAML:
factories:
- call: my_pkg::TopicPublisher
kind: publisher
name_arg: 1
msg_type: std_msgs/String
name_arg is the zero-based constructor argument containing a literal ROS name. Supported
kinds are publisher, subscription, service, client, action_server, and action_client.
Factory-derived endpoints retain source evidence and medium or low confidence.
Literal Python and C++ parameter defaults can resolve endpoint names and are labeled
parameter_default. When a launch Node passes literal parameters=[{...}] dictionaries,
the last value for that parameter replaces the default for that deployment and the edge
carries launch_parameter_override evidence. Parameters that cannot be read statically,
such as a YAML file path or a substitution, mark the deployment's parameters field
unresolved, leave the endpoint name unresolved, and suppress connectivity findings the
default would otherwise cause. Computed defaults remain unresolved.
Endpoint names are resolved the way rcl resolves them: the name and each remap rule are
expanded with the node's namespace and name before matching, and node-qualified
(node:from:=to) and rostopic:///rosservice:// rules apply only where ROS applies them.
A rule that names an action leaves that action unresolved, because Jazzy releases differ:
rcl_action 9.2.7 ignores such a rule and 9.2.11 applies it.
A namespace pushed by PushRosNamespace (or push-ros-namespace in XML and YAML) inside a
group applies to the nodes in that group; one that cannot be read statically leaves the
deployment unresolved instead of guessing.
Literal Python QoSProfile settings and C++ rclcpp::QoS(...) settings are reported when
available. The qos_compatibility rule reports only explicit reliability or durability
mismatches; unknown QoS settings do not create findings.
Remaps retain source order and duplicate match keys; the first matching rule wins.
JSON remaps values in launch records and node deployments are arrays of [from, to]
pairs, including an empty array when there are no rules (previously an object). See
the remap JSON schema. A node qualifier precedes the
optional kind prefix, for example talker:rostopic://chatter or
talker:rosservice://chatter. Node-level remaps are supported; SetRemap launch actions
remain unsupported and must not be treated as resolved global remaps.
Output formats
scan, packages, nodes, audit, and validate support table, JSON, and YAML output where applicable. graph supports Mermaid, DOT, and JSON. viz writes interactive HTML.
Machine-readable records go to standard output while diagnostics go to standard error, which keeps pipelines clean:
ros2inspector nodes --format json | jq '.[] | select(.package == "my_pkg")'
ros2inspector audit --format yaml > audit.yaml
ros2inspector graph comms --format mermaid -o architecture.mmd
Policy files
Create a ros2inspector_policy.yaml file:
version: 1
rules:
- type: health_threshold
min_score: 70
severity: warning
- type: license
allowed: [Apache-2.0, MIT]
severity: error
- type: naming
packages:
pattern: '^[a-z][a-z0-9_]*$'
severity: warning
nodes:
pattern: '^[A-Z][a-zA-Z0-9]*Node$'
severity: info
- type: dependency
forbidden:
- { from: perception, to: planner, severity: error }
required:
- { package: navigation, depends_on: nav_core, severity: warning }
- type: no_circular_deps
severity: error
- type: topic_connectivity
no_publisher: true
no_subscriber: true
type_mismatch: true
exclude: [/rosout, /clock]
- type: maintainer_required
require_email: true
severity: warning
topic_connectivity also reports interface_type_mismatch (severity
severity_type_mismatch, default error) when endpoints on one topic, service or action
use different interface types; type_mismatch: false turns it off. Connection output lists
each endpoint with its own type and only names peers whose type is compatible.
Supported rule types are health_threshold, license, naming, dependency, no_circular_deps, topic_connectivity, node_isolation, service_connectivity, action_connectivity, qos_compatibility, maintainer_required, and version_not_default.
Policy keys and nested options are checked before analysis. Unknown keys include a spelling suggestion when available. Booleans must be YAML booleans, license allowlists must be lists of non-empty strings, and health thresholds must be integers from 0 to 100. Invalid configuration or unsupported file encoding returns exit code 2 with the rule, line and field named.
CI integration
- name: Install ROS 2 Inspector
run: pip install ros2inspector
- name: Architecture audit
run: ros2inspector audit --fail-on warning --format json > audit.json
- name: Policy validation
run: ros2inspector validate --policy .ros2inspector_policy.yaml --fail-on error
Relevant exit codes:
| Code | Meaning |
|---|---|
0 |
Success or no findings at the configured threshold |
1 |
Findings at or above the configured threshold |
2 |
Invalid invocation or policy configuration |
3 |
Workspace or policy file not found |
Architecture
ros2inspector/
├── cli/ # scan, packages, nodes, graph, viz, audit, validate, docs, watch, doctor, design
├── design/ # package designer: models, code generator, local server and UI
├── discovery/ # workspace, package, and interface discovery
├── static/ # Python, C++, interface, launch, package, and health parsers
├── model/ # Pydantic schemas and the NetworkX UAM
├── graph/ # Mermaid, DOT, and JSON renderers
├── viz/ # interactive HTML renderer and bundled Cytoscape.js
├── policy/ # policy loader, engine, and rules
├── cache/ # diskcache-backed incremental analysis
├── output/ # output package plumbing
└── utils/ # shared utilities
The Unified Architecture Model is a networkx.MultiDiGraph. Graph nodes represent packages, ROS nodes, topics, services, actions, and interfaces. Edges carry relationships such as depends_on, publishes, subscribes, provides, calls, defined_in, and uses_interface.
Ignoring generated or vendor trees
Package scans automatically prune common generated directories such as build/, install/, log/, .git/, virtual environments, node_modules/, and __pycache__/. For large package-local vendor or generated trees, add a .ros2inspectorignore file at the package root. It accepts simple shell-style glob patterns; a pattern ending in / prunes that entire subtree before traversal.
vendor/
third_party/generated/
*.generated.py
Development
pip install -e ".[dev]"
pytest
ruff check ros2inspector/
mypy ros2inspector/
The test suite uses fixture workspaces under tests/fixtures/ and does not require a live ROS 2 environment.
Headless-browser tests for the designer and the generated HTML pages need Playwright:
pip install -e ".[dev,browser]"
python -m playwright install chromium
ROS2INSPECTOR_SCREENSHOT_DIR=screenshots pytest tests/browser -m browser --no-cov
Publishing
Maintainers should follow RELEASING.md. Publishing is performed by
GitHub Actions through PyPI Trusted Publishing when a GitHub Release is published.
Analysis and output guarantees
Static analysis is deliberately conservative. Common C++ header/source separation,
qualified out-of-class methods, indirect node inheritance, and direct
rclcpp::Node::make_shared/std::make_shared<rclcpp::Node> construction are recognised
when ownership is supported by source evidence. Ambiguous associations are retained as
incomplete-analysis diagnostics instead of being guessed.
Launch presence is represented as known, conditional, or unresolved. Conditional
and unresolved deployments do not establish definite missing-peer findings. Python launch
analysis follows constructor lists and add_action/add_entity calls in order. Literal
IfCondition/UnlessCondition namespace pushes, scoped and unscoped groups, and
forwarding=False groups follow launch namespace rules. Unknown conditions or mutations
mark the dependent namespace unresolved.
Includes of workspace launch files inherit the namespace and conditions at the include.
Literal paths and common package-share path expressions resolve to source launch files.
An included deployment keeps the child's launch_file and records its direct parent in
included_from. A file reached through an include also retains conditional standalone
deployments, which cannot establish definite connectivity findings. Unresolved include
targets and cycles produce launch_include_unresolved diagnostics and suppress missing-peer
claims that an unseen child could explain. Include arguments that cannot be consumed
statically leave the included namespace unresolved. Launch files are never executed.
Grouped layout positions all displayed leaf entities, including shared communication
entities and disconnected components. uses_interface relationships remain in the HTML
payload and are controlled by the relationship filter rather than being discarded.
For nodes, graph, and viz, output parent directories must already exist. File writes
use a temporary sibling and atomic replacement; ordinary path/permission failures exit 2 without a traceback.
License
MIT. See LICENSE.
Metadata
Release files for ros2inspector 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| ros2inspector-0.2.0.tar.gz | 282.2 kB | With provenance | Details for ros2inspector-0.2.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| ros2inspector-0.2.0-py3-none-any.whl | Python 3 | none | any | Details for ros2inspector-0.2.0-py3-none-any.whl |
Total release size: 582.2 kB
Release files / ros2inspector-0.2.0.tar.gz
| Download URL | ros2inspector-0.2.0.tar.gz |
|---|---|
| Size | 282.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d2719fa814223d476664a5967f10825e6ab3f74892cbb3d44273531d65649768
|
|
BLAKE2b-256 checksum How to use checksums |
34f8afaac28dd0ae13cba36328c8ee2eb5373664d7bd4d6e34709305b5332629
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 8, 2026.
Transparency logRelease files / ros2inspector-0.2.0-py3-none-any.whl
| Download URL | ros2inspector-0.2.0-py3-none-any.whl |
|---|---|
| Size | 300.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8d18e4b03c70a9b7f42fdca7a48d342cd9c69297af811a934db22fc9423f73e8
|
|
BLAKE2b-256 checksum How to use checksums |
5b17c9a3d90191bbe85798ed69956b4a95f20266270a8bd96de8108ebc5e55cb
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 8, 2026.
Transparency logSecurity for ros2inspector 0.2.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
Both files in this release have been uploaded with provenance metadata.
Attestation for ros2inspector-0.2.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Nov 8, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.