Metadata-Version: 2.4
Name: ros2inspector
Version: 0.2.0
Summary: Static ROS 2 architecture analysis, visualization, and policy checking
Author-email: Amine Bensaid <aminebensaid66@gmail.com>
License-Expression: MIT
Project-URL: Homepage, https://ros2-inspector-theta.vercel.app/
Project-URL: Documentation, https://ros2-inspector-theta.vercel.app/docs/
Project-URL: Repository, https://github.com/aminebensaid66/ros2_inspector
Project-URL: Issues, https://github.com/aminebensaid66/ros2_inspector/issues
Keywords: ros2,robotics,architecture,static-analysis,cli
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Topic :: Software Development :: Quality Assurance
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: typer>=0.12
Requires-Dist: rich>=13.7
Requires-Dist: networkx<3.5,>=3.3
Requires-Dist: pydantic>=2.7
Requires-Dist: pyyaml>=6.0
Requires-Dist: orjson>=3.10
Requires-Dist: tree-sitter>=0.25
Requires-Dist: tree-sitter-cpp>=0.23.4
Requires-Dist: diskcache>=5.6
Requires-Dist: jinja2>=3.1
Provides-Extra: dev
Requires-Dist: pytest>=8.2; extra == "dev"
Requires-Dist: pytest-cov>=5.0; extra == "dev"
Requires-Dist: ruff>=0.4; extra == "dev"
Requires-Dist: mypy>=1.10; extra == "dev"
Requires-Dist: hypothesis>=6.100; extra == "dev"
Requires-Dist: mutmut>=3.2; extra == "dev"
Provides-Extra: browser
Requires-Dist: playwright>=1.44; extra == "browser"
Dynamic: license-file

# 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](https://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 `.action` interface 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`:

```bash
pipx install ros2inspector
```

Run it once without keeping it installed:

```bash
pipx run ros2inspector --help
```

Inside an existing Python virtual environment, use `pip`:

```bash
pip install ros2inspector
```

Install from source for development:

```bash
git clone https://github.com/aminebensaid66/ros2_inspector
cd ros2_inspector
pip install -e ".[dev]"
```

## Quickstart

```bash
# 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`

```bash
ros2inspector scan [PATH] [--format table|json|yaml]
```

Discovers ROS 2 packages and displays a health-scored workspace summary.

### `packages`

```bash
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`

```bash
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`

```bash
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:

```bash
ros2inspector graph deps --format json
ros2inspector graph comms --format mermaid -o architecture.mmd
ros2inspector graph full --format dot | dot -Tsvg -o architecture.svg
```

### `viz`

```bash
ros2inspector viz [-C PATH] [-o FILE] [--open|--no-open] [--no-cache]
```

Generates a self-contained interactive Cytoscape.js HTML graph.

### `audit`

```bash
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`

```bash
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`

```bash
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`

```bash
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`

```bash
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`

```bash
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

```bash
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:

```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](docs/launch-remaps.schema.json). 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:

```bash
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:

```yaml
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

```yaml
- 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

```text
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.

```text
vendor/
third_party/generated/
*.generated.py
```

## Development

```bash
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:

```bash
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`](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`](LICENSE).
