topicforge 0.5.5__tar.gz → 0.5.6__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.5.5 → topicforge-0.5.6}/.gitignore +7 -36
- {topicforge-0.5.5 → topicforge-0.5.6}/CHANGELOG.md +168 -200
- {topicforge-0.5.5 → topicforge-0.5.6}/PKG-INFO +34 -33
- {topicforge-0.5.5 → topicforge-0.5.6}/README.md +31 -30
- {topicforge-0.5.5 → topicforge-0.5.6}/docs/DDS_QUICKSTART.md +15 -15
- {topicforge-0.5.5 → topicforge-0.5.6}/docs/TESTING.md +5 -5
- {topicforge-0.5.5 → topicforge-0.5.6}/docs/TROUBLESHOOTING.md +11 -11
- {topicforge-0.5.5 → topicforge-0.5.6}/docs/TUTORIEL.md +16 -20
- topicforge-0.5.6/docs/dds-interop-matrix.md +55 -0
- {topicforge-0.5.5 → topicforge-0.5.6}/examples/README.md +2 -2
- {topicforge-0.5.5 → topicforge-0.5.6}/examples/dds/00_hello_pub_sub/README.md +17 -23
- topicforge-0.5.6/examples/dds/01_who_is_on_the_bus/README.md +47 -0
- {topicforge-0.5.5 → topicforge-0.5.6}/examples/dds/02_why_cant_they_talk/README.md +18 -22
- topicforge-0.5.6/examples/dds/03_a_node_crashed/README.md +56 -0
- {topicforge-0.5.5 → topicforge-0.5.6}/examples/dds/04_late_joiner_misses_data/README.md +12 -16
- {topicforge-0.5.5 → topicforge-0.5.6}/examples/dds/05_reliability_in_code/README.md +14 -16
- {topicforge-0.5.5 → topicforge-0.5.6}/examples/dds/06_durability_late_joiner_in_code/README.md +24 -30
- {topicforge-0.5.5 → topicforge-0.5.6}/examples/dds/07_deadline_in_code/README.md +15 -22
- {topicforge-0.5.5 → topicforge-0.5.6}/examples/dds/08_crash_seen_from_inside/README.md +21 -29
- {topicforge-0.5.5 → topicforge-0.5.6}/examples/dds/10_lidar_silent_after_driver_swap/README.md +17 -20
- {topicforge-0.5.5 → topicforge-0.5.6}/examples/dds/11_who_talks_to_whom/README.md +17 -20
- topicforge-0.5.6/examples/dds/12_safety_monitor_dropout/README.md +59 -0
- {topicforge-0.5.5 → topicforge-0.5.6}/examples/dds/13_deadline_not_offered/README.md +18 -23
- {topicforge-0.5.5 → topicforge-0.5.6}/examples/dds/14_restart_loop/README.md +14 -18
- {topicforge-0.5.5 → topicforge-0.5.6}/examples/dds/README.md +4 -4
- {topicforge-0.5.5 → topicforge-0.5.6}/pyproject.toml +4 -4
- {topicforge-0.5.5 → topicforge-0.5.6}/scripts/agent_eval/README.md +6 -5
- {topicforge-0.5.5 → topicforge-0.5.6}/scripts/integration/README.md +12 -14
- {topicforge-0.5.5 → topicforge-0.5.6}/scripts/integration/publishers/fast_publisher_cpp/README.md +2 -2
- {topicforge-0.5.5 → topicforge-0.5.6}/scripts/integration/publishers/fast_py/README.md +3 -3
- {topicforge-0.5.5 → topicforge-0.5.6}/scripts/integration/publishers/opensplice_publisher/README.md +2 -2
- {topicforge-0.5.5 → topicforge-0.5.6}/src/topicforge/__init__.py +1 -1
- topicforge-0.5.6/src/topicforge/adapters/base.py +131 -0
- {topicforge-0.5.5 → topicforge-0.5.6}/src/topicforge/adapters/common/__init__.py +2 -0
- topicforge-0.5.6/src/topicforge/adapters/common/cdr_decoder.py +176 -0
- {topicforge-0.5.5 → topicforge-0.5.6}/src/topicforge/adapters/common/dds_helpers.py +65 -77
- {topicforge-0.5.5 → topicforge-0.5.6}/src/topicforge/adapters/common/dds_introspection.py +18 -36
- {topicforge-0.5.5 → topicforge-0.5.6}/src/topicforge/adapters/common/discovery_tracker.py +10 -16
- {topicforge-0.5.5 → topicforge-0.5.6}/src/topicforge/adapters/common/endpoints.py +7 -6
- {topicforge-0.5.5 → topicforge-0.5.6}/src/topicforge/adapters/common/lifecycle.py +32 -81
- {topicforge-0.5.5 → topicforge-0.5.6}/src/topicforge/adapters/common/metrics_buffer.py +36 -92
- {topicforge-0.5.5 → topicforge-0.5.6}/src/topicforge/adapters/common/qos_analyzer.py +18 -9
- {topicforge-0.5.5 → topicforge-0.5.6}/src/topicforge/adapters/common/qos_endpoints.py +12 -10
- {topicforge-0.5.5 → topicforge-0.5.6}/src/topicforge/adapters/common/qos_normalize.py +63 -50
- {topicforge-0.5.5 → topicforge-0.5.6}/src/topicforge/adapters/common/qos_scan.py +61 -23
- topicforge-0.5.6/src/topicforge/adapters/common/xtypes.py +97 -0
- {topicforge-0.5.5 → topicforge-0.5.6}/src/topicforge/adapters/composite.py +26 -25
- {topicforge-0.5.5 → topicforge-0.5.6}/src/topicforge/adapters/dds_cyclone/adapter.py +108 -222
- topicforge-0.5.6/src/topicforge/adapters/dds_dust/__init__.py +10 -0
- {topicforge-0.5.5 → topicforge-0.5.6}/src/topicforge/adapters/dds_dust/adapter.py +21 -22
- {topicforge-0.5.5 → topicforge-0.5.6}/src/topicforge/adapters/dds_fast/adapter.py +66 -148
- topicforge-0.5.6/src/topicforge/adapters/dds_opendds/__init__.py +11 -0
- {topicforge-0.5.5 → topicforge-0.5.6}/src/topicforge/adapters/dds_opendds/adapter.py +25 -32
- {topicforge-0.5.5 → topicforge-0.5.6}/src/topicforge/adapters/ros2_live/adapter.py +194 -137
- topicforge-0.5.6/src/topicforge/adapters/ros2_live/parsers.py +113 -0
- {topicforge-0.5.5 → topicforge-0.5.6}/src/topicforge/adapters/ros2_mock/adapter.py +17 -20
- {topicforge-0.5.5 → topicforge-0.5.6}/src/topicforge/adapters/ros2_mock/fixtures.py +48 -99
- {topicforge-0.5.5 → topicforge-0.5.6}/src/topicforge/config/settings.py +55 -67
- topicforge-0.5.6/src/topicforge/constants.py +25 -0
- {topicforge-0.5.5 → topicforge-0.5.6}/src/topicforge/models/schemas.py +141 -67
- {topicforge-0.5.5 → topicforge-0.5.6}/src/topicforge/server/app.py +6 -16
- topicforge-0.5.6/src/topicforge/services/bag_service.py +412 -0
- topicforge-0.5.6/src/topicforge/services/bag_stats.py +218 -0
- {topicforge-0.5.5 → topicforge-0.5.6}/src/topicforge/services/factory.py +83 -55
- {topicforge-0.5.5 → topicforge-0.5.6}/src/topicforge/services/health.py +19 -21
- {topicforge-0.5.5 → topicforge-0.5.6}/src/topicforge/services/inspector.py +81 -51
- topicforge-0.5.6/src/topicforge/services/sample_budget.py +53 -0
- topicforge-0.5.6/src/topicforge/telemetry/__init__.py +22 -0
- {topicforge-0.5.5 → topicforge-0.5.6}/src/topicforge/telemetry/client.py +19 -41
- {topicforge-0.5.5 → topicforge-0.5.6}/src/topicforge/tools/handlers.py +180 -142
- {topicforge-0.5.5 → topicforge-0.5.6}/tests/conftest.py +16 -0
- topicforge-0.5.6/tests/fixtures/ros2_topic_info_verbose_cmd_vel.txt +20 -0
- topicforge-0.5.6/tests/fixtures/ros2_topic_info_verbose_parameter_events.txt +104 -0
- topicforge-0.5.6/tests/fixtures/ros2_topic_info_verbose_scan.txt +20 -0
- topicforge-0.5.6/tests/fixtures/ros2_topic_info_verbose_tf.txt +34 -0
- topicforge-0.5.6/tests/fixtures/ros2_topic_info_verbose_tf_static.txt +34 -0
- topicforge-0.5.6/tests/integration/ros2/Dockerfile +28 -0
- topicforge-0.5.6/tests/integration/ros2/__init__.py +0 -0
- topicforge-0.5.6/tests/integration/ros2/entrypoint.sh +43 -0
- topicforge-0.5.6/tests/integration/ros2/publisher.py +112 -0
- topicforge-0.5.6/tests/integration/ros2/run_bench.py +79 -0
- topicforge-0.5.6/tests/integration/ros2/test_live_adapter.py +191 -0
- {topicforge-0.5.5 → topicforge-0.5.6}/tests/test_analyze_bag_multi_format.py +5 -10
- topicforge-0.5.6/tests/test_bag_omnisim_humble.py +150 -0
- {topicforge-0.5.5 → topicforge-0.5.6}/tests/test_bag_service.py +1 -3
- topicforge-0.5.6/tests/test_bag_stats.py +291 -0
- {topicforge-0.5.5 → topicforge-0.5.6}/tests/test_cdr_decoder.py +34 -13
- {topicforge-0.5.5 → topicforge-0.5.6}/tests/test_composite_adapter.py +8 -6
- {topicforge-0.5.5 → topicforge-0.5.6}/tests/test_config.py +7 -7
- {topicforge-0.5.5 → topicforge-0.5.6}/tests/test_cyclone_adapter.py +44 -7
- {topicforge-0.5.5 → topicforge-0.5.6}/tests/test_dds_cross_vendor.py +1 -6
- {topicforge-0.5.5 → topicforge-0.5.6}/tests/test_dds_helpers.py +35 -8
- topicforge-0.5.6/tests/test_dds_inactive_reason.py +94 -0
- {topicforge-0.5.5 → topicforge-0.5.6}/tests/test_dds_introspection.py +4 -5
- {topicforge-0.5.5 → topicforge-0.5.6}/tests/test_dds_qos_normalization.py +4 -6
- {topicforge-0.5.5 → topicforge-0.5.6}/tests/test_dds_schemas.py +1 -1
- {topicforge-0.5.5 → topicforge-0.5.6}/tests/test_discovery_tracker.py +1 -1
- {topicforge-0.5.5 → topicforge-0.5.6}/tests/test_dust_adapter.py +3 -4
- {topicforge-0.5.5 → topicforge-0.5.6}/tests/test_endpoints.py +4 -4
- {topicforge-0.5.5 → topicforge-0.5.6}/tests/test_factory.py +1 -1
- {topicforge-0.5.5 → topicforge-0.5.6}/tests/test_fast_adapter.py +3 -3
- topicforge-0.5.6/tests/test_history_and_hints.py +273 -0
- {topicforge-0.5.5 → topicforge-0.5.6}/tests/test_inspector.py +1 -1
- {topicforge-0.5.5 → topicforge-0.5.6}/tests/test_lifecycle_buffer.py +2 -2
- topicforge-0.5.6/tests/test_live_adapter_graph.py +404 -0
- {topicforge-0.5.5 → topicforge-0.5.6}/tests/test_metrics_buffer.py +7 -9
- {topicforge-0.5.5 → topicforge-0.5.6}/tests/test_mock_adapter.py +7 -8
- {topicforge-0.5.5 → topicforge-0.5.6}/tests/test_opendds_adapter.py +9 -9
- {topicforge-0.5.5 → topicforge-0.5.6}/tests/test_peek_bag_samples.py +1 -1
- {topicforge-0.5.5 → topicforge-0.5.6}/tests/test_qos_analyzer.py +5 -8
- {topicforge-0.5.5 → topicforge-0.5.6}/tests/test_qos_endpoints.py +1 -1
- {topicforge-0.5.5 → topicforge-0.5.6}/tests/test_qos_scan.py +3 -5
- topicforge-0.5.6/tests/test_sample_options.py +217 -0
- {topicforge-0.5.5 → topicforge-0.5.6}/tests/test_telemetry.py +3 -3
- {topicforge-0.5.5 → topicforge-0.5.6}/tests/test_tools_integration.py +8 -13
- {topicforge-0.5.5 → topicforge-0.5.6}/tests/test_topic_metrics.py +1 -1
- topicforge-0.5.5/docs/dds-interop-matrix.md +0 -58
- topicforge-0.5.5/docs/pro.md +0 -38
- topicforge-0.5.5/docs/product-plan.md +0 -183
- topicforge-0.5.5/examples/dds/01_who_is_on_the_bus/README.md +0 -58
- topicforge-0.5.5/examples/dds/03_a_node_crashed/README.md +0 -62
- topicforge-0.5.5/examples/dds/12_safety_monitor_dropout/README.md +0 -70
- topicforge-0.5.5/src/topicforge/adapters/base.py +0 -143
- topicforge-0.5.5/src/topicforge/adapters/common/cdr_decoder.py +0 -203
- topicforge-0.5.5/src/topicforge/adapters/common/xtypes.py +0 -120
- topicforge-0.5.5/src/topicforge/adapters/dds_dust/__init__.py +0 -12
- topicforge-0.5.5/src/topicforge/adapters/dds_opendds/__init__.py +0 -15
- topicforge-0.5.5/src/topicforge/constants.py +0 -28
- topicforge-0.5.5/src/topicforge/services/bag_service.py +0 -273
- topicforge-0.5.5/src/topicforge/telemetry/__init__.py +0 -27
- {topicforge-0.5.5 → topicforge-0.5.6}/LICENSE +0 -0
- {topicforge-0.5.5 → topicforge-0.5.6}/scripts/integration/publishers/cyclone_c/README.md +0 -0
- {topicforge-0.5.5 → topicforge-0.5.6}/scripts/integration/publishers/cyclone_cpp/README.md +0 -0
- {topicforge-0.5.5 → topicforge-0.5.6}/scripts/integration/publishers/cyclone_rust/README.md +0 -0
- {topicforge-0.5.5 → topicforge-0.5.6}/scripts/integration/publishers/dust_py/README.md +0 -0
- {topicforge-0.5.5 → topicforge-0.5.6}/scripts/integration/publishers/rti_c/README.md +0 -0
- {topicforge-0.5.5 → topicforge-0.5.6}/scripts/integration/publishers/rti_cpp/README.md +0 -0
- {topicforge-0.5.5 → topicforge-0.5.6}/src/topicforge/__main__.py +0 -0
- {topicforge-0.5.5 → topicforge-0.5.6}/src/topicforge/adapters/__init__.py +0 -0
- {topicforge-0.5.5 → topicforge-0.5.6}/src/topicforge/adapters/common/topic_filter.py +0 -0
- {topicforge-0.5.5 → topicforge-0.5.6}/src/topicforge/adapters/dds_cyclone/__init__.py +0 -0
- {topicforge-0.5.5 → topicforge-0.5.6}/src/topicforge/adapters/dds_fast/__init__.py +0 -0
- {topicforge-0.5.5 → topicforge-0.5.6}/src/topicforge/adapters/ros2_live/__init__.py +0 -0
- {topicforge-0.5.5 → topicforge-0.5.6}/src/topicforge/adapters/ros2_mock/__init__.py +0 -0
- {topicforge-0.5.5 → topicforge-0.5.6}/src/topicforge/config/__init__.py +0 -0
- {topicforge-0.5.5 → topicforge-0.5.6}/src/topicforge/models/__init__.py +0 -0
- {topicforge-0.5.5 → topicforge-0.5.6}/src/topicforge/server/__init__.py +0 -0
- {topicforge-0.5.5 → topicforge-0.5.6}/src/topicforge/services/__init__.py +0 -0
- {topicforge-0.5.5 → topicforge-0.5.6}/src/topicforge/tools/__init__.py +0 -0
- {topicforge-0.5.5 → topicforge-0.5.6}/tests/__init__.py +0 -0
- {topicforge-0.5.5 → topicforge-0.5.6}/tests/fixtures/csv_echo_imu.txt +0 -0
- {topicforge-0.5.5 → topicforge-0.5.6}/tests/fixtures/csv_echo_pose_multi.txt +0 -0
- {topicforge-0.5.5 → topicforge-0.5.6}/tests/integration/__init__.py +0 -0
- {topicforge-0.5.5 → topicforge-0.5.6}/tests/integration/test_real_bus.py +0 -0
- {topicforge-0.5.5 → topicforge-0.5.6}/tests/test_example_node_spec.py +0 -0
- {topicforge-0.5.5 → topicforge-0.5.6}/tests/test_health.py +0 -0
- {topicforge-0.5.5 → topicforge-0.5.6}/tests/test_honest_outputs.py +0 -0
- {topicforge-0.5.5 → topicforge-0.5.6}/tests/test_live_adapter_parse.py +0 -0
- {topicforge-0.5.5 → topicforge-0.5.6}/tests/test_live_adapter_subprocess.py +0 -0
- {topicforge-0.5.5 → topicforge-0.5.6}/tests/test_xtypes.py +0 -0
|
@@ -160,6 +160,8 @@ dump.rdb
|
|
|
160
160
|
*.db3
|
|
161
161
|
*.mcap
|
|
162
162
|
rosbag2_*/
|
|
163
|
+
# Test fixtures that are meant to be committed
|
|
164
|
+
!tests/fixtures/bags/**/*.db3
|
|
163
165
|
|
|
164
166
|
|
|
165
167
|
# -------------------------------------------------------------------------
|
|
@@ -187,42 +189,8 @@ personal/
|
|
|
187
189
|
CLAUDE*.md
|
|
188
190
|
|
|
189
191
|
|
|
190
|
-
#
|
|
191
|
-
|
|
192
|
-
# Allowlist exceptions: the folder README explains the convention, and
|
|
193
|
-
# `*-spec.md` files are committable specs produced by pack-growth streams
|
|
194
|
-
# (e.g. Stream C of a release plan). Everything else under projet-file/
|
|
195
|
-
# stays local: PDFs, personal market briefs, raw strategy notes.
|
|
196
|
-
# -------------------------------------------------------------------------
|
|
197
|
-
/docs/projet-file/**
|
|
198
|
-
!/docs/projet-file/README.md
|
|
199
|
-
!/docs/projet-file/*-spec.md
|
|
200
|
-
# Traction snapshots are versioned: weekly JSON + summary + folder README.
|
|
201
|
-
# This is the historical curve that informs decision gates G1/G2/G3
|
|
202
|
-
# (product-plan section 12). Numeric history must survive across machines.
|
|
203
|
-
!/docs/projet-file/traction/
|
|
204
|
-
!/docs/projet-file/traction/**
|
|
205
|
-
# Launch-post drafts (Reddit, LinkedIn, X). Versioned so the maintainer
|
|
206
|
-
# can diff drafts across releases and keep marketing history auditable.
|
|
207
|
-
# Drafts, not production copy, never the public README.
|
|
208
|
-
!/docs/projet-file/launch-posts/
|
|
209
|
-
!/docs/projet-file/launch-posts/**
|
|
210
|
-
# Audit reports (security, architecture) + audit-followup triage docs.
|
|
211
|
-
# Versioned so audit trails survive across machines and the triage
|
|
212
|
-
# decisions are bisectable per release.
|
|
213
|
-
!/docs/projet-file/*-audit-*.md
|
|
214
|
-
!/docs/projet-file/audit-*.md
|
|
215
|
-
# External reference material (OMG interop reports, third-party specs
|
|
216
|
-
# snapshots) the maintainer wants pinned in git so strategy decisions
|
|
217
|
-
# remain reproducible. Tracked, not part of sdist.
|
|
218
|
-
!/docs/projet-file/references/
|
|
219
|
-
!/docs/projet-file/references/**
|
|
220
|
-
# Archive of superseded strategic artifacts (old audit reports, dated
|
|
221
|
-
# launch-post drafts). Kept in git so the historical context survives
|
|
222
|
-
# but tucked away so the active `projet-file/` folder stays focused on
|
|
223
|
-
# what is currently load-bearing.
|
|
224
|
-
!/docs/projet-file/archive/
|
|
225
|
-
!/docs/projet-file/archive/**
|
|
192
|
+
# Owner's private strategy notes, specs and traction snapshots: local only.
|
|
193
|
+
/docs/projet-file/
|
|
226
194
|
/docs/assets/screencast-raw/
|
|
227
195
|
|
|
228
196
|
|
|
@@ -235,3 +203,6 @@ pro/
|
|
|
235
203
|
scripts/integration/**/rti_license.dat
|
|
236
204
|
scripts/integration/**/*.dat
|
|
237
205
|
.venv-demo/
|
|
206
|
+
|
|
207
|
+
# Owner's traction snapshot script (runs from a local scheduled task): local only.
|
|
208
|
+
/scripts/traction-snapshot.sh
|
|
@@ -5,234 +5,201 @@ All notable changes to TopicForge are documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog 1.1.0](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
-
## [
|
|
8
|
+
## [0.5.6] - 2026-10-02
|
|
9
|
+
|
|
10
|
+
Fixes from a live run of 0.5.3 and 0.5.5 against OmniSim's simulated
|
|
11
|
+
Clearpath Husky (ROS 2 Humble, Fast DDS, Cyclone backend for the DDS tools),
|
|
12
|
+
reported with ground truth by the OmniSim team.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- `sample_messages` takes `max_array_length` (1..65536, default 128, null for no
|
|
17
|
+
cut) and `arrays_summary_only`. A cut is listed under `_truncated_after_columns`
|
|
18
|
+
in the sample and in `note`.
|
|
19
|
+
- Size caps on returned samples: 1 MiB per message and 4 MiB per call
|
|
20
|
+
(`TOPICFORGE_MAX_SAMPLE_BYTES` sets the per-message cap). Over-cap messages are
|
|
21
|
+
dropped and `note` says so. `peek_bag_samples` is capped the same way, and its
|
|
22
|
+
arrays are cut at 4096 elements.
|
|
23
|
+
- `BagTopicStats` gains `first_timestamp_ns`, `last_timestamp_ns`,
|
|
24
|
+
`frequency_basis` (`topic_span` or `bag_duration`) and `latched`.
|
|
25
|
+
- `TopicInfo.qos_durability`; `qos_reliability` and `qos_durability` are filled
|
|
26
|
+
by `get_topic_info` from the publishers' QoS (`mixed` when they disagree).
|
|
27
|
+
- `HealthReport.dds_inactive_reason` says why `dds_backend` is `none`.
|
|
28
|
+
- A real Humble rosbag2 bag from the OmniSim team as a test fixture
|
|
29
|
+
(`tests/fixtures/bags/omnisim_humble/`).
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
|
|
33
|
+
- `list_topics` (live) uses one `ros2 topic list -v` call for publisher and
|
|
34
|
+
subscriber counts instead of one `ros2 topic info` per topic, falling back to
|
|
35
|
+
the per-topic calls if the output is not recognized. It leaves QoS null.
|
|
36
|
+
- `rosbags>=0.11.3` is required (0.10 reads a bare `.db3` as ROS 1 and returns
|
|
37
|
+
message definitions and QoS as plain strings).
|
|
38
|
+
- A latched topic gets no rate only when its messages span under 1 second (a
|
|
39
|
+
start-up burst such as `/tf_static`); a latched topic published over a longer
|
|
40
|
+
span keeps its rate. `latched` is unchanged.
|
|
41
|
+
- `analyze_bag` reads per-topic times of an `.mcap` bag only up to 200 MiB and
|
|
42
|
+
5 s; past either it keeps `bag_duration` rates and says so in the new
|
|
43
|
+
`BagAnalysis.note`.
|
|
44
|
+
- The `policies_checked` entry for History reads "History (risky only, where
|
|
45
|
+
announced)".
|
|
46
|
+
- `max_array_length` also cuts strings and bytes to that many characters plus
|
|
47
|
+
`...`; those cells are listed under `_truncated_columns`.
|
|
48
|
+
- `QosProfile.history` is now optional, and a new `history_note` explains why it
|
|
49
|
+
is missing. DDS discovery does not carry History (the builtin endpoint data has
|
|
50
|
+
no such member), so TopicForge reports it only for its own endpoints and for
|
|
51
|
+
Cyclone DDS peers that set something other than the default. For Fast DDS, RTI
|
|
52
|
+
and unknown-vendor endpoints it is `null`; for a Cyclone peer, KEEP_LAST depth 1
|
|
53
|
+
is also `null` because the binding fills missing QoS with that default. Clients
|
|
54
|
+
that assumed `history` is always a string must handle `null`.
|
|
55
|
+
- A discovered endpoint that announced no History keeps its reliability,
|
|
56
|
+
durability and the other policies; before, the whole `qos` became `null`.
|
|
57
|
+
- `detect_qos_mismatches` judges the KEEP_ALL-vs-KEEP_LAST History risk only
|
|
58
|
+
where both sides announced History; elsewhere one hint states that discovery
|
|
59
|
+
does not carry it, instead of a warning on every pair.
|
|
60
|
+
|
|
61
|
+
### Fixed
|
|
62
|
+
|
|
63
|
+
- `sample_messages` with `arrays_summary_only` shifted every CSV column after an
|
|
64
|
+
array: `<sequence type: float, length: 541>` contains a comma and was split in
|
|
65
|
+
two. It is one cell now.
|
|
66
|
+
- `sample_messages` returned no samples and no explanation when the echo timed
|
|
67
|
+
out (for example a large message with `max_array_length` null); `note` now says
|
|
68
|
+
so.
|
|
69
|
+
- `list_topics` reported 0 publishers and 0 subscribers for a topic missing from
|
|
70
|
+
`ros2 topic list -v`; it now asks `ros2 topic info` for that topic.
|
|
71
|
+
- `peek_bag_samples` converted a whole numpy array to a list before cutting it
|
|
72
|
+
at 4096 elements; it now converts only the part it keeps.
|
|
73
|
+
- Fast DDS endpoints no longer report a History taken from the binding's
|
|
74
|
+
defaults: `detect_qos_mismatches` treats it as not announced, like the other
|
|
75
|
+
vendors. This path has never run against a real Fast DDS bus.
|
|
76
|
+
- The `tests/fixtures/bags` bag is left out of the sdist.
|
|
77
|
+
- `peek_bag_samples` failed on every Humble `.db3` bag with "Bag contains no
|
|
78
|
+
type definitions". The reader now gets the type definitions of the distro the
|
|
79
|
+
bag records, or Humble, and `note` says which. `LaserScan.ranges` and other
|
|
80
|
+
numeric arrays now come back as lists, not a numpy repr string.
|
|
81
|
+
- `analyze_bag` rates were count / whole-bag duration, 0.1 to 0.7 percent off on
|
|
82
|
+
periodic topics and meaningless on latched ones (`/tf_static` showed 0.06 Hz,
|
|
83
|
+
`/rosout` 0.37 Hz). They are now `(n - 1) / (last - first)` per topic, read
|
|
84
|
+
from the bag when it is readable locally, and latched topics are flagged.
|
|
85
|
+
- `get_topic_info` returned `qos_reliability: null` on every topic although the
|
|
86
|
+
CLI prints Reliability and Durability per endpoint.
|
|
87
|
+
- `peek_dds_samples('/scan')` reported the topic as not discovered while
|
|
88
|
+
`rt/scan` and `scan` worked; it now resolves the name like the other DDS tools
|
|
89
|
+
and says which topic matched.
|
|
90
|
+
- The "DDS module is not active" error said to install the Cyclone binding even
|
|
91
|
+
when it was installed. It now states the actual cause: backend not selected,
|
|
92
|
+
binding missing, or adapter failed to start. README wording aligned.
|
|
93
|
+
- CSV `...` truncation cells from `ros2 topic echo` no longer count as data
|
|
94
|
+
columns.
|
|
95
|
+
- `ros2` output is decoded as UTF-8 on Windows instead of cp1252.
|
|
96
|
+
- `detect_qos_mismatches` no longer calls service, action, `rosout`,
|
|
97
|
+
`parameter_events` or `ros_discovery_info` topics typos of each other (for
|
|
98
|
+
example `get_parametersRequest` vs `set_parametersRequest`), and no longer
|
|
99
|
+
reports them as orphans. Only plain `rt/` topics and bare DDS names are compared.
|
|
100
|
+
- The "differs by N edits" hint is now only given between a writer-only name and
|
|
101
|
+
a reader-only name; a topic with both sides is never suggested as a typo.
|
|
9
102
|
|
|
10
103
|
## [0.5.5] - 2026-10-02
|
|
11
104
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
105
|
+
Tool outputs were reworked after testing them with LLM agents on the author's
|
|
106
|
+
16 test scenarios (live DDS buses with planted faults).
|
|
107
|
+
|
|
108
|
+
### Breaking
|
|
109
|
+
|
|
110
|
+
- `detect_qos_mismatches` returns a `MismatchScan` envelope instead of a bare
|
|
111
|
+
list. Migrate by reading `["reports"]` where you used the list.
|
|
17
112
|
|
|
18
113
|
### Added
|
|
19
114
|
|
|
20
|
-
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
- `health_check` gains `now_ns`, `observer_started_ns`, `observed_domain_note`
|
|
38
|
-
and the tracker status (`tracker_running`, `tracker_passes`, `tracker_errors`,
|
|
39
|
-
`tracker_last_pass_ns`).
|
|
40
|
-
- `QosProfile` gains optional `liveliness_kind`, `liveliness_lease_ns`,
|
|
41
|
-
`ownership_kind`, `ownership_strength`, `partitions`, `latency_budget_ns`,
|
|
42
|
-
`destination_order` and `data_representation`, read from Cyclone discovery.
|
|
43
|
-
A missing Partition policy is reported as `[""]` everywhere.
|
|
44
|
-
- `peek_dds_samples` on `DCPSPublication`, `DCPSSubscription` and
|
|
45
|
-
`DCPSParticipant` carries structured `role`, `participant_guid`,
|
|
46
|
-
`participant_name`, `type_id`, `qos`, `announced_ns` and `is_observer`, and
|
|
47
|
-
sets `timestamp_ns` from the announcement. `_raw_text` is kept only for a
|
|
48
|
-
sample with nothing structured to read, truncated to 300 characters.
|
|
49
|
-
- `peek_dds_samples` on a builtin topic returns the cached discovery state, not
|
|
50
|
-
a stream.
|
|
51
|
-
- Topic filters accept `rt/x` and `x` interchangeably and report which form
|
|
52
|
-
matched. A filter that matches nothing returns the list of known topics.
|
|
53
|
-
- Examples: the generic role nodes take partition, liveliness and ownership
|
|
54
|
-
options.
|
|
115
|
+
- `list_endpoints`, the twelfth tool: every announced DDS writer and reader with
|
|
116
|
+
owning participant, structured QoS and a per-topic roll-up that flags orphans
|
|
117
|
+
(`no_reader`, `no_writer`). Cyclone and mock only.
|
|
118
|
+
- Continuous discovery tracking on Cyclone, so a node restarted three times
|
|
119
|
+
shows as 3 `lost` and 4 `discovered` events dated by DDS, not by poll time.
|
|
120
|
+
- `announced_ns`, `lost_ns`, `time_source` and related timestamp fields on
|
|
121
|
+
`participant_events` and `list_participants`.
|
|
122
|
+
- `health_check` reports `now_ns`, tracker status and an observed-domain note.
|
|
123
|
+
- `QosProfile` gains Liveliness, Ownership, Partition, LatencyBudget,
|
|
124
|
+
DestinationOrder and DataRepresentation.
|
|
125
|
+
- `peek_dds_samples` on builtin discovery topics returns structured endpoint
|
|
126
|
+
and participant fields instead of a raw repr string.
|
|
127
|
+
- Topic filters accept `rt/x` and `x` interchangeably; a filter that matches
|
|
128
|
+
nothing returns the known topics.
|
|
129
|
+
- `detect_qos_mismatches` also reports `matched` and `not_matched` pairs, hints
|
|
130
|
+
(near-miss topic names, path suffixes, type id differences), and the policies
|
|
131
|
+
it did and did not check.
|
|
55
132
|
|
|
56
133
|
### Changed
|
|
57
134
|
|
|
58
|
-
-
|
|
59
|
-
|
|
60
|
-
envelope also carries `matched`, `not_matched`, `hints`, `pairs_checked`,
|
|
61
|
-
`topics_scanned`, `policies_checked`, `policies_unchecked` and
|
|
62
|
-
`mode_effective`. Why: an empty list was read as a healthy bus although only
|
|
63
|
-
four policies were checked, and readers and writers in different partitions
|
|
64
|
-
were blamed on Reliability.
|
|
65
|
-
- Partition is checked first (`*` and `?` wildcards; a wildcard against a
|
|
66
|
-
wildcard never matches). A pair it separates is a `not_matched` entry with
|
|
67
|
-
reason `partition`; differing type names give reason `type_name`. The RxO
|
|
68
|
-
rules are not run on either. A `not_matched` pair lists the policies that
|
|
69
|
-
would be incompatible if it matched (`latent_incompatible_policies`).
|
|
70
|
-
- Liveliness (kind and lease), LatencyBudget, Ownership (kind equality),
|
|
135
|
+
- `detect_qos_mismatches` checks Partition first (with `*` and `?` wildcards),
|
|
136
|
+
then type names, then the RxO rules; Liveliness, LatencyBudget, Ownership,
|
|
71
137
|
DestinationOrder and DataRepresentation join Reliability, Durability and
|
|
72
|
-
Deadline.
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
`unchecked`.
|
|
77
|
-
- Hints cover orphan topics whose names differ by at most 2 edits (compared
|
|
78
|
-
against all topics), path-suffix matches and XTypes type id differences (a
|
|
79
|
-
note, never a mismatch). Hints are prioritized and say how many were omitted.
|
|
80
|
-
- A late joiner is not a hint (it is normal on most buses): a `MatchedPair` is
|
|
81
|
-
flagged `late_joiner` when its writer is VOLATILE and the reader, on the same
|
|
82
|
-
host, appeared more than 1 s later.
|
|
83
|
-
- `reports`, `matched` and `not_matched` are capped at 200 entries each, with
|
|
84
|
-
`reports_total`, `matched_total`, `not_matched_total` and `truncated`.
|
|
85
|
-
- `topic_metrics` reports a `status`, and on a user topic says it has no data
|
|
86
|
-
instead of returning zeros. `peek_dds_samples` on a user topic returns count
|
|
87
|
-
0 and a note. `ParticipantInfo` and endpoints gain `is_observer`, and
|
|
88
|
-
`vendor_source` says where a vendor came from.
|
|
89
|
-
- Tool descriptions no longer carry internal history, and state the
|
|
90
|
-
single-domain and EXCLUSIVE-ownership facts.
|
|
91
|
-
- `EndpointInfo.activity` is reserved (always `None`) with an `activity_note`
|
|
92
|
-
saying liveness is not observed.
|
|
138
|
+
Deadline.
|
|
139
|
+
- `topic_metrics` and `peek_dds_samples` on a user topic say they have no data
|
|
140
|
+
instead of returning zeros or a placeholder.
|
|
141
|
+
- Tool descriptions no longer carry internal history.
|
|
93
142
|
|
|
94
143
|
### Fixed
|
|
95
144
|
|
|
96
|
-
- Infinite durations
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
-
|
|
104
|
-
threads in `take()` at once corrupted the heap on Windows. Every binding call
|
|
105
|
-
now goes through one process-wide lock, and tool handlers no longer call the
|
|
106
|
-
binding at all.
|
|
107
|
-
- If every tracker pass fails, tools no longer wait 3 s each: warm-up is bounded
|
|
108
|
-
once. `health_check` reports failed passes and cache evictions.
|
|
109
|
-
- An unreadable QoS duration is treated as unknown (`QosProfile.unknown_policies`),
|
|
110
|
-
not as infinite, so it cannot produce a false Deadline incompatibility.
|
|
111
|
-
- Topic-name typo hints are bounded (banded edit distance, orphan cap, call
|
|
112
|
-
budget), so a bus with a thousand topics does not stall the call.
|
|
113
|
-
- Partition matching was checked against a live Cyclone bus: `*` and `?` are
|
|
114
|
-
wildcards, `[...]` is literal, and two wildcard expressions never match each
|
|
115
|
-
other. Pinned by tests.
|
|
145
|
+
- Infinite durations are `None` instead of 9223372036854775807.
|
|
146
|
+
- Races between the tracker and tool calls could mark a live participant lost
|
|
147
|
+
or keep endpoints of a departed one.
|
|
148
|
+
- Concurrent cyclonedds calls could corrupt the heap on Windows; every binding
|
|
149
|
+
call now takes one lock.
|
|
150
|
+
- A failing tracker no longer adds 3 s to every tool call.
|
|
151
|
+
- An unreadable QoS duration is treated as unknown, not infinite.
|
|
152
|
+
- Typo hints stay bounded on a bus with a thousand topics.
|
|
116
153
|
|
|
117
154
|
### Known limits
|
|
118
155
|
|
|
119
|
-
- A hung writer (alive,
|
|
120
|
-
|
|
121
|
-
- A
|
|
122
|
-
|
|
123
|
-
-
|
|
124
|
-
tracker passes can be missed.
|
|
125
|
-
- The Fast DDS backend has never run on a bus, and `list_endpoints` is not
|
|
156
|
+
- A hung writer (alive, no data) cannot be observed without a data probe.
|
|
157
|
+
- A crash and a clean leave look the same; `lost_ns` is an upper bound.
|
|
158
|
+
- A participant that cycles faster than the history depth between two tracker
|
|
159
|
+
passes can be missed.
|
|
160
|
+
- The Fast DDS backend has never run on a bus; `list_endpoints` is not
|
|
126
161
|
supported there.
|
|
127
|
-
- DDS Security is not supported.
|
|
128
|
-
- User-topic payload decoding is disabled, so `topic_metrics` has no data on
|
|
129
|
-
user topics.
|
|
162
|
+
- DDS Security is not supported, and user-topic payload decoding is disabled.
|
|
130
163
|
|
|
131
164
|
## [0.5.4] - 2026-10-02
|
|
132
165
|
|
|
133
|
-
First run of the DDS code against a live
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
checked statically. The run exposed four defects that together made the DDS
|
|
137
|
-
module non-functional on Cyclone; all are fixed and pinned by tests.
|
|
166
|
+
First run of the DDS code against a live bus (Windows 11, a Python / Cyclone DDS
|
|
167
|
+
participant and a Rust / Dust DDS participant). It exposed four defects that
|
|
168
|
+
left the DDS module non-functional on Cyclone; all are fixed and tested.
|
|
138
169
|
|
|
139
170
|
### Fixed
|
|
140
171
|
|
|
141
|
-
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
statically only, since its tests need a binding that is not on PyPI. The `vendor` field of
|
|
161
|
-
`ParticipantInfo` and `ParticipantEvent` now also accepts `rti_micro`,
|
|
162
|
-
`opensplice`, `opendds`, `coredx`, `intercom` and `dust` (soft-breaking for
|
|
163
|
-
clients validating the previous enum).
|
|
164
|
-
- **`detect_qos_mismatches` never reported anything on Cyclone.** cyclonedds
|
|
165
|
-
scopes its policy class names (`Reliability.BestEffort`), and the
|
|
166
|
-
normalizer matched only the bare name, so no QoS profile was ever built.
|
|
167
|
-
- **A stopped participant never disappeared.** Discovery readers keep the last
|
|
168
|
-
sample of a departed participant with a NOT_ALIVE instance state; those are
|
|
169
|
-
now ignored for participants and endpoints, so a participant is reported as
|
|
170
|
-
left when its lease expires, and a dead endpoint no longer produces a
|
|
171
|
-
mismatch.
|
|
172
|
-
|
|
173
|
-
- Cyclone adapter created a new DDS reader on a builtin discovery topic on
|
|
174
|
-
every tool call and never deleted it. It now keeps one reader per builtin
|
|
175
|
-
topic and takes a non-blocking snapshot, so calls no longer wait 2 s each
|
|
176
|
-
(`detect_qos_mismatches` waited 4 s).
|
|
177
|
-
- `peek_dds_samples` on `DCPSPublication` / `DCPSSubscription` reported the
|
|
178
|
-
endpoints of participants that had left; disposed entries are now dropped.
|
|
179
|
-
Its payload also carries the endpoint `type_name`.
|
|
180
|
-
- `test_dds_cross_vendor.py` expected an error message from v0.3; it had
|
|
181
|
-
never run, since CI has no DDS binding. First run against the real Cyclone
|
|
182
|
-
binding.
|
|
172
|
+
- `list_participants` reports the participant name and hostname on Cyclone
|
|
173
|
+
(both were always null).
|
|
174
|
+
- Participant GUIDs were never read, so every participant collapsed onto one
|
|
175
|
+
`unknown` entry.
|
|
176
|
+
- Vendors were never identified. The vendor id is now read from the GUID prefix;
|
|
177
|
+
Dust DDS and RTI by default still report `unknown`.
|
|
178
|
+
- The OMG vendor-id table mapped Fast DDS and Cyclone to the wrong ids (now
|
|
179
|
+
`01.0F` and `01.10`). The Fast DDS side is verified statically only.
|
|
180
|
+
- `ParticipantInfo.vendor` also accepts `rti_micro`, `opensplice`, `opendds`,
|
|
181
|
+
`coredx`, `intercom` and `dust`.
|
|
182
|
+
- `detect_qos_mismatches` never reported anything on Cyclone because policy
|
|
183
|
+
class names were not normalized.
|
|
184
|
+
- A stopped participant never disappeared; departed participants and endpoints
|
|
185
|
+
are now dropped.
|
|
186
|
+
- The Cyclone adapter leaked a reader per tool call and waited 2 s each; it now
|
|
187
|
+
keeps one reader per builtin topic.
|
|
188
|
+
- `peek_dds_samples` on `DCPSPublication` / `DCPSSubscription` listed endpoints
|
|
189
|
+
of participants that had left, and now includes the endpoint `type_name`.
|
|
190
|
+
- Example role nodes published below their rate on Windows.
|
|
183
191
|
|
|
184
192
|
### Added
|
|
185
193
|
|
|
186
|
-
- `examples/dds/`:
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
and TopicForge explains the same situation from outside. All fourteen
|
|
192
|
-
examples pass on a live bus.
|
|
193
|
-
- The generic role nodes print one line per second per reader with what
|
|
194
|
-
they received, and examples 02, 04, 10 and 13 check that the broken
|
|
195
|
-
subscriber receives nothing while the control one receives data.
|
|
196
|
-
- The harness keeps each program's output in a log file and prints it live
|
|
197
|
-
under `--hold`.
|
|
198
|
-
|
|
199
|
-
- `scripts/integration/interop_check.py`: one-command multi-vendor demo
|
|
200
|
-
that starts a Rust / Dust and a Python / Cyclone participant, drives
|
|
201
|
-
TopicForge over stdio through the official MCP client, checks participant
|
|
202
|
-
discovery, a deliberate Reliability mismatch between the two vendors, and
|
|
203
|
-
the departure of a stopped participant, then stops every process it started.
|
|
204
|
-
- A real Rust / Dust DDS participant (`publishers/dust_publisher`) and a real
|
|
205
|
-
Python / Cyclone participant replacing the previous scaffold, which never
|
|
206
|
-
wrote a sample.
|
|
207
|
-
- Twelve interop programs in total, one per vendor and language with an
|
|
208
|
-
officially released binding (Cyclone C / C++ / Rust / Python, Dust Rust /
|
|
209
|
-
Python, Fast DDS C++ / Python, RTI Connext C / C++ / Python, OpenSplice C),
|
|
210
|
-
all following the contract in `scripts/integration/DEMO_CONTRACT.md`. The
|
|
211
|
-
driver starts whichever ones are built on the host and adapts its checks;
|
|
212
|
-
`--list` shows what can run. Only Cyclone Python and Dust Rust / Python / C
|
|
213
|
-
have been run, on Windows; the rest are written but unrun. RTI participants
|
|
214
|
-
need a local license and are never run in CI.
|
|
215
|
-
- One-command launch scripts, `scripts/integration/launch/setup` and
|
|
216
|
-
`run_demo` (`.ps1` and `.sh`), which create `.venv-demo`, install the Cyclone
|
|
217
|
-
binding and build the Rust participant. `setup.ps1 -Firewall` adds inbound
|
|
218
|
-
UDP 7400-7500 rules on private networks for multi-machine runs.
|
|
219
|
-
- Unicast peer configuration for Cyclone and Fast DDS and a guide for a mixed
|
|
220
|
-
Linux and Windows bus (`scripts/integration/config/`).
|
|
221
|
-
- `.github/workflows/demo.yml` runs the driver with the Cyclone and Dust
|
|
222
|
-
participants on Ubuntu and Windows; `demo-fast.yml` builds Fast DDS 3 from
|
|
223
|
-
pinned tags and starts the C++ participant (weekly and manual).
|
|
224
|
-
- `scripts/integration/README.md` rewritten around the demo: it previously
|
|
225
|
-
described the removed docker / scenario rig.
|
|
226
|
-
|
|
227
|
-
- `examples/dds/`: real use cases 10 to 14 (driver swap, wiring, safety
|
|
228
|
-
monitor dropout, deadline not offered, restart loop) next to the concept
|
|
229
|
-
examples 01 to 04. All nine pass on a live bus.
|
|
194
|
+
- `examples/dds/`: fourteen runnable examples (concepts and use cases), all
|
|
195
|
+
passing on a live bus.
|
|
196
|
+
- `scripts/integration/`: a demo driver, twelve interop programs (Cyclone, Dust,
|
|
197
|
+
Fast DDS, RTI, OpenSplice), one-command setup scripts and CI workflows. Only
|
|
198
|
+
Cyclone Python and Dust Rust / Python / C have been run, on Windows.
|
|
230
199
|
|
|
231
200
|
### Removed
|
|
232
201
|
|
|
233
|
-
- The docker / scenario integration rig
|
|
234
|
-
`docker-compose.yml`, per-vendor Dockerfiles, scenario JSON files, schema
|
|
235
|
-
test and `integration.yml`) is removed in favour of the demo driver.
|
|
202
|
+
- The docker / scenario integration rig, replaced by the demo driver.
|
|
236
203
|
|
|
237
204
|
## [0.5.3] - 2026-10-01
|
|
238
205
|
|
|
@@ -1235,7 +1202,8 @@ Initial MVP release of TopicForge: ROS Topic Inspector & Bag Analyzer MCP server
|
|
|
1235
1202
|
- The write path (publishing, commanding robots) is intentionally out of scope for the MVP.
|
|
1236
1203
|
- `analyze_bag` in live mode parses `ros2 bag info` text output; deeper anomaly detection remains mock-only for now.
|
|
1237
1204
|
|
|
1238
|
-
[Unreleased]: https://github.com/yaniswav/TopicForge/compare/v0.5.
|
|
1205
|
+
[Unreleased]: https://github.com/yaniswav/TopicForge/compare/v0.5.6...HEAD
|
|
1206
|
+
[0.5.6]: https://github.com/yaniswav/TopicForge/compare/v0.5.5...v0.5.6
|
|
1239
1207
|
[0.5.5]: https://github.com/yaniswav/TopicForge/compare/v0.5.4...v0.5.5
|
|
1240
1208
|
[0.5.4]: https://github.com/yaniswav/TopicForge/compare/v0.5.3...v0.5.4
|
|
1241
1209
|
[0.5.3]: https://github.com/yaniswav/TopicForge/compare/v0.5.2...v0.5.3
|