topicforge 0.6.4__tar.gz → 0.7.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {topicforge-0.6.4 → topicforge-0.7.1}/CHANGELOG.md +276 -1
- {topicforge-0.6.4 → topicforge-0.7.1}/PKG-INFO +27 -20
- {topicforge-0.6.4 → topicforge-0.7.1}/README.md +23 -17
- {topicforge-0.6.4 → topicforge-0.7.1}/docs/CLIENTS.md +60 -18
- topicforge-0.7.1/docs/CONTRACT.md +776 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/docs/DDS_QUICKSTART.md +6 -6
- topicforge-0.7.1/docs/EVAL.md +158 -0
- topicforge-0.7.1/docs/MIGRATION-0.7.md +239 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/docs/TESTING.md +4 -4
- topicforge-0.7.1/docs/TOOLS.md +459 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/docs/TROUBLESHOOTING.md +4 -4
- {topicforge-0.6.4 → topicforge-0.7.1}/docs/TUTORIEL.md +2 -2
- topicforge-0.7.1/docs/VALIDATION.md +44 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/docs/dds-interop-matrix.md +2 -2
- {topicforge-0.6.4 → topicforge-0.7.1}/examples/README.md +1 -1
- {topicforge-0.6.4 → topicforge-0.7.1}/plugin/README.md +2 -1
- {topicforge-0.6.4 → topicforge-0.7.1}/plugin/pyproject.toml +2 -2
- {topicforge-0.6.4 → topicforge-0.7.1}/pyproject.toml +7 -6
- {topicforge-0.6.4 → topicforge-0.7.1}/scripts/agent_eval/README.md +3 -0
- topicforge-0.7.1/scripts/ground_truth/README.md +60 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/__init__.py +1 -1
- {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/__main__.py +30 -4
- {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/__init__.py +0 -2
- {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/base.py +13 -15
- topicforge-0.7.1/src/topicforge/adapters/common/bag_kind.py +26 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/common/cdr_decoder.py +16 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/common/dds_helpers.py +4 -3
- {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/common/endpoints.py +39 -26
- {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/common/lifecycle.py +5 -15
- {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/common/metrics_buffer.py +11 -11
- topicforge-0.7.1/src/topicforge/adapters/common/nodes.py +248 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/common/qos_endpoints.py +4 -5
- topicforge-0.7.1/src/topicforge/adapters/common/qos_fixes.py +518 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/common/qos_scan.py +58 -15
- topicforge-0.7.1/src/topicforge/adapters/common/ros_names.py +31 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/composite.py +23 -8
- {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/dds_cyclone/adapter.py +27 -15
- {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/dds_fast/adapter.py +25 -11
- {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/ros2_live/adapter.py +246 -47
- {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/ros2_live/echo_parser.py +37 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/ros2_live/echo_stream.py +9 -1
- topicforge-0.7.1/src/topicforge/adapters/ros2_live/node_parsers.py +96 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/ros2_live/parsers.py +62 -12
- topicforge-0.7.1/src/topicforge/adapters/ros2_live/process_runner.py +141 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/ros2_live/process_tree.py +24 -2
- topicforge-0.7.1/src/topicforge/adapters/ros2_live/sample_extras.py +77 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/ros2_mock/adapter.py +40 -18
- {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/ros2_mock/fixtures.py +286 -75
- topicforge-0.7.1/src/topicforge/adapters/ros2_mock/node_fixtures.py +121 -0
- topicforge-0.7.1/src/topicforge/budget.py +50 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/config/settings.py +15 -24
- {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/constants.py +14 -3
- {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/models/__init__.py +42 -0
- topicforge-0.7.1/src/topicforge/models/nodes.py +158 -0
- topicforge-0.7.1/src/topicforge/models/schemas.py +1269 -0
- topicforge-0.7.1/src/topicforge/models/summaries.py +242 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/server/app.py +7 -20
- topicforge-0.7.1/src/topicforge/server/http.py +75 -0
- topicforge-0.7.1/src/topicforge/server/prompts.py +114 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/services/bag_service.py +51 -15
- {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/services/bag_stats.py +2 -0
- topicforge-0.7.1/src/topicforge/services/dds_views.py +154 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/services/factory.py +0 -52
- {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/services/health.py +43 -3
- {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/services/inspector.py +143 -24
- topicforge-0.7.1/src/topicforge/services/summaries/__init__.py +77 -0
- topicforge-0.7.1/src/topicforge/services/summaries/_common.py +98 -0
- topicforge-0.7.1/src/topicforge/services/summaries/laser_scan.py +143 -0
- topicforge-0.7.1/src/topicforge/services/summaries/motion.py +82 -0
- topicforge-0.7.1/src/topicforge/services/summaries/rate.py +171 -0
- topicforge-0.7.1/src/topicforge/services/summaries/sensor_data.py +113 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/telemetry/client.py +1 -1
- topicforge-0.7.1/src/topicforge/tools/guard.py +127 -0
- topicforge-0.7.1/src/topicforge/tools/handlers.py +699 -0
- topicforge-0.7.1/src/topicforge/tools/schema_compact.py +60 -0
- topicforge-0.7.1/tests/contract/_prompts.json +37 -0
- topicforge-0.7.1/tests/contract/analyze_bag.json +219 -0
- topicforge-0.7.1/tests/contract/detect_qos_mismatches.json +516 -0
- topicforge-0.7.1/tests/contract/get_node_info.json +233 -0
- topicforge-0.7.1/tests/contract/get_topic_info.json +171 -0
- topicforge-0.7.1/tests/contract/health_check.json +303 -0
- topicforge-0.7.1/tests/contract/list_endpoints.json +683 -0
- topicforge-0.7.1/tests/contract/list_nodes.json +111 -0
- topicforge-0.7.1/tests/contract/list_participants.json +298 -0
- topicforge-0.7.1/tests/contract/list_topics.json +96 -0
- topicforge-0.7.1/tests/contract/participant_events.json +239 -0
- topicforge-0.7.1/tests/contract/peek_bag_samples.json +1083 -0
- topicforge-0.7.1/tests/contract/peek_dds_samples.json +1077 -0
- topicforge-0.7.1/tests/contract/sample_messages.json +1104 -0
- topicforge-0.7.1/tests/contract/topic_metrics.json +181 -0
- topicforge-0.7.1/tests/fixtures/ground_truth/omnisim_064_results.json +1 -0
- topicforge-0.7.1/tests/fixtures/ground_truth/omnisim_064_truth.json +1 -0
- topicforge-0.7.1/tests/fixtures/ros2_node/node_info_humble.txt +24 -0
- topicforge-0.7.1/tests/fixtures/ros2_node/node_info_no_param_services.txt +13 -0
- topicforge-0.7.1/tests/fixtures/ros2_node/node_info_not_found.txt +1 -0
- topicforge-0.7.1/tests/fixtures/ros2_node/node_list_duplicates.txt +6 -0
- topicforge-0.7.1/tests/fixtures/ros2_node/node_list_plain.txt +2 -0
- topicforge-0.7.1/tests/fixtures/ros2_node/node_list_warning_stdout.txt +4 -0
- topicforge-0.7.1/tests/fixtures/ros2_node/param_dump_humble.yaml +12 -0
- topicforge-0.7.1/tests/fixtures/ros2_node/param_dump_secrets.yaml +13 -0
- topicforge-0.7.1/tests/fixtures/ros2_topic_info_verbose_clock.txt +89 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/integration/ros2/Dockerfile +1 -0
- topicforge-0.7.1/tests/integration/ros2/__init__.py +0 -0
- topicforge-0.7.1/tests/integration/ros2/blocked_node.py +38 -0
- topicforge-0.7.1/tests/integration/ros2/entrypoint.sh +75 -0
- topicforge-0.7.1/tests/integration/ros2/ground_truth.py +221 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/integration/ros2/publisher.py +18 -17
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/integration/ros2/run_bench.py +12 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/integration/ros2/test_live_adapter.py +147 -5
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_analyze_bag_multi_format.py +12 -15
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_bag_omnisim_humble.py +1 -1
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_bag_service.py +1 -1
- topicforge-0.7.1/tests/test_bag_summaries.py +198 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_composite_adapter.py +13 -18
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_config.py +15 -39
- topicforge-0.7.1/tests/test_contract_shape.py +73 -0
- topicforge-0.7.1/tests/test_contract_snapshots.py +83 -0
- topicforge-0.7.1/tests/test_contract_v2.py +179 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_cyclone_adapter.py +1 -1
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_dds_helpers.py +1 -2
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_dds_inactive_reason.py +5 -10
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_dds_schemas.py +8 -8
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_discovery_tracker.py +1 -1
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_endpoints.py +52 -20
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_factory.py +4 -54
- topicforge-0.7.1/tests/test_field_names.py +100 -0
- topicforge-0.7.1/tests/test_friction_fixes.py +315 -0
- topicforge-0.7.1/tests/test_ground_truth_compare.py +737 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_health.py +56 -25
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_history_and_hints.py +1 -3
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_honest_outputs.py +9 -12
- topicforge-0.7.1/tests/test_http_transport.py +112 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_inspector.py +15 -11
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_lifecycle_buffer.py +10 -14
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_live_adapter_graph.py +119 -32
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_live_adapter_parse.py +2 -2
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_live_adapter_subprocess.py +34 -15
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_live_sample_notes.py +8 -7
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_metrics_buffer.py +25 -25
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_mock_adapter.py +47 -18
- topicforge-0.7.1/tests/test_node_logic.py +159 -0
- topicforge-0.7.1/tests/test_node_parsers.py +135 -0
- topicforge-0.7.1/tests/test_node_tools.py +383 -0
- topicforge-0.7.1/tests/test_process_runner.py +221 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_process_tree.py +35 -0
- topicforge-0.7.1/tests/test_prompts.py +81 -0
- topicforge-0.7.1/tests/test_qos_fixes.py +362 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_qos_scan.py +1 -3
- topicforge-0.7.1/tests/test_rate_verdicts.py +281 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_sample_options.py +8 -6
- topicforge-0.7.1/tests/test_sample_summary_flow.py +376 -0
- topicforge-0.7.1/tests/test_server_guard.py +158 -0
- topicforge-0.7.1/tests/test_server_info_version.py +35 -0
- topicforge-0.7.1/tests/test_server_lanes.py +261 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_stamps.py +1 -1
- topicforge-0.7.1/tests/test_summaries.py +408 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_telemetry.py +2 -2
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_tool_annotations.py +7 -7
- topicforge-0.7.1/tests/test_tool_descriptions.py +116 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_tools_integration.py +53 -55
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_topic_metrics.py +14 -14
- topicforge-0.6.4/docs/VALIDATION.md +0 -15
- topicforge-0.6.4/src/topicforge/adapters/dds_dust/__init__.py +0 -10
- topicforge-0.6.4/src/topicforge/adapters/dds_dust/adapter.py +0 -106
- topicforge-0.6.4/src/topicforge/adapters/dds_opendds/__init__.py +0 -11
- topicforge-0.6.4/src/topicforge/adapters/dds_opendds/adapter.py +0 -112
- topicforge-0.6.4/src/topicforge/models/schemas.py +0 -1402
- topicforge-0.6.4/src/topicforge/tools/handlers.py +0 -700
- topicforge-0.6.4/tests/integration/ros2/entrypoint.sh +0 -43
- topicforge-0.6.4/tests/test_dust_adapter.py +0 -48
- topicforge-0.6.4/tests/test_opendds_adapter.py +0 -60
- {topicforge-0.6.4 → topicforge-0.7.1}/.gitignore +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/LICENSE +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/examples/dds/00_hello_pub_sub/README.md +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/examples/dds/01_who_is_on_the_bus/README.md +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/examples/dds/02_why_cant_they_talk/README.md +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/examples/dds/03_a_node_crashed/README.md +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/examples/dds/04_late_joiner_misses_data/README.md +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/examples/dds/05_reliability_in_code/README.md +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/examples/dds/06_durability_late_joiner_in_code/README.md +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/examples/dds/07_deadline_in_code/README.md +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/examples/dds/08_crash_seen_from_inside/README.md +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/examples/dds/10_lidar_silent_after_driver_swap/README.md +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/examples/dds/11_who_talks_to_whom/README.md +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/examples/dds/12_safety_monitor_dropout/README.md +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/examples/dds/13_deadline_not_offered/README.md +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/examples/dds/14_restart_loop/README.md +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/examples/dds/README.md +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/plugin/LICENSE +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/scripts/integration/README.md +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/scripts/integration/publishers/cyclone_c/README.md +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/scripts/integration/publishers/cyclone_cpp/README.md +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/scripts/integration/publishers/cyclone_rust/README.md +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/scripts/integration/publishers/dust_py/README.md +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/scripts/integration/publishers/fast_publisher_cpp/README.md +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/scripts/integration/publishers/fast_py/README.md +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/scripts/integration/publishers/opensplice_publisher/README.md +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/scripts/integration/publishers/rti_c/README.md +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/scripts/integration/publishers/rti_cpp/README.md +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/common/__init__.py +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/common/dds_introspection.py +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/common/discovery_tracker.py +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/common/qos_analyzer.py +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/common/qos_normalize.py +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/common/stamps.py +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/common/topic_filter.py +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/common/xtypes.py +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/dds_cyclone/__init__.py +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/dds_fast/__init__.py +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/ros2_live/__init__.py +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/adapters/ros2_mock/__init__.py +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/config/__init__.py +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/server/__init__.py +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/services/__init__.py +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/services/sample_budget.py +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/telemetry/__init__.py +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/tools/__init__.py +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/src/topicforge/tools/annotations.py +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/__init__.py +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/conftest.py +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/fixtures/ros2_echo/image_noarr.yaml +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/fixtures/ros2_echo/image_trunc4.yaml +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/fixtures/ros2_echo/scan_edge.yaml +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/fixtures/ros2_echo/scan_noarr.yaml +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/fixtures/ros2_echo/scan_trunc128.yaml +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/fixtures/ros2_echo/scan_trunc3.yaml +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/fixtures/ros2_echo/string_latched.yaml +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/fixtures/ros2_echo/twist.yaml +0 -0
- /topicforge-0.6.4/tests/integration/ros2/__init__.py → /topicforge-0.7.1/tests/fixtures/ros2_node/node_list_empty.txt +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/fixtures/ros2_topic_info_verbose_cmd_vel.txt +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/fixtures/ros2_topic_info_verbose_parameter_events.txt +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/fixtures/ros2_topic_info_verbose_scan.txt +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/fixtures/ros2_topic_info_verbose_tf.txt +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/fixtures/ros2_topic_info_verbose_tf_static.txt +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/integration/__init__.py +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/integration/test_real_bus.py +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_bag_stats.py +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_cdr_decoder.py +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_dds_cross_vendor.py +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_dds_introspection.py +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_dds_qos_normalization.py +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_echo_parser.py +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_echo_stream.py +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_example_node_spec.py +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_fast_adapter.py +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_peek_bag_samples.py +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_qos_analyzer.py +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_qos_endpoints.py +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_version_consistency.py +0 -0
- {topicforge-0.6.4 → topicforge-0.7.1}/tests/test_xtypes.py +0 -0
|
@@ -7,6 +7,279 @@ and this project adheres to [Semantic Versioning 2.0.0](https://semver.org/spec/
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.7.1] - 2026-10-14
|
|
11
|
+
|
|
12
|
+
QoS fix suggestions and agent-friction fixes from the blind evaluation (`docs/EVAL.md`). Additive only: no field or
|
|
13
|
+
input was renamed, removed or retyped (`docs/CONTRACT.md` section 9).
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- `detect_qos_mismatches` fills `suggested_fixes` (output contract 2, additive; see
|
|
18
|
+
`docs/CONTRACT.md` section 3.1): "diagnose, propose the exact fix, verify". Each QoS
|
|
19
|
+
mismatch report and each `partition` split gets up to one fix per side that can change
|
|
20
|
+
(`MismatchReport.suggested_fixes`, and the new `NotMatchedPair.suggested_fixes`), with its
|
|
21
|
+
tradeoff and a `verify_with` call. A ROS 2 topic with a Reliability or Durability finding
|
|
22
|
+
gets a `qos_override` parameter-file entry; every other case gets a `dds_profile` (Fast DDS
|
|
23
|
+
XML plus the cyclonedds-python equivalent, since Cyclone DDS has no XML QoS profile).
|
|
24
|
+
Covered: Reliability, Durability, Deadline, Liveliness, Ownership, LatencyBudget and
|
|
25
|
+
Partition; other findings get a hint instead. Identical findings on a topic share the
|
|
26
|
+
first report's fixes. Text only: TopicForge never applies a fix, and a test fails any
|
|
27
|
+
suggestion containing a stop, remove or write verb.
|
|
28
|
+
- `list_participants` and `participant_events`: `restarts_by_name` (`{name: {lives,
|
|
29
|
+
departures}}` for names seen with several GUIDs or lost) and `restarts_note`, so an agent
|
|
30
|
+
no longer counts GUIDs to find a restart loop.
|
|
31
|
+
- `participant_events`: `age_s` on each event, seconds between the event and the response
|
|
32
|
+
(`null` in mock mode).
|
|
33
|
+
- `detect_qos_mismatches`: `summary`, a one-line reading of the scan that says when pairs are
|
|
34
|
+
not matched and no data flows, even with an empty `reports`.
|
|
35
|
+
- `list_endpoints`: optional input `fields` (`full` default, or `compact`) with the output
|
|
36
|
+
fields `fields` and `qos_summary`; compact replaces each `qos` by a one-line summary and
|
|
37
|
+
drops `type_id`, `announced_ns` and `ros_topic_note`.
|
|
38
|
+
|
|
39
|
+
### Changed
|
|
40
|
+
|
|
41
|
+
- `tools/list` is about 37 percent smaller (about 178 KB to 113 KB compact), because some
|
|
42
|
+
clients truncated it: tool and field descriptions are shorter, and the generated schema
|
|
43
|
+
titles and nested-model docstrings are no longer served. Names, types, defaults and
|
|
44
|
+
constraints are unchanged. The long explanations moved to `docs/CONTRACT.md` section 9.
|
|
45
|
+
- `domain_id` on `list_participants`, `participant_events`, `topic_metrics` and
|
|
46
|
+
`list_endpoints`: the description now opens with "Ignored", and a non-zero value that
|
|
47
|
+
differs from the domain joined at startup adds an explicit sentence to the response `note`
|
|
48
|
+
(the response `domain_id` already named the observed domain).
|
|
49
|
+
|
|
50
|
+
### Fixed
|
|
51
|
+
|
|
52
|
+
- `scripts/ground_truth/compare.py` counts only active DDS participants: the `ros2` CLI
|
|
53
|
+
processes that `get_node_info` starts stay listed with status `left` and inflated the count
|
|
54
|
+
(found on the OmniSim kit run of 2026-10-14 on 0.7.0: 76 checks passed, 0 failed).
|
|
55
|
+
|
|
56
|
+
## [0.7.0] - 2026-10-12
|
|
57
|
+
|
|
58
|
+
Breaking release: output contract 2 (see `docs/MIGRATION-0.7.md`), the move to the MCP SDK
|
|
59
|
+
2.x, and a tool surface of 14 tools (`list_nodes` and `get_node_info` are new).
|
|
60
|
+
|
|
61
|
+
### Breaking
|
|
62
|
+
|
|
63
|
+
Output contract 2 (`health_check.contract_version` is 2; 0.6.x is implicitly 1). The tool
|
|
64
|
+
list and the input defaults are unchanged except where noted. `docs/CONTRACT.md` is the
|
|
65
|
+
specification and `docs/MIGRATION-0.7.md` expands this list.
|
|
66
|
+
|
|
67
|
+
One object per tool, never a bare list:
|
|
68
|
+
|
|
69
|
+
- `list_topics`: `list[TopicInfo]` -> `TopicListing {topics, returned, total, truncated,
|
|
70
|
+
mode_effective, note}`.
|
|
71
|
+
- `list_participants`: `list[ParticipantInfo]` -> `ParticipantListing {participants, returned,
|
|
72
|
+
total, truncated, domain_id, mode_effective, note}`.
|
|
73
|
+
- `participant_events`: `list[ParticipantEvent]` -> `ParticipantEventListing {events,
|
|
74
|
+
returned, total, truncated, domain_id, mode_effective, note}`. The silent 200-event cap is
|
|
75
|
+
now `truncated` plus a `note`.
|
|
76
|
+
|
|
77
|
+
Topic models:
|
|
78
|
+
|
|
79
|
+
- `TopicInfo` splits: `list_topics` returns `TopicListItem` (`name`, `message_type`,
|
|
80
|
+
`publisher_count`, `subscriber_count`, no QoS, no `mode_effective`); `get_topic_info`
|
|
81
|
+
returns `TopicInfo`.
|
|
82
|
+
- `TopicInfo.qos_reliability` and `qos_durability` (publishers only) -> `publisher_qos` and
|
|
83
|
+
`subscription_qos` (`{reliability, durability, endpoint_count}`, `mixed` when the endpoints
|
|
84
|
+
of a side disagree), each with a `_note` when it is `null`. A topic with subscribers only
|
|
85
|
+
no longer reports nothing.
|
|
86
|
+
- `TopicInfo` gains `publisher_nodes` and `subscriber_nodes` (fully qualified node names read
|
|
87
|
+
from `ros2 topic info --verbose`) and a `note`.
|
|
88
|
+
- Removed because no adapter ever filled them: `TopicInfo.reader_count`, `writer_count` and
|
|
89
|
+
`qos_profile`; `BagAnalysis.samples_decoded_count`, `participants_recorded` and
|
|
90
|
+
`recording_duration_ns`.
|
|
91
|
+
|
|
92
|
+
Renames (unit suffixes, `_reason` -> `_note`):
|
|
93
|
+
|
|
94
|
+
- `BagAnalysis.duration_seconds` -> `duration_s`.
|
|
95
|
+
- `TopicMetrics.window_seconds` -> `window_s`, `window_seconds_actual` -> `window_actual_s`,
|
|
96
|
+
`frequency_hz_observed` -> `observed_frequency_hz`, `frequency_hz_declared` ->
|
|
97
|
+
`declared_frequency_hz`.
|
|
98
|
+
- Tool inputs: `topic_metrics.window_seconds` -> `window_s`, `participant_events.lookback_seconds`
|
|
99
|
+
-> `lookback_s`.
|
|
100
|
+
- `EndpointListing.departed_endpoints` -> `departed_endpoint_count`,
|
|
101
|
+
`excluded_observer_endpoints` -> `excluded_observer_endpoint_count`, `total_discovered` ->
|
|
102
|
+
`total`.
|
|
103
|
+
- `HealthReport.dds_inactive_reason` -> `dds_inactive_note`, `payload_decoding_reason` ->
|
|
104
|
+
`payload_decoding_note`.
|
|
105
|
+
|
|
106
|
+
`list_endpoints`:
|
|
107
|
+
|
|
108
|
+
- `EndpointInfo.topic` -> `dds_topic` (raw name, `rt/scan`) plus `ros_topic` (`/scan`, `null`
|
|
109
|
+
with `ros_topic_note` for a DDS topic that is not a ROS 2 topic). `TopicSummary.topic` ->
|
|
110
|
+
`dds_topic` plus `ros_topic`.
|
|
111
|
+
- New input `include_internal` (default false): the ROS 2 service and action endpoints
|
|
112
|
+
(`rq/`, `rr/`, `rs/`, `rp/`, `ra/` and `ros_discovery_info`) are hidden unless it is true;
|
|
113
|
+
`hidden_internal_endpoint_count` says how many were left out.
|
|
114
|
+
- `EndpointInfo.activity` (always `null`) and `activity_note` (the same sentence on every
|
|
115
|
+
endpoint) are removed; the sentence is stated once in the new root-level `hints`.
|
|
116
|
+
- `EndpointInfo.domain_id` and `mode_effective` are removed (the listing carries them once).
|
|
117
|
+
|
|
118
|
+
Nested items no longer repeat the envelope: `mode_effective` is removed from
|
|
119
|
+
`ParticipantInfo`, `ParticipantEvent`, `EndpointInfo` and `MismatchReport`.
|
|
120
|
+
|
|
121
|
+
`stamp_source` (every `MessageSample`) is required and has five values: `header`, `payload`,
|
|
122
|
+
`recorded`, `dds_source`, `none`. It was nullable with four. `peek_dds_samples` now says
|
|
123
|
+
`dds_source` (builtin discovery topics, from the announcement timestamp) or `none`.
|
|
124
|
+
|
|
125
|
+
`sample_messages.timeout_s` accepts 1..40 (see Changed).
|
|
126
|
+
|
|
127
|
+
Configuration and Python API:
|
|
128
|
+
|
|
129
|
+
- `TOPICFORGE_DDS_BACKEND` accepts `mock`, `cyclone`, `fast` and `auto` only. `opendds` and
|
|
130
|
+
`dust` (stub adapters that never served) and the long-rejected `rti`, `opensplice`, `coredx`
|
|
131
|
+
and `intercom` now fail at startup with an error that names the valid values.
|
|
132
|
+
`health_check.dds_backend` no longer returns `opendds` or `dust`.
|
|
133
|
+
- `RosAdapter` (an alias of `MiddlewareAdapter`) is removed from `topicforge.adapters`.
|
|
134
|
+
|
|
135
|
+
### Added (contract 2)
|
|
136
|
+
|
|
137
|
+
- `health_check`: `contract_version` (2), `rmw_implementation` and `rmw_source` (`env`,
|
|
138
|
+
`distro_default` or `none`; `ros2_cli` is reserved), and a `note`. The RMW comes from
|
|
139
|
+
`RMW_IMPLEMENTATION` or the default of `ROS_DISTRO`, never from the running graph.
|
|
140
|
+
- A `note` on `get_topic_info`, `detect_qos_mismatches`, `topic_metrics` and `health_check`.
|
|
141
|
+
- `analyze_bag` topics gain `kind`: `user`, `rosbag2_internal` (`/events/write_split`) or
|
|
142
|
+
`ros_builtin` (`/parameter_events`, `/rosout`).
|
|
143
|
+
- Reserved optional fields: `ParticipantInfo.node_names` and `node_names_source` (empty and
|
|
144
|
+
`none` until a tool fills them), `MismatchReport.suggested_fixes` (empty).
|
|
145
|
+
- `tests/test_contract_shape.py` (every tool returns one object and one text block),
|
|
146
|
+
`tests/test_field_names.py` (no `_seconds`, `_sec`, `_ms`, `_hertz`, `_reason` and similar
|
|
147
|
+
suffixes in any input or output field).
|
|
148
|
+
|
|
149
|
+
### Added
|
|
150
|
+
|
|
151
|
+
- Ground-truth comparator in CI (`scripts/ground_truth/`): one `compare.py`, two producers of
|
|
152
|
+
ground truth. The OmniSim kit (external, manual, kept as confirmation) and the Docker bench
|
|
153
|
+
publisher, which now writes its own `ground_truth.json` in the same schema subset
|
|
154
|
+
(`tests/integration/ros2/ground_truth.py`: topics, types, endpoint counts and QoS, the
|
|
155
|
+
synthetic 541-beam scan and its sector minima, configured rates, `use_sim_time` per node,
|
|
156
|
+
RMW and distro). `drive.py` is the parameterised MCP stdio driver and `calls.json` the
|
|
157
|
+
default call list (graph calls before any sampling). Every `ros2-live.yml` matrix cell
|
|
158
|
+
(Humble/Jazzy x Fast DDS/Cyclone) runs both after the integration tests, fails on any FAIL
|
|
159
|
+
or SHAPE row, prints the measured rate `interval_cv` values (the `STABLE_CV` evidence for
|
|
160
|
+
`docs/CONTRACT.md` section 4) and uploads `COMPARISON.md`, `comparison.json` and the raw
|
|
161
|
+
results as an artifact. `compare.py` also reads a bag with `rosbags` (`--bag-truth`) to
|
|
162
|
+
check `analyze_bag` and `peek_bag_samples` independently.
|
|
163
|
+
- Two tools, additive: `list_nodes` and `get_node_info` (the surface is now fourteen tools).
|
|
164
|
+
- `list_nodes` -> `NodeListing {nodes, returned, total, truncated, duplicates,
|
|
165
|
+
mode_effective, note}`: one `ros2 node list` call, names, namespaces and the full names used
|
|
166
|
+
by more than one node (`duplicate_count`).
|
|
167
|
+
- `get_node_info(node, timeout_s)` -> `NodeInfo`: publishers, subscribers, service servers and
|
|
168
|
+
clients, action servers and clients (each `{name, type}`) from `ros2 node info`, then the
|
|
169
|
+
parameters from `ros2 param dump` (YAML on stdout, never an output option, so nothing is
|
|
170
|
+
written to disk) and `use_sim_time`. `timeout_s` is 1..20, default 8. A node that does not
|
|
171
|
+
answer its parameter read in time gives `parameters` null and a `parameters_note` (its
|
|
172
|
+
executor is probably blocked): a finding, not an error. A value whose name contains
|
|
173
|
+
password, secret, token, api_key or credential is masked; a value over 2048 characters or
|
|
174
|
+
128 list elements (a robot description) is cut and flagged; the dump is capped at 1 MiB.
|
|
175
|
+
A name used by several nodes is flagged in `duplicate_count` and `note`. An unknown node
|
|
176
|
+
raises an error that lists close matches.
|
|
177
|
+
- Both run the `ros2` CLI in the ROS lane; DDS-only setups raise a clear error. The only
|
|
178
|
+
request TopicForge ever sends is a parameter read (list/get) (SECURITY.md).
|
|
179
|
+
- The `inspect-ros2-robot` prompt and plugin skill gain the node step.
|
|
180
|
+
- Per-type message summaries and an observed rate with a verdict on `sample_messages` and
|
|
181
|
+
`peek_bag_samples` (additive; no new input, no new tool).
|
|
182
|
+
- `MessageSample.summary`: `laser_scan` (beam count, angle geometry, finite / `inf` /
|
|
183
|
+
`-inf` / `nan` counts, closest obstacle, front / left / right / rear sector minima by
|
|
184
|
+
bearing in the sensor frame), `odometry` (speed, angular z, position, yaw), `imu` (roll,
|
|
185
|
+
pitch, yaw, angular velocity and acceleration norms, orientation validity), `image`
|
|
186
|
+
(size, encoding, step, buffer length) and `point_cloud2` (point count, layout). `null`
|
|
187
|
+
for any other type. Units are meters, radians and seconds with no unit suffix
|
|
188
|
+
(docs/CONTRACT.md section 4).
|
|
189
|
+
- The summary is computed on the whole message even when `payload` is cut: a scan is
|
|
190
|
+
streamed uncut (`--full-length`, up to 4 MiB per message while streaming), summarized,
|
|
191
|
+
then cut to `max_array_length` as before; `Image` and `PointCloud2` never read their
|
|
192
|
+
buffers; a bag scan is decoded whole.
|
|
193
|
+
- `SampleResult.rate`: `message_count`, `window_s`, mean / median / max interval,
|
|
194
|
+
`interval_cv`, `observed_frequency_hz` (wall clock: `received_ns`, or `recorded_ns` for a
|
|
195
|
+
bag), `sim_frequency_hz` (from the messages' own stamps), `trailing_gap_s` and a
|
|
196
|
+
`verdict`: `silent`, `insufficient`, `intermittent`, `stable`, `jittery` or `erratic`.
|
|
197
|
+
Thresholds confirmed at 0.2 and 0.5 on both bases; the `jittery` note gives the longest
|
|
198
|
+
gap as a multiple of the median. Physical quantities carry no unit suffix (CONTRACT 1.4).
|
|
199
|
+
- `peek_bag_samples` now returns non-finite floats as the strings `nan`, `inf`, `-inf`
|
|
200
|
+
(they could not be written as JSON numbers).
|
|
201
|
+
- Mock mode: `/scan` is a 720-beam scan in a walled room, and `/imu/data` is a new mock
|
|
202
|
+
topic.
|
|
203
|
+
- MCP prompts `diagnose-dds-bus` (optional `topic`, `symptom`) and `inspect-ros2-robot`
|
|
204
|
+
(optional `topic`, `bag_path`), the plugin skills in client-neutral wording, and server
|
|
205
|
+
`instructions` in the `initialize` result (read-only guarantee, which tool first,
|
|
206
|
+
`contract_version`) for clients that do not show prompts. They are snapshotted in
|
|
207
|
+
`tests/contract/_prompts.json`.
|
|
208
|
+
- `tests/test_tool_descriptions.py`: no history words (`v0.x`, `Phase`, `ceiling`, `TODO`,
|
|
209
|
+
`since 0.`) in any tool or field description, and every tool that takes a lane lock states
|
|
210
|
+
its 45 s bound.
|
|
211
|
+
- `docs/MIGRATION-0.7.md`: every old field and its new name, with before and after JSON.
|
|
212
|
+
- Optional local HTTP transport: `topicforge --transport streamable-http --port N` serves the
|
|
213
|
+
tools on `http://127.0.0.1:N/mcp`. stdio stays the default. The bind address is fixed to
|
|
214
|
+
127.0.0.1 (no flag changes it) and DNS-rebinding protection is on (loopback `Host` and
|
|
215
|
+
`Origin` only). There is no authentication: use an SSH tunnel to reach a robot. See
|
|
216
|
+
`docs/CLIENTS.md`.
|
|
217
|
+
- `docs/CONTRACT.md`: the output contract (one object per tool, a single `note`, no silent
|
|
218
|
+
null, suffix vocabulary, enum casing by layer, non-finite floats, reserved optional fields,
|
|
219
|
+
rate verdicts) and the complete list of renames and splits.
|
|
220
|
+
- `docs/TOOLS.md`: tool reference generated from the served tools.
|
|
221
|
+
- Golden snapshots of every tool (`tests/contract/*.json`), generated by
|
|
222
|
+
`scripts/contract/snapshot_tools.py`, and `tests/test_contract_snapshots.py`, which fails
|
|
223
|
+
with a diff on any change to a description, schema or annotation and when `TOOLS.md` is
|
|
224
|
+
stale.
|
|
225
|
+
|
|
226
|
+
### Removed
|
|
227
|
+
|
|
228
|
+
- The `opendds` and `dust` stub adapters (`topicforge.adapters.dds_opendds`,
|
|
229
|
+
`dds_dust`), their tests and the `requires_opendds` / `requires_dust` markers. The Pro tier
|
|
230
|
+
and `topicforge_pro` hook were already gone in 0.5.3: no code, setting or documentation
|
|
231
|
+
mentions `TOPICFORGE_LICENSE_KEY` any more.
|
|
232
|
+
- The `RosAdapter` alias (see Breaking).
|
|
233
|
+
|
|
234
|
+
### Changed
|
|
235
|
+
|
|
236
|
+
- Tool descriptions state present behaviour only, name the lock they take and their worst-case
|
|
237
|
+
duration (45 s, lock wait included), and the description of `SampleResult.count` no longer
|
|
238
|
+
contradicts itself on the maximum of 50.
|
|
239
|
+
- Moved to the MCP Python SDK 2.x: the dependency is now `mcp>=2.3,<3` (it was `mcp>=1.0.0,<2`).
|
|
240
|
+
`FastMCP` became `MCPServer`; the server reports `name` and `version` through the
|
|
241
|
+
constructor, which replaces the private-attribute workaround of 0.6.4. Handlers, tool
|
|
242
|
+
names, parameters and the stdio transport are unchanged. The SDK brings new dependencies
|
|
243
|
+
(`mcp-types`, `httpx2`, `opentelemetry-api`, `anyio`, `pydantic>=2.12`). The served schemas
|
|
244
|
+
differ only where the SDK serializes differently (see the golden snapshot commit).
|
|
245
|
+
- Tool calls are serialized per backend (`topicforge.tools.guard`): the 2.x SDK runs
|
|
246
|
+
synchronous handlers in worker threads, and neither the Cyclone binding nor the `ros2` CLI
|
|
247
|
+
adapter can take overlapping calls. There are two locks: a ROS lane (six tools: `list_topics`,
|
|
248
|
+
`get_topic_info`, `list_nodes`, `get_node_info`, `sample_messages`, `analyze_bag`) and a
|
|
249
|
+
DDS lane (six tools: `list_participants`, `detect_qos_mismatches`, `peek_dds_samples`,
|
|
250
|
+
`participant_events`, `topic_metrics`, `list_endpoints`), so a slow `sample_messages` no longer delays a DDS call.
|
|
251
|
+
`health_check` and `peek_bag_samples` take no lock.
|
|
252
|
+
- A tool call has a wall budget of 45 s, lock wait included. A call that waited for its lane
|
|
253
|
+
runs with the time that is left (the `ros2` timeouts shrink accordingly), and one that cannot
|
|
254
|
+
start with at least 5 s left fails at once with `busy: another ros call is running, retry`
|
|
255
|
+
(or `dds`) instead of waiting. At most 16 calls queue per lane.
|
|
256
|
+
- `sample_messages` `timeout_s` now accepts 1..40 (it was 1..45), so that the timeout plus
|
|
257
|
+
stopping the CLI and decoding stays under the 45 s budget. The default is unchanged (10).
|
|
258
|
+
- `health_check` `sim_clock_published` no longer runs `ros2 topic info /clock`: it reports
|
|
259
|
+
what the last `list_topics` (or `get_topic_info` on `/clock`) saw, `null` before the first
|
|
260
|
+
graph read and two minutes after it. This is what lets `health_check` always answer.
|
|
261
|
+
- CI builds the wheel and installs it into a fresh venv on Ubuntu and Windows.
|
|
262
|
+
|
|
263
|
+
### Fixed
|
|
264
|
+
|
|
265
|
+
- `rosbags` 0.11.6 is excluded: it builds message definitions with `PosixPath` and fails
|
|
266
|
+
on Windows for types with nested arrays, which broke opening some bags there.
|
|
267
|
+
- The server can no longer freeze on a hung `ros2` CLI. Every `ros2` call now goes through one
|
|
268
|
+
process runner (`adapters/ros2_live/process_runner.py`) instead of `subprocess.run`: the
|
|
269
|
+
process starts in its own kill unit (Windows Job Object with a `taskkill /T` fallback, POSIX
|
|
270
|
+
process group with SIGINT then SIGKILL), runs under a hard deadline, and its whole tree is
|
|
271
|
+
killed at the deadline, so the stock `ros2` launcher can no longer leave an orphan holding
|
|
272
|
+
the output pipes. Output is capped at 8 MiB per stream. Error messages are unchanged.
|
|
273
|
+
- `health_check` always answers, also while a `sample_messages` call is running.
|
|
274
|
+
- The server no longer blocks the asyncio event loop during a long tool call (a 45 s
|
|
275
|
+
`sample_messages`, a hung `ros2` CLI): handlers run in worker threads, so the transport
|
|
276
|
+
keeps answering pings and cancellations.
|
|
277
|
+
- `AdapterError` messages still reach the client as `isError` text. The 2.x SDK keeps the
|
|
278
|
+
text of any exception other than `ToolError` on the server, so every handler now
|
|
279
|
+
re-raises `AdapterError` as `ToolError` with the same message. Unexpected exceptions
|
|
280
|
+
stay redacted ("Error executing tool X") and leak no traceback.
|
|
281
|
+
- `rate.observed_frequency_hz` on `received_ns` no longer counts the start-up drain of `ros2 topic echo` (up to 5 queued messages printed at once, which read `/clock` at 91 Hz for 50 Hz): the leading run of near-zero intervals is set aside and reported in the new additive `startup_burst_count`; the ground-truth comparator gates FAIL and STABLE_CV on 10 messages spanning 0.9 s.
|
|
282
|
+
|
|
10
283
|
## [0.6.4] - 2026-10-08
|
|
11
284
|
|
|
12
285
|
### Added
|
|
@@ -1388,7 +1661,9 @@ Initial MVP release of TopicForge: ROS Topic Inspector & Bag Analyzer MCP server
|
|
|
1388
1661
|
- The write path (publishing, commanding robots) is intentionally out of scope for the MVP.
|
|
1389
1662
|
- `analyze_bag` in live mode parses `ros2 bag info` text output; deeper anomaly detection remains mock-only for now.
|
|
1390
1663
|
|
|
1391
|
-
[Unreleased]: https://github.com/yaniswav/TopicForge/compare/v0.
|
|
1664
|
+
[Unreleased]: https://github.com/yaniswav/TopicForge/compare/v0.7.1...HEAD
|
|
1665
|
+
[0.7.1]: https://github.com/yaniswav/TopicForge/compare/v0.7.0...v0.7.1
|
|
1666
|
+
[0.7.0]: https://github.com/yaniswav/TopicForge/compare/v0.6.4...v0.7.0
|
|
1392
1667
|
[0.6.4]: https://github.com/yaniswav/TopicForge/compare/v0.6.3...v0.6.4
|
|
1393
1668
|
[0.6.3]: https://github.com/yaniswav/TopicForge/compare/v0.6.2...v0.6.3
|
|
1394
1669
|
[0.6.2]: https://github.com/yaniswav/TopicForge/compare/v0.6.1...v0.6.2
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: topicforge
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.7.1
|
|
4
4
|
Summary: ROS Topic Inspector & Bag Analyzer MCP server for AI agents
|
|
5
5
|
Project-URL: Homepage, https://github.com/yaniswav/TopicForge
|
|
6
6
|
Project-URL: Repository, https://github.com/yaniswav/TopicForge
|
|
@@ -39,12 +39,13 @@ Classifier: Programming Language :: Python :: 3.10
|
|
|
39
39
|
Classifier: Programming Language :: Python :: 3.11
|
|
40
40
|
Classifier: Programming Language :: Python :: 3.12
|
|
41
41
|
Classifier: Programming Language :: Python :: 3.13
|
|
42
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
42
43
|
Requires-Python: >=3.10
|
|
43
44
|
Requires-Dist: cyclonedds>=0.10; python_version < '3.14' and ((platform_system == 'Linux' and platform_machine == 'x86_64') or (platform_system == 'Windows' and platform_machine == 'AMD64') or platform_system == 'Darwin')
|
|
44
|
-
Requires-Dist: mcp<2
|
|
45
|
+
Requires-Dist: mcp<3,>=2.3
|
|
45
46
|
Requires-Dist: pydantic>=2.6
|
|
46
47
|
Requires-Dist: pyyaml>=6
|
|
47
|
-
Requires-Dist: rosbags
|
|
48
|
+
Requires-Dist: rosbags!=0.11.6,>=0.11.3
|
|
48
49
|
Provides-Extra: all
|
|
49
50
|
Requires-Dist: cyclonedds>=0.10; extra == 'all'
|
|
50
51
|
Provides-Extra: bags
|
|
@@ -72,9 +73,9 @@ A read-only MCP (Model Context Protocol) server that lets an AI agent inspect a
|
|
|
72
73
|
|
|
73
74
|
Every tool also declares itself read-only in the MCP protocol (`readOnlyHint`, `destructiveHint=false`, `idempotentHint`, with an honest `openWorldHint`), and a test fails if any tool lacks it. See [`SECURITY.md`](SECURITY.md#read-only-guarantee-architecture-declaration-proof) for the guarantee and its limits.
|
|
74
75
|
|
|
75
|
-
It gives the agent
|
|
76
|
+
It gives the agent fourteen typed tools that return frozen Pydantic schemas (output contract 2: one object per tool, one `note`, units in the field names; see [`docs/CONTRACT.md`](docs/CONTRACT.md)), identical whether the server talks to a real robot or to its built-in mock fixtures. Ask why `nav_planner` gets no scan, and the agent reads the bus, finds the BEST_EFFORT writer facing a RELIABLE reader and names the incompatible policy (see [`examples/02-debug-qos-mismatch.md`](examples/02-debug-qos-mismatch.md)). It is meant for ROS2 developers, robotics ML/CV engineers and teams that cannot accept a write path into a production stack.
|
|
76
77
|
|
|
77
|
-
For DDS, TopicForge joins a domain as a read-only participant through one open-source binding (Eclipse CycloneDDS from PyPI) and reads the builtin discovery topics that the OMG DDS-RTPS protocol standardizes.
|
|
78
|
+
For DDS, TopicForge joins a domain as a read-only participant through one open-source binding (Eclipse CycloneDDS from PyPI) and reads the builtin discovery topics that the OMG DDS-RTPS protocol standardizes. Cyclone DDS, Fast DDS and Dust DDS participants have been observed on live buses: the Fast DDS ones on every CI run of the ROS 2 test bench (Humble and Jazzy with `rmw_fastrtps_cpp`) and in the OmniSim validation. RTI Connext, OpenDDS and CoreDX announce themselves through the same standard discovery, but none of them has been observed yet. This covers discovery only: participants, readers, writers and their QoS. See [`docs/dds-interop-matrix.md`](docs/dds-interop-matrix.md).
|
|
78
79
|
|
|
79
80
|
## Quickstart
|
|
80
81
|
|
|
@@ -100,21 +101,23 @@ The server speaks MCP over stdio and waits for a client, so wire it into one. Fo
|
|
|
100
101
|
}
|
|
101
102
|
```
|
|
102
103
|
|
|
103
|
-
Then ask it to list the topics or to analyze `/tmp/demo.mcap`. For Claude Code: `claude mcp add topicforge -- topicforge`.
|
|
104
|
+
Then ask it to list the topics or to analyze `/tmp/demo.mcap`. For Claude Code: `claude mcp add topicforge -- topicforge`. If the client cannot launch the server (WSL2 to Windows, a robot over an SSH tunnel), `topicforge --transport streamable-http --port 8765` serves `http://127.0.0.1:8765/mcp` on loopback only; see [`docs/CLIENTS.md`](docs/CLIENTS.md#local-http-transport).
|
|
104
105
|
|
|
105
|
-
**Other MCP clients.** Ready-to-paste configs for Claude Desktop, Cursor, VS Code, Windsurf, Cline, Roo Code, Continue, Zed, JetBrains, Codex, Gemini CLI, Goose and more are in [`docs/CLIENTS.md`](docs/CLIENTS.md). One-click: [Add to Cursor](https://cursor.com/install-mcp?name=topicforge&config=
|
|
106
|
+
**Other MCP clients.** Ready-to-paste configs for Claude Desktop, Cursor, VS Code, Windsurf, Cline, Roo Code, Continue, Zed, JetBrains, Codex, Gemini CLI, Goose and more are in [`docs/CLIENTS.md`](docs/CLIENTS.md). One-click: [Add to Cursor](https://cursor.com/install-mcp?name=topicforge&config=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyItLWZyb20iLCJ0b3BpY2ZvcmdlPT0wLjcuMSIsInRvcGljZm9yZ2UiXSwiZW52Ijp7IlRPUElDRk9SR0VfTU9ERSI6ImF1dG8iLCJUT1BJQ0ZPUkdFX0REU19CQUNLRU5EIjoiY3ljbG9uZSIsIlRPUElDRk9SR0VfRERTX0RPTUFJTl9JRCI6IjAifX0%3D) | [Install in VS Code](https://vscode.dev/redirect/mcp/install?name=topicforge&config=%7B%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22--from%22%2C%22topicforge%3D%3D0.7.1%22%2C%22topicforge%22%5D%2C%22env%22%3A%7B%22TOPICFORGE_MODE%22%3A%22auto%22%2C%22TOPICFORGE_DDS_BACKEND%22%3A%22cyclone%22%2C%22TOPICFORGE_DDS_DOMAIN_ID%22%3A%220%22%7D%7D).
|
|
106
107
|
|
|
107
108
|
Setup for a real ROS2 environment (WSL2, Linux, Docker, native Windows) is in [`docs/TESTING.md`](docs/TESTING.md); recurring monitoring prompts and the privacy contract are in [`docs/TUTORIEL.md`](docs/TUTORIEL.md).
|
|
108
109
|
|
|
109
110
|
## Tools
|
|
110
111
|
|
|
111
|
-
Every response except `health_check` carries `mode_effective` (`"live"` or `"mock"`), so a caller can tell a real graph from fixtures.
|
|
112
|
+
Every response except `health_check` carries `mode_effective` (`"live"` or `"mock"`), so a caller can tell a real graph from fixtures, and a single `note` that explains anything unusual. `health_check.contract_version` is `2`. The full reference, with every field, is generated in [`docs/TOOLS.md`](docs/TOOLS.md); upgrading from 0.6.x: [`docs/MIGRATION-0.7.md`](docs/MIGRATION-0.7.md).
|
|
112
113
|
|
|
113
114
|
| Tool | Purpose |
|
|
114
115
|
| ----------------------- | ------------------------------------------------------------------------------------------------ |
|
|
115
116
|
| `health_check` | Environment and mode introspection. Always succeeds; reports `mode` next to `requested_mode` |
|
|
116
117
|
| `list_topics` | Discover the ROS2 graph |
|
|
117
118
|
| `get_topic_info` | Message type, publisher/subscriber counts and QoS for one topic |
|
|
119
|
+
| `list_nodes` | ROS 2 nodes on the graph: names, namespaces, duplicate names (one `ros2 node list` call) |
|
|
120
|
+
| `get_node_info` | One node's publishers, subscribers, services, actions, parameters (secrets masked) and `use_sim_time`; a node that does not answer is reported, not an error |
|
|
118
121
|
| `sample_messages` | Peek recent messages on a ROS2 topic (count capped at 50) |
|
|
119
122
|
| `analyze_bag` | Summarize a `.mcap` / `.db3` recording or `rosbag2_*` directory (via `ros2 bag info`) |
|
|
120
123
|
| `list_participants` | DDS participants on the domain: vendor, `name` (EntityName QoS, Cyclone) and `hostname` |
|
|
@@ -125,6 +128,8 @@ Every response except `health_check` carries `mode_effective` (`"live"` or `"moc
|
|
|
125
128
|
| `peek_bag_samples` | Decoded samples from a recorded bag, including ROS 1 `.bag` (`rosbags` ships with the package) |
|
|
126
129
|
| `list_endpoints` | DDS writers and readers with structured QoS, per-topic roll-up that flags orphans (writer with no reader, reader with no writer) |
|
|
127
130
|
|
|
131
|
+
The server also offers two MCP prompts, `diagnose-dds-bus` and `inspect-ros2-robot`, that give a client the read-only call order, and short server `instructions` for clients that do not show prompts (see [`docs/CLIENTS.md`](docs/CLIENTS.md#prompts-and-instructions)).
|
|
132
|
+
|
|
128
133
|
Walkthroughs against the mock, each with the exact tool calls and payloads, are in [`examples/`](examples/README.md). To run the DDS tools against a real bus with several programs and vendors, see [`examples/dds/README.md`](examples/dds/README.md) (`python examples/dds/run_all.py`).
|
|
129
134
|
|
|
130
135
|
## Modes
|
|
@@ -145,9 +150,9 @@ pip install "topicforge[dds]" # alias, kept so older commands
|
|
|
145
150
|
TOPICFORGE_DDS_BACKEND=cyclone python -m topicforge
|
|
146
151
|
```
|
|
147
152
|
|
|
148
|
-
`TOPICFORGE_DDS_BACKEND` accepts `mock` (default), `cyclone`, `fast` and `auto` (`fast`, then `cyclone`, then `mock`, whichever binding imports). The default `mock` selects no DDS backend: installing the Cyclone binding is not enough, you must also set `TOPICFORGE_DDS_BACKEND=cyclone`. With `TOPICFORGE_MODE=live` and no backend selected, the DDS tools raise `DDS module is not active: ...` with the actual cause (backend not selected, binding not installed, or binding installed but the adapter failed to start), and `health_check` reports `dds_backend: "none"` plus `
|
|
153
|
+
`TOPICFORGE_DDS_BACKEND` accepts `mock` (default), `cyclone`, `fast` and `auto` (`fast`, then `cyclone`, then `mock`, whichever binding imports). The default `mock` selects no DDS backend: installing the Cyclone binding is not enough, you must also set `TOPICFORGE_DDS_BACKEND=cyclone`. With `TOPICFORGE_MODE=live` and no backend selected, the DDS tools raise `DDS module is not active: ...` with the actual cause (backend not selected, binding not installed, or binding installed but the adapter failed to start), and `health_check` reports `dds_backend: "none"` plus `dds_inactive_note`. An explicit value is honoured with or without `ros2` on PATH, in any mode except `mock`. If the binding is missing or the participant cannot start, the server logs a warning naming the cause and falls back to the ROS2 CLI alone, or to the mock fixtures. When both `ros2` and a DDS backend are up, a composite adapter routes the six `ros2` CLI tools (`list_topics`, `get_topic_info`, `list_nodes`, `get_node_info`, `sample_messages`, `analyze_bag`) to the CLI and the six DDS tools to the DDS backend; `peek_bag_samples` reads bags without the CLI.
|
|
149
154
|
|
|
150
|
-
A Fast DDS adapter exists but has never run against a bus, and its `fastdds` Python binding is not on PyPI: build it from eProsima's sources and install it next to TopicForge. There is no `[dds-fast]` extra.
|
|
155
|
+
A Fast DDS adapter exists but has never run against a bus, and its `fastdds` Python binding is not on PyPI: build it from eProsima's sources and install it next to TopicForge. There is no `[dds-fast]` extra. Any other value (`rti`, `opensplice`, `coredx`, `intercom`, `opendds`, `dust`) is rejected at startup with an error that lists the valid ones; Cyclone already sees those vendors' participants through standard discovery. Full backend selection, the routing table and the QoS mismatch scenario are in [`docs/DDS_QUICKSTART.md`](docs/DDS_QUICKSTART.md); error messages are in [`docs/TROUBLESHOOTING.md`](docs/TROUBLESHOOTING.md).
|
|
151
156
|
|
|
152
157
|
## Configuration reference
|
|
153
158
|
|
|
@@ -157,26 +162,28 @@ A Fast DDS adapter exists but has never run against a bus, and its `fastdds` Pyt
|
|
|
157
162
|
| `TOPICFORGE_LOG_LEVEL` | `INFO` | `DEBUG`, `INFO`, `WARNING`, `ERROR` |
|
|
158
163
|
| `TOPICFORGE_ROS2_BIN` | `ros2` | Name or path of the ROS2 CLI binary |
|
|
159
164
|
| `TOPICFORGE_TELEMETRY` | `off` | Opt-in anonymous telemetry; an unrecognized value aborts startup. See [Telemetry](#telemetry) |
|
|
160
|
-
| `TOPICFORGE_DDS_BACKEND` | `mock` | `mock` (no DDS backend), `cyclone`, `fast`, `auto
|
|
165
|
+
| `TOPICFORGE_DDS_BACKEND` | `mock` | `mock` (no DDS backend), `cyclone`, `fast`, `auto`; anything else is rejected |
|
|
161
166
|
| `TOPICFORGE_DDS_DOMAIN_ID` | `0` | DDS domain observed (0..232). Joined at startup; changing it needs a restart |
|
|
162
167
|
| `TOPICFORGE_MAX_SAMPLE_BYTES` | `1048576` | Size cap for one sampled message (1 KiB..64 MiB); a call returns at most 4 times that. Over-cap messages are dropped with a note |
|
|
163
168
|
|
|
169
|
+
Command-line options (not environment variables): `--transport stdio|streamable-http` (default `stdio`) and `--port N` (default 8765, only with `streamable-http`). The HTTP transport binds `127.0.0.1` only, with DNS-rebinding protection and no authentication; see [docs/CLIENTS.md](docs/CLIENTS.md#local-http-transport).
|
|
170
|
+
|
|
164
171
|
Samples with comments are in [`.env.example`](.env.example). Any invalid value stops the server with `topicforge: configuration error: ...` and exit code 2, so a typo cannot silently change behaviour.
|
|
165
172
|
|
|
166
173
|
## Limitations
|
|
167
174
|
|
|
168
|
-
External validation against a simulated robot's ground truth: [docs/VALIDATION.md](docs/VALIDATION.md).
|
|
175
|
+
External validation against a simulated robot's ground truth (OmniSim): [docs/VALIDATION.md](docs/VALIDATION.md).
|
|
169
176
|
|
|
170
|
-
- DDS validation is partial. The Cyclone adapter has run against
|
|
177
|
+
- DDS validation is partial. The Cyclone adapter has run against real buses with Cyclone, Fast DDS and Dust DDS participants, on Windows and in CI on Ubuntu and Windows (`.github/workflows/demo.yml`, `.github/workflows/ros2-live.yml`). The Fast DDS adapter (TopicForge joining through the `fastdds` binding) has never run against a bus, and no RTI, OpenDDS, CoreDX or OpenSplice participant has been observed by this project. The multi-vendor claim rests on the RTPS protocol guarantee, not on a recorded cross-vendor run.
|
|
171
178
|
- User-topic payloads are not decoded. `peek_dds_samples` on a user topic returns count 0 and a note that the topic is announced on the bus; no traffic is read. `topic_metrics` therefore has data only for the builtin discovery topics and says so in its `status`. It is a discovery-layer probe, not a publish-rate monitor.
|
|
172
179
|
- Liveliness at runtime is not observed. A writer that is alive but silent (a hung process whose lease is still renewed) looks healthy, because TopicForge reads discovery, not data. An opt-in data probe is planned. A crash and a clean leave cannot be told apart, and `lost_ns` is an upper bound of the death.
|
|
173
180
|
- Cyclone vendor ids: participants that do not follow the RTPS vendor-id convention in their GUID prefix (Dust DDS, and RTI by default) are reported with vendor `unknown`.
|
|
174
181
|
- Single domain: the server observes the domain it joined at startup; changing it needs a restart.
|
|
175
182
|
- DDS Security is not handled. A participant without credentials sees an empty secure bus. `detect_qos_mismatches` checks Partition, type name, Reliability, Durability, Deadline, Liveliness, LatencyBudget, Ownership (kind), DestinationOrder and DataRepresentation (History as a risk); Presentation, XTypes assignability and runtime behavior are not checked, and the result lists them in `policies_unchecked`. It returns a `MismatchScan` envelope: read `reports` for the mismatches.
|
|
176
183
|
- Fast DDS serves no `list_endpoints`.
|
|
177
|
-
- `sample_messages` (live) streams `ros2 topic echo` until `count` messages arrive or `timeout_s` (1..
|
|
184
|
+
- `sample_messages` (live) streams `ros2 topic echo` until `count` messages arrive or `timeout_s` (1..40, default 10, for the whole call) runs out, and returns what arrived with a `note` (`N of M messages within T s`, saying whether a publisher exists). QoS is matched to the publishers, so latched topics work. The payload has nested named fields (`payload.header.stamp.sec`); `timestamp_ns` is `header.stamp` (the publisher's clock, sim time on a simulation), the time in the body of `/clock`, `/tf` and `/rosout` messages (`stamp_source` `payload`), or 0 for other headerless types, with `stamp_source` and `received_ns` (wall clock when the CLI printed it). Arrays are cut at 128 elements by default; `max_array_length` (1..65536, or null for no cut) and `arrays_summary_only` change that, and a cut is listed under `_truncated_fields`. `nan` and `inf` come back as strings. `count` above 50 is capped with a note.
|
|
178
185
|
- `analyze_bag` (live) parses `ros2 bag info` text for the totals and counts; anomaly detection is mock-only. Per-topic times, rates (`(n - 1) / span`) and `latched` are added when the bag can be read locally (`.db3` with the standard library, `.mcap` with `rosbags`), else rates fall back to count / bag duration (`frequency_basis`). `peek_bag_samples` reads the file itself, through `rosbags`, returns the first `count` messages in recording order (not the last), and is served only by the ROS2 CLI adapter or the mock; its `timestamp_ns` is the message's `header.stamp` (`stamp_source` `header`), the body time of `Clock`, `TFMessage` and `Log` messages (`payload`) or, without either, the bag record time (`stamp_source` `recorded`), and `recorded_ns` is always the bag record time; bags that embed no message definitions (Humble `.db3`) are decoded with the Humble definitions, or the distro the bag records, and `note` says so. Without `ros2`, bag tools return fixtures: check `health_check` for `mode: "mock"` before trusting bag output. `health_check` also reports `sim_clock_published` (live only): true when `/clock` has a publisher, a hint that header stamps may be simulation time.
|
|
179
|
-
-
|
|
186
|
+
- Calls are serialized per backend (one `ros2` call at a time, one DDS call at a time) and each call has a 45 s wall budget, lock wait included; a call that cannot get its lane fails at once with `busy`. `health_check` and `peek_bag_samples` take no lock.
|
|
180
187
|
- No streaming or push subscriptions: tools are strictly request/response.
|
|
181
188
|
|
|
182
189
|
Next: an opt-in probe to tell a hung writer from a healthy one, wider real-bus validation (Fast DDS, RTI, OpenDDS), and DDS Security. Open work is tracked in [issues](https://github.com/yaniswav/TopicForge/issues).
|
|
@@ -195,10 +202,10 @@ When on, each tool call emits one event with exactly six fields:
|
|
|
195
202
|
|
|
196
203
|
| Field | Example | Notes |
|
|
197
204
|
| ------------ | --------------- | ----------------------------------------------------------- |
|
|
198
|
-
| `tool_name` | `"list_topics"` | One of the
|
|
205
|
+
| `tool_name` | `"list_topics"` | One of the fourteen tools, never argument values |
|
|
199
206
|
| `latency_ms` | `12.34` | Handler wall-clock duration, 2 decimals |
|
|
200
207
|
| `mode` | `"mock"` | Mode of the adapter actually serving: `mock` or `live` |
|
|
201
|
-
| `version` | `"0.
|
|
208
|
+
| `version` | `"0.7.1"` | TopicForge server version |
|
|
202
209
|
| `session_id` | `"a1b2c3..."` | Random UUID per process, never persisted |
|
|
203
210
|
| `success` | `true` | Whether the handler returned or raised |
|
|
204
211
|
|
|
@@ -230,14 +237,14 @@ Tests run against the mock adapter, the live adapter's pure parsers and the bind
|
|
|
230
237
|
|
|
231
238
|
## Upgrading
|
|
232
239
|
|
|
233
|
-
TopicForge is pre-1.0; [`CHANGELOG.md`](CHANGELOG.md) lists every change, including the yanked releases and removed extras.
|
|
240
|
+
TopicForge is pre-1.0; [`CHANGELOG.md`](CHANGELOG.md) lists every change, including the yanked releases and removed extras. 0.7.0 changed the shape of tool results (`contract_version` 2): [`docs/MIGRATION-0.7.md`](docs/MIGRATION-0.7.md) maps every old field to its new one.
|
|
234
241
|
|
|
235
242
|
## Layout
|
|
236
243
|
|
|
237
244
|
```
|
|
238
245
|
src/topicforge/
|
|
239
|
-
server/ MCP bootstrap, build_app(settings)
|
|
240
|
-
tools/ thin
|
|
246
|
+
server/ MCP bootstrap, build_app(settings), prompts and instructions
|
|
247
|
+
tools/ thin MCPServer handlers, no backend logic
|
|
241
248
|
services/ input validation, orchestration, adapter factory
|
|
242
249
|
adapters/ ros2_live, ros2_mock, dds_cyclone, dds_fast, common/ (binding-free logic)
|
|
243
250
|
models/ frozen Pydantic schemas, the contract with MCP clients
|
|
@@ -12,9 +12,9 @@ A read-only MCP (Model Context Protocol) server that lets an AI agent inspect a
|
|
|
12
12
|
|
|
13
13
|
Every tool also declares itself read-only in the MCP protocol (`readOnlyHint`, `destructiveHint=false`, `idempotentHint`, with an honest `openWorldHint`), and a test fails if any tool lacks it. See [`SECURITY.md`](SECURITY.md#read-only-guarantee-architecture-declaration-proof) for the guarantee and its limits.
|
|
14
14
|
|
|
15
|
-
It gives the agent
|
|
15
|
+
It gives the agent fourteen typed tools that return frozen Pydantic schemas (output contract 2: one object per tool, one `note`, units in the field names; see [`docs/CONTRACT.md`](docs/CONTRACT.md)), identical whether the server talks to a real robot or to its built-in mock fixtures. Ask why `nav_planner` gets no scan, and the agent reads the bus, finds the BEST_EFFORT writer facing a RELIABLE reader and names the incompatible policy (see [`examples/02-debug-qos-mismatch.md`](examples/02-debug-qos-mismatch.md)). It is meant for ROS2 developers, robotics ML/CV engineers and teams that cannot accept a write path into a production stack.
|
|
16
16
|
|
|
17
|
-
For DDS, TopicForge joins a domain as a read-only participant through one open-source binding (Eclipse CycloneDDS from PyPI) and reads the builtin discovery topics that the OMG DDS-RTPS protocol standardizes.
|
|
17
|
+
For DDS, TopicForge joins a domain as a read-only participant through one open-source binding (Eclipse CycloneDDS from PyPI) and reads the builtin discovery topics that the OMG DDS-RTPS protocol standardizes. Cyclone DDS, Fast DDS and Dust DDS participants have been observed on live buses: the Fast DDS ones on every CI run of the ROS 2 test bench (Humble and Jazzy with `rmw_fastrtps_cpp`) and in the OmniSim validation. RTI Connext, OpenDDS and CoreDX announce themselves through the same standard discovery, but none of them has been observed yet. This covers discovery only: participants, readers, writers and their QoS. See [`docs/dds-interop-matrix.md`](docs/dds-interop-matrix.md).
|
|
18
18
|
|
|
19
19
|
## Quickstart
|
|
20
20
|
|
|
@@ -40,21 +40,23 @@ The server speaks MCP over stdio and waits for a client, so wire it into one. Fo
|
|
|
40
40
|
}
|
|
41
41
|
```
|
|
42
42
|
|
|
43
|
-
Then ask it to list the topics or to analyze `/tmp/demo.mcap`. For Claude Code: `claude mcp add topicforge -- topicforge`.
|
|
43
|
+
Then ask it to list the topics or to analyze `/tmp/demo.mcap`. For Claude Code: `claude mcp add topicforge -- topicforge`. If the client cannot launch the server (WSL2 to Windows, a robot over an SSH tunnel), `topicforge --transport streamable-http --port 8765` serves `http://127.0.0.1:8765/mcp` on loopback only; see [`docs/CLIENTS.md`](docs/CLIENTS.md#local-http-transport).
|
|
44
44
|
|
|
45
|
-
**Other MCP clients.** Ready-to-paste configs for Claude Desktop, Cursor, VS Code, Windsurf, Cline, Roo Code, Continue, Zed, JetBrains, Codex, Gemini CLI, Goose and more are in [`docs/CLIENTS.md`](docs/CLIENTS.md). One-click: [Add to Cursor](https://cursor.com/install-mcp?name=topicforge&config=
|
|
45
|
+
**Other MCP clients.** Ready-to-paste configs for Claude Desktop, Cursor, VS Code, Windsurf, Cline, Roo Code, Continue, Zed, JetBrains, Codex, Gemini CLI, Goose and more are in [`docs/CLIENTS.md`](docs/CLIENTS.md). One-click: [Add to Cursor](https://cursor.com/install-mcp?name=topicforge&config=eyJjb21tYW5kIjoidXZ4IiwiYXJncyI6WyItLWZyb20iLCJ0b3BpY2ZvcmdlPT0wLjcuMSIsInRvcGljZm9yZ2UiXSwiZW52Ijp7IlRPUElDRk9SR0VfTU9ERSI6ImF1dG8iLCJUT1BJQ0ZPUkdFX0REU19CQUNLRU5EIjoiY3ljbG9uZSIsIlRPUElDRk9SR0VfRERTX0RPTUFJTl9JRCI6IjAifX0%3D) | [Install in VS Code](https://vscode.dev/redirect/mcp/install?name=topicforge&config=%7B%22command%22%3A%22uvx%22%2C%22args%22%3A%5B%22--from%22%2C%22topicforge%3D%3D0.7.1%22%2C%22topicforge%22%5D%2C%22env%22%3A%7B%22TOPICFORGE_MODE%22%3A%22auto%22%2C%22TOPICFORGE_DDS_BACKEND%22%3A%22cyclone%22%2C%22TOPICFORGE_DDS_DOMAIN_ID%22%3A%220%22%7D%7D).
|
|
46
46
|
|
|
47
47
|
Setup for a real ROS2 environment (WSL2, Linux, Docker, native Windows) is in [`docs/TESTING.md`](docs/TESTING.md); recurring monitoring prompts and the privacy contract are in [`docs/TUTORIEL.md`](docs/TUTORIEL.md).
|
|
48
48
|
|
|
49
49
|
## Tools
|
|
50
50
|
|
|
51
|
-
Every response except `health_check` carries `mode_effective` (`"live"` or `"mock"`), so a caller can tell a real graph from fixtures.
|
|
51
|
+
Every response except `health_check` carries `mode_effective` (`"live"` or `"mock"`), so a caller can tell a real graph from fixtures, and a single `note` that explains anything unusual. `health_check.contract_version` is `2`. The full reference, with every field, is generated in [`docs/TOOLS.md`](docs/TOOLS.md); upgrading from 0.6.x: [`docs/MIGRATION-0.7.md`](docs/MIGRATION-0.7.md).
|
|
52
52
|
|
|
53
53
|
| Tool | Purpose |
|
|
54
54
|
| ----------------------- | ------------------------------------------------------------------------------------------------ |
|
|
55
55
|
| `health_check` | Environment and mode introspection. Always succeeds; reports `mode` next to `requested_mode` |
|
|
56
56
|
| `list_topics` | Discover the ROS2 graph |
|
|
57
57
|
| `get_topic_info` | Message type, publisher/subscriber counts and QoS for one topic |
|
|
58
|
+
| `list_nodes` | ROS 2 nodes on the graph: names, namespaces, duplicate names (one `ros2 node list` call) |
|
|
59
|
+
| `get_node_info` | One node's publishers, subscribers, services, actions, parameters (secrets masked) and `use_sim_time`; a node that does not answer is reported, not an error |
|
|
58
60
|
| `sample_messages` | Peek recent messages on a ROS2 topic (count capped at 50) |
|
|
59
61
|
| `analyze_bag` | Summarize a `.mcap` / `.db3` recording or `rosbag2_*` directory (via `ros2 bag info`) |
|
|
60
62
|
| `list_participants` | DDS participants on the domain: vendor, `name` (EntityName QoS, Cyclone) and `hostname` |
|
|
@@ -65,6 +67,8 @@ Every response except `health_check` carries `mode_effective` (`"live"` or `"moc
|
|
|
65
67
|
| `peek_bag_samples` | Decoded samples from a recorded bag, including ROS 1 `.bag` (`rosbags` ships with the package) |
|
|
66
68
|
| `list_endpoints` | DDS writers and readers with structured QoS, per-topic roll-up that flags orphans (writer with no reader, reader with no writer) |
|
|
67
69
|
|
|
70
|
+
The server also offers two MCP prompts, `diagnose-dds-bus` and `inspect-ros2-robot`, that give a client the read-only call order, and short server `instructions` for clients that do not show prompts (see [`docs/CLIENTS.md`](docs/CLIENTS.md#prompts-and-instructions)).
|
|
71
|
+
|
|
68
72
|
Walkthroughs against the mock, each with the exact tool calls and payloads, are in [`examples/`](examples/README.md). To run the DDS tools against a real bus with several programs and vendors, see [`examples/dds/README.md`](examples/dds/README.md) (`python examples/dds/run_all.py`).
|
|
69
73
|
|
|
70
74
|
## Modes
|
|
@@ -85,9 +89,9 @@ pip install "topicforge[dds]" # alias, kept so older commands
|
|
|
85
89
|
TOPICFORGE_DDS_BACKEND=cyclone python -m topicforge
|
|
86
90
|
```
|
|
87
91
|
|
|
88
|
-
`TOPICFORGE_DDS_BACKEND` accepts `mock` (default), `cyclone`, `fast` and `auto` (`fast`, then `cyclone`, then `mock`, whichever binding imports). The default `mock` selects no DDS backend: installing the Cyclone binding is not enough, you must also set `TOPICFORGE_DDS_BACKEND=cyclone`. With `TOPICFORGE_MODE=live` and no backend selected, the DDS tools raise `DDS module is not active: ...` with the actual cause (backend not selected, binding not installed, or binding installed but the adapter failed to start), and `health_check` reports `dds_backend: "none"` plus `
|
|
92
|
+
`TOPICFORGE_DDS_BACKEND` accepts `mock` (default), `cyclone`, `fast` and `auto` (`fast`, then `cyclone`, then `mock`, whichever binding imports). The default `mock` selects no DDS backend: installing the Cyclone binding is not enough, you must also set `TOPICFORGE_DDS_BACKEND=cyclone`. With `TOPICFORGE_MODE=live` and no backend selected, the DDS tools raise `DDS module is not active: ...` with the actual cause (backend not selected, binding not installed, or binding installed but the adapter failed to start), and `health_check` reports `dds_backend: "none"` plus `dds_inactive_note`. An explicit value is honoured with or without `ros2` on PATH, in any mode except `mock`. If the binding is missing or the participant cannot start, the server logs a warning naming the cause and falls back to the ROS2 CLI alone, or to the mock fixtures. When both `ros2` and a DDS backend are up, a composite adapter routes the six `ros2` CLI tools (`list_topics`, `get_topic_info`, `list_nodes`, `get_node_info`, `sample_messages`, `analyze_bag`) to the CLI and the six DDS tools to the DDS backend; `peek_bag_samples` reads bags without the CLI.
|
|
89
93
|
|
|
90
|
-
A Fast DDS adapter exists but has never run against a bus, and its `fastdds` Python binding is not on PyPI: build it from eProsima's sources and install it next to TopicForge. There is no `[dds-fast]` extra.
|
|
94
|
+
A Fast DDS adapter exists but has never run against a bus, and its `fastdds` Python binding is not on PyPI: build it from eProsima's sources and install it next to TopicForge. There is no `[dds-fast]` extra. Any other value (`rti`, `opensplice`, `coredx`, `intercom`, `opendds`, `dust`) is rejected at startup with an error that lists the valid ones; Cyclone already sees those vendors' participants through standard discovery. Full backend selection, the routing table and the QoS mismatch scenario are in [`docs/DDS_QUICKSTART.md`](docs/DDS_QUICKSTART.md); error messages are in [`docs/TROUBLESHOOTING.md`](docs/TROUBLESHOOTING.md).
|
|
91
95
|
|
|
92
96
|
## Configuration reference
|
|
93
97
|
|
|
@@ -97,26 +101,28 @@ A Fast DDS adapter exists but has never run against a bus, and its `fastdds` Pyt
|
|
|
97
101
|
| `TOPICFORGE_LOG_LEVEL` | `INFO` | `DEBUG`, `INFO`, `WARNING`, `ERROR` |
|
|
98
102
|
| `TOPICFORGE_ROS2_BIN` | `ros2` | Name or path of the ROS2 CLI binary |
|
|
99
103
|
| `TOPICFORGE_TELEMETRY` | `off` | Opt-in anonymous telemetry; an unrecognized value aborts startup. See [Telemetry](#telemetry) |
|
|
100
|
-
| `TOPICFORGE_DDS_BACKEND` | `mock` | `mock` (no DDS backend), `cyclone`, `fast`, `auto
|
|
104
|
+
| `TOPICFORGE_DDS_BACKEND` | `mock` | `mock` (no DDS backend), `cyclone`, `fast`, `auto`; anything else is rejected |
|
|
101
105
|
| `TOPICFORGE_DDS_DOMAIN_ID` | `0` | DDS domain observed (0..232). Joined at startup; changing it needs a restart |
|
|
102
106
|
| `TOPICFORGE_MAX_SAMPLE_BYTES` | `1048576` | Size cap for one sampled message (1 KiB..64 MiB); a call returns at most 4 times that. Over-cap messages are dropped with a note |
|
|
103
107
|
|
|
108
|
+
Command-line options (not environment variables): `--transport stdio|streamable-http` (default `stdio`) and `--port N` (default 8765, only with `streamable-http`). The HTTP transport binds `127.0.0.1` only, with DNS-rebinding protection and no authentication; see [docs/CLIENTS.md](docs/CLIENTS.md#local-http-transport).
|
|
109
|
+
|
|
104
110
|
Samples with comments are in [`.env.example`](.env.example). Any invalid value stops the server with `topicforge: configuration error: ...` and exit code 2, so a typo cannot silently change behaviour.
|
|
105
111
|
|
|
106
112
|
## Limitations
|
|
107
113
|
|
|
108
|
-
External validation against a simulated robot's ground truth: [docs/VALIDATION.md](docs/VALIDATION.md).
|
|
114
|
+
External validation against a simulated robot's ground truth (OmniSim): [docs/VALIDATION.md](docs/VALIDATION.md).
|
|
109
115
|
|
|
110
|
-
- DDS validation is partial. The Cyclone adapter has run against
|
|
116
|
+
- DDS validation is partial. The Cyclone adapter has run against real buses with Cyclone, Fast DDS and Dust DDS participants, on Windows and in CI on Ubuntu and Windows (`.github/workflows/demo.yml`, `.github/workflows/ros2-live.yml`). The Fast DDS adapter (TopicForge joining through the `fastdds` binding) has never run against a bus, and no RTI, OpenDDS, CoreDX or OpenSplice participant has been observed by this project. The multi-vendor claim rests on the RTPS protocol guarantee, not on a recorded cross-vendor run.
|
|
111
117
|
- User-topic payloads are not decoded. `peek_dds_samples` on a user topic returns count 0 and a note that the topic is announced on the bus; no traffic is read. `topic_metrics` therefore has data only for the builtin discovery topics and says so in its `status`. It is a discovery-layer probe, not a publish-rate monitor.
|
|
112
118
|
- Liveliness at runtime is not observed. A writer that is alive but silent (a hung process whose lease is still renewed) looks healthy, because TopicForge reads discovery, not data. An opt-in data probe is planned. A crash and a clean leave cannot be told apart, and `lost_ns` is an upper bound of the death.
|
|
113
119
|
- Cyclone vendor ids: participants that do not follow the RTPS vendor-id convention in their GUID prefix (Dust DDS, and RTI by default) are reported with vendor `unknown`.
|
|
114
120
|
- Single domain: the server observes the domain it joined at startup; changing it needs a restart.
|
|
115
121
|
- DDS Security is not handled. A participant without credentials sees an empty secure bus. `detect_qos_mismatches` checks Partition, type name, Reliability, Durability, Deadline, Liveliness, LatencyBudget, Ownership (kind), DestinationOrder and DataRepresentation (History as a risk); Presentation, XTypes assignability and runtime behavior are not checked, and the result lists them in `policies_unchecked`. It returns a `MismatchScan` envelope: read `reports` for the mismatches.
|
|
116
122
|
- Fast DDS serves no `list_endpoints`.
|
|
117
|
-
- `sample_messages` (live) streams `ros2 topic echo` until `count` messages arrive or `timeout_s` (1..
|
|
123
|
+
- `sample_messages` (live) streams `ros2 topic echo` until `count` messages arrive or `timeout_s` (1..40, default 10, for the whole call) runs out, and returns what arrived with a `note` (`N of M messages within T s`, saying whether a publisher exists). QoS is matched to the publishers, so latched topics work. The payload has nested named fields (`payload.header.stamp.sec`); `timestamp_ns` is `header.stamp` (the publisher's clock, sim time on a simulation), the time in the body of `/clock`, `/tf` and `/rosout` messages (`stamp_source` `payload`), or 0 for other headerless types, with `stamp_source` and `received_ns` (wall clock when the CLI printed it). Arrays are cut at 128 elements by default; `max_array_length` (1..65536, or null for no cut) and `arrays_summary_only` change that, and a cut is listed under `_truncated_fields`. `nan` and `inf` come back as strings. `count` above 50 is capped with a note.
|
|
118
124
|
- `analyze_bag` (live) parses `ros2 bag info` text for the totals and counts; anomaly detection is mock-only. Per-topic times, rates (`(n - 1) / span`) and `latched` are added when the bag can be read locally (`.db3` with the standard library, `.mcap` with `rosbags`), else rates fall back to count / bag duration (`frequency_basis`). `peek_bag_samples` reads the file itself, through `rosbags`, returns the first `count` messages in recording order (not the last), and is served only by the ROS2 CLI adapter or the mock; its `timestamp_ns` is the message's `header.stamp` (`stamp_source` `header`), the body time of `Clock`, `TFMessage` and `Log` messages (`payload`) or, without either, the bag record time (`stamp_source` `recorded`), and `recorded_ns` is always the bag record time; bags that embed no message definitions (Humble `.db3`) are decoded with the Humble definitions, or the distro the bag records, and `note` says so. Without `ros2`, bag tools return fixtures: check `health_check` for `mode: "mock"` before trusting bag output. `health_check` also reports `sim_clock_published` (live only): true when `/clock` has a publisher, a hint that header stamps may be simulation time.
|
|
119
|
-
-
|
|
125
|
+
- Calls are serialized per backend (one `ros2` call at a time, one DDS call at a time) and each call has a 45 s wall budget, lock wait included; a call that cannot get its lane fails at once with `busy`. `health_check` and `peek_bag_samples` take no lock.
|
|
120
126
|
- No streaming or push subscriptions: tools are strictly request/response.
|
|
121
127
|
|
|
122
128
|
Next: an opt-in probe to tell a hung writer from a healthy one, wider real-bus validation (Fast DDS, RTI, OpenDDS), and DDS Security. Open work is tracked in [issues](https://github.com/yaniswav/TopicForge/issues).
|
|
@@ -135,10 +141,10 @@ When on, each tool call emits one event with exactly six fields:
|
|
|
135
141
|
|
|
136
142
|
| Field | Example | Notes |
|
|
137
143
|
| ------------ | --------------- | ----------------------------------------------------------- |
|
|
138
|
-
| `tool_name` | `"list_topics"` | One of the
|
|
144
|
+
| `tool_name` | `"list_topics"` | One of the fourteen tools, never argument values |
|
|
139
145
|
| `latency_ms` | `12.34` | Handler wall-clock duration, 2 decimals |
|
|
140
146
|
| `mode` | `"mock"` | Mode of the adapter actually serving: `mock` or `live` |
|
|
141
|
-
| `version` | `"0.
|
|
147
|
+
| `version` | `"0.7.1"` | TopicForge server version |
|
|
142
148
|
| `session_id` | `"a1b2c3..."` | Random UUID per process, never persisted |
|
|
143
149
|
| `success` | `true` | Whether the handler returned or raised |
|
|
144
150
|
|
|
@@ -170,14 +176,14 @@ Tests run against the mock adapter, the live adapter's pure parsers and the bind
|
|
|
170
176
|
|
|
171
177
|
## Upgrading
|
|
172
178
|
|
|
173
|
-
TopicForge is pre-1.0; [`CHANGELOG.md`](CHANGELOG.md) lists every change, including the yanked releases and removed extras.
|
|
179
|
+
TopicForge is pre-1.0; [`CHANGELOG.md`](CHANGELOG.md) lists every change, including the yanked releases and removed extras. 0.7.0 changed the shape of tool results (`contract_version` 2): [`docs/MIGRATION-0.7.md`](docs/MIGRATION-0.7.md) maps every old field to its new one.
|
|
174
180
|
|
|
175
181
|
## Layout
|
|
176
182
|
|
|
177
183
|
```
|
|
178
184
|
src/topicforge/
|
|
179
|
-
server/ MCP bootstrap, build_app(settings)
|
|
180
|
-
tools/ thin
|
|
185
|
+
server/ MCP bootstrap, build_app(settings), prompts and instructions
|
|
186
|
+
tools/ thin MCPServer handlers, no backend logic
|
|
181
187
|
services/ input validation, orchestration, adapter factory
|
|
182
188
|
adapters/ ros2_live, ros2_mock, dds_cyclone, dds_fast, common/ (binding-free logic)
|
|
183
189
|
models/ frozen Pydantic schemas, the contract with MCP clients
|