roschema 0.2.1__tar.gz → 0.4.0__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.
Files changed (128) hide show
  1. {roschema-0.2.1 → roschema-0.4.0}/.github/workflows/ci.yml +3 -3
  2. {roschema-0.2.1 → roschema-0.4.0}/.github/workflows/release.yml +1 -1
  3. {roschema-0.2.1 → roschema-0.4.0}/CHANGELOG.md +43 -0
  4. {roschema-0.2.1 → roschema-0.4.0}/PKG-INFO +69 -21
  5. {roschema-0.2.1 → roschema-0.4.0}/README.md +67 -20
  6. {roschema-0.2.1 → roschema-0.4.0}/action/action.yml +25 -2
  7. {roschema-0.2.1 → roschema-0.4.0}/pyproject.toml +2 -1
  8. {roschema-0.2.1 → roschema-0.4.0}/src/roschema/cli.py +191 -32
  9. {roschema-0.2.1 → roschema-0.4.0}/src/roschema/config.py +24 -11
  10. {roschema-0.2.1 → roschema-0.4.0}/src/roschema/report/__init__.py +5 -1
  11. roschema-0.4.0/src/roschema/report/changes.py +92 -0
  12. {roschema-0.2.1 → roschema-0.4.0}/src/roschema/report/github.py +14 -3
  13. roschema-0.4.0/src/roschema/report/json_report.py +87 -0
  14. roschema-0.4.0/src/roschema/report/markdown.py +92 -0
  15. roschema-0.4.0/src/roschema/report/summary.py +122 -0
  16. {roschema-0.2.1 → roschema-0.4.0}/src/roschema/report/text.py +49 -13
  17. {roschema-0.2.1 → roschema-0.4.0}/src/roschema/rules/gendocs.py +28 -0
  18. {roschema-0.2.1 → roschema-0.4.0}/src/roschema/rules/model.py +3 -1
  19. {roschema-0.2.1 → roschema-0.4.0}/tests/fixtures/golden_check.json +6 -0
  20. {roschema-0.2.1 → roschema-0.4.0}/tests/test_action.py +37 -8
  21. {roschema-0.2.1 → roschema-0.4.0}/tests/test_check_cli.py +153 -0
  22. roschema-0.4.0/tests/test_cli_explain.py +23 -0
  23. {roschema-0.2.1 → roschema-0.4.0}/tests/test_config.py +23 -1
  24. {roschema-0.2.1 → roschema-0.4.0}/tests/test_gendocs.py +14 -0
  25. {roschema-0.2.1 → roschema-0.4.0}/tests/test_golden.py +14 -7
  26. {roschema-0.2.1 → roschema-0.4.0}/tests/test_lint.py +8 -0
  27. {roschema-0.2.1 → roschema-0.4.0}/tests/test_mcap.py +42 -0
  28. {roschema-0.2.1 → roschema-0.4.0}/tests/test_report_formats.py +29 -1
  29. roschema-0.4.0/tests/test_report_markdown.py +61 -0
  30. roschema-0.2.1/src/roschema/report/json_report.py +0 -52
  31. roschema-0.2.1/src/roschema/report/summary.py +0 -60
  32. {roschema-0.2.1 → roschema-0.4.0}/.gitignore +0 -0
  33. {roschema-0.2.1 → roschema-0.4.0}/CONTRIBUTING.md +0 -0
  34. {roschema-0.2.1 → roschema-0.4.0}/LICENSE +0 -0
  35. {roschema-0.2.1 → roschema-0.4.0}/docs/rules.md +0 -0
  36. {roschema-0.2.1 → roschema-0.4.0}/src/roschema/__init__.py +0 -0
  37. {roschema-0.2.1 → roschema-0.4.0}/src/roschema/deep.py +0 -0
  38. {roschema-0.2.1 → roschema-0.4.0}/src/roschema/differ.py +0 -0
  39. {roschema-0.2.1 → roschema-0.4.0}/src/roschema/discovery/__init__.py +0 -0
  40. {roschema-0.2.1 → roschema-0.4.0}/src/roschema/discovery/discover.py +0 -0
  41. {roschema-0.2.1 → roschema-0.4.0}/src/roschema/discovery/model.py +0 -0
  42. {roschema-0.2.1 → roschema-0.4.0}/src/roschema/discovery/resolve.py +0 -0
  43. {roschema-0.2.1 → roschema-0.4.0}/src/roschema/formatter.py +0 -0
  44. {roschema-0.2.1 → roschema-0.4.0}/src/roschema/freeze.py +0 -0
  45. {roschema-0.2.1 → roschema-0.4.0}/src/roschema/ir.py +0 -0
  46. {roschema-0.2.1 → roschema-0.4.0}/src/roschema/loader.py +0 -0
  47. {roschema-0.2.1 → roschema-0.4.0}/src/roschema/mcap_fs.py +0 -0
  48. {roschema-0.2.1 → roschema-0.4.0}/src/roschema/parser/__init__.py +0 -0
  49. {roschema-0.2.1 → roschema-0.4.0}/src/roschema/parser/core.py +0 -0
  50. {roschema-0.2.1 → roschema-0.4.0}/src/roschema/parser/errors.py +0 -0
  51. {roschema-0.2.1 → roschema-0.4.0}/src/roschema/py.typed +0 -0
  52. {roschema-0.2.1 → roschema-0.4.0}/src/roschema/rules/__init__.py +0 -0
  53. {roschema-0.2.1 → roschema-0.4.0}/src/roschema/rules/breaking/__init__.py +0 -0
  54. {roschema-0.2.1 → roschema-0.4.0}/src/roschema/rules/breaking/rs001_field_removed.py +0 -0
  55. {roschema-0.2.1 → roschema-0.4.0}/src/roschema/rules/breaking/rs002_field_added.py +0 -0
  56. {roschema-0.2.1 → roschema-0.4.0}/src/roschema/rules/breaking/rs003_field_type_changed.py +0 -0
  57. {roschema-0.2.1 → roschema-0.4.0}/src/roschema/rules/breaking/rs004_fields_reordered.py +0 -0
  58. {roschema-0.2.1 → roschema-0.4.0}/src/roschema/rules/breaking/rs005_field_renamed.py +0 -0
  59. {roschema-0.2.1 → roschema-0.4.0}/src/roschema/rules/breaking/rs006_array_kind_changed.py +0 -0
  60. {roschema-0.2.1 → roschema-0.4.0}/src/roschema/rules/breaking/rs007_bound_tightened.py +0 -0
  61. {roschema-0.2.1 → roschema-0.4.0}/src/roschema/rules/breaking/rs008_bound_loosened.py +0 -0
  62. {roschema-0.2.1 → roschema-0.4.0}/src/roschema/rules/breaking/rs020_constant_removed.py +0 -0
  63. {roschema-0.2.1 → roschema-0.4.0}/src/roschema/rules/breaking/rs021_constant_value_changed.py +0 -0
  64. {roschema-0.2.1 → roschema-0.4.0}/src/roschema/rules/breaking/rs022_constant_type_changed.py +0 -0
  65. {roschema-0.2.1 → roschema-0.4.0}/src/roschema/rules/breaking/rs030_default_changed.py +0 -0
  66. {roschema-0.2.1 → roschema-0.4.0}/src/roschema/rules/breaking/rs040_interface_removed.py +0 -0
  67. {roschema-0.2.1 → roschema-0.4.0}/src/roschema/rules/breaking/rs060_message_version.py +0 -0
  68. {roschema-0.2.1 → roschema-0.4.0}/src/roschema/rules/breaking/rs110_transitive.py +0 -0
  69. {roschema-0.2.1 → roschema-0.4.0}/src/roschema/rules/lint/__init__.py +0 -0
  70. {roschema-0.2.1 → roschema-0.4.0}/src/roschema/rules/lint/_common.py +0 -0
  71. {roschema-0.2.1 → roschema-0.4.0}/src/roschema/rules/lint/rsl001_field_snake_case.py +0 -0
  72. {roschema-0.2.1 → roschema-0.4.0}/src/roschema/rules/lint/rsl002_constant_upper_snake.py +0 -0
  73. {roschema-0.2.1 → roschema-0.4.0}/src/roschema/rules/lint/rsl003_name_pascal_case.py +0 -0
  74. {roschema-0.2.1 → roschema-0.4.0}/src/roschema/rules/lint/rsl004_missing_doc.py +0 -0
  75. {roschema-0.2.1 → roschema-0.4.0}/src/roschema/rules/lint/rsl005_discouraged_types.py +0 -0
  76. {roschema-0.2.1 → roschema-0.4.0}/src/roschema/rules/lint/rsl006_unbounded.py +0 -0
  77. {roschema-0.2.1 → roschema-0.4.0}/src/roschema/rules/lint/rsl010_srv_shadow.py +0 -0
  78. {roschema-0.2.1 → roschema-0.4.0}/src/roschema/rules/registry.py +0 -0
  79. {roschema-0.2.1 → roschema-0.4.0}/src/roschema/vfs.py +0 -0
  80. {roschema-0.2.1 → roschema-0.4.0}/tests/_helpers.py +0 -0
  81. {roschema-0.2.1 → roschema-0.4.0}/tests/conftest.py +0 -0
  82. {roschema-0.2.1 → roschema-0.4.0}/tests/fixtures/AMENT_IGNORE +0 -0
  83. {roschema-0.2.1 → roschema-0.4.0}/tests/fixtures/COLCON_IGNORE +0 -0
  84. {roschema-0.2.1 → roschema-0.4.0}/tests/fixtures/ws_new/demo_msgs/action/Move.action +0 -0
  85. {roschema-0.2.1 → roschema-0.4.0}/tests/fixtures/ws_new/demo_msgs/msg/ArrayKind.msg +0 -0
  86. {roschema-0.2.1 → roschema-0.4.0}/tests/fixtures/ws_new/demo_msgs/msg/BoundLoose.msg +0 -0
  87. {roschema-0.2.1 → roschema-0.4.0}/tests/fixtures/ws_new/demo_msgs/msg/BoundTight.msg +0 -0
  88. {roschema-0.2.1 → roschema-0.4.0}/tests/fixtures/ws_new/demo_msgs/msg/BoundedNest.msg +0 -0
  89. {roschema-0.2.1 → roschema-0.4.0}/tests/fixtures/ws_new/demo_msgs/msg/Constants.msg +0 -0
  90. {roschema-0.2.1 → roschema-0.4.0}/tests/fixtures/ws_new/demo_msgs/msg/Empty.msg +0 -0
  91. {roschema-0.2.1 → roschema-0.4.0}/tests/fixtures/ws_new/demo_msgs/msg/FieldAdded.msg +0 -0
  92. {roschema-0.2.1 → roschema-0.4.0}/tests/fixtures/ws_new/demo_msgs/msg/FieldRemoved.msg +0 -0
  93. {roschema-0.2.1 → roschema-0.4.0}/tests/fixtures/ws_new/demo_msgs/msg/Renamed.msg +0 -0
  94. {roschema-0.2.1 → roschema-0.4.0}/tests/fixtures/ws_new/demo_msgs/msg/Reordered.msg +0 -0
  95. {roschema-0.2.1 → roschema-0.4.0}/tests/fixtures/ws_new/demo_msgs/msg/Suppressed.msg +0 -0
  96. {roschema-0.2.1 → roschema-0.4.0}/tests/fixtures/ws_new/demo_msgs/msg/TypeChanged.msg +0 -0
  97. {roschema-0.2.1 → roschema-0.4.0}/tests/fixtures/ws_new/demo_msgs/msg/Unchanged.msg +0 -0
  98. {roschema-0.2.1 → roschema-0.4.0}/tests/fixtures/ws_new/demo_msgs/msg/WeirdSpacing.msg +0 -0
  99. {roschema-0.2.1 → roschema-0.4.0}/tests/fixtures/ws_new/demo_msgs/package.xml +0 -0
  100. {roschema-0.2.1 → roschema-0.4.0}/tests/fixtures/ws_new/demo_msgs/srv/DoThing.srv +0 -0
  101. {roschema-0.2.1 → roschema-0.4.0}/tests/fixtures/ws_old/demo_msgs/action/Move.action +0 -0
  102. {roschema-0.2.1 → roschema-0.4.0}/tests/fixtures/ws_old/demo_msgs/msg/ArrayKind.msg +0 -0
  103. {roschema-0.2.1 → roschema-0.4.0}/tests/fixtures/ws_old/demo_msgs/msg/BoundLoose.msg +0 -0
  104. {roschema-0.2.1 → roschema-0.4.0}/tests/fixtures/ws_old/demo_msgs/msg/BoundTight.msg +0 -0
  105. {roschema-0.2.1 → roschema-0.4.0}/tests/fixtures/ws_old/demo_msgs/msg/BoundedNest.msg +0 -0
  106. {roschema-0.2.1 → roschema-0.4.0}/tests/fixtures/ws_old/demo_msgs/msg/Constants.msg +0 -0
  107. {roschema-0.2.1 → roschema-0.4.0}/tests/fixtures/ws_old/demo_msgs/msg/Empty.msg +0 -0
  108. {roschema-0.2.1 → roschema-0.4.0}/tests/fixtures/ws_old/demo_msgs/msg/FieldAdded.msg +0 -0
  109. {roschema-0.2.1 → roschema-0.4.0}/tests/fixtures/ws_old/demo_msgs/msg/FieldRemoved.msg +0 -0
  110. {roschema-0.2.1 → roschema-0.4.0}/tests/fixtures/ws_old/demo_msgs/msg/Removed.msg +0 -0
  111. {roschema-0.2.1 → roschema-0.4.0}/tests/fixtures/ws_old/demo_msgs/msg/Renamed.msg +0 -0
  112. {roschema-0.2.1 → roschema-0.4.0}/tests/fixtures/ws_old/demo_msgs/msg/Reordered.msg +0 -0
  113. {roschema-0.2.1 → roschema-0.4.0}/tests/fixtures/ws_old/demo_msgs/msg/Suppressed.msg +0 -0
  114. {roschema-0.2.1 → roschema-0.4.0}/tests/fixtures/ws_old/demo_msgs/msg/TypeChanged.msg +0 -0
  115. {roschema-0.2.1 → roschema-0.4.0}/tests/fixtures/ws_old/demo_msgs/msg/Unchanged.msg +0 -0
  116. {roschema-0.2.1 → roschema-0.4.0}/tests/fixtures/ws_old/demo_msgs/msg/WeirdSpacing.msg +0 -0
  117. {roschema-0.2.1 → roschema-0.4.0}/tests/fixtures/ws_old/demo_msgs/package.xml +0 -0
  118. {roschema-0.2.1 → roschema-0.4.0}/tests/fixtures/ws_old/demo_msgs/srv/DoThing.srv +0 -0
  119. {roschema-0.2.1 → roschema-0.4.0}/tests/test_cli_ls.py +0 -0
  120. {roschema-0.2.1 → roschema-0.4.0}/tests/test_cli_version.py +0 -0
  121. {roschema-0.2.1 → roschema-0.4.0}/tests/test_deep.py +0 -0
  122. {roschema-0.2.1 → roschema-0.4.0}/tests/test_differ.py +0 -0
  123. {roschema-0.2.1 → roschema-0.4.0}/tests/test_discovery.py +0 -0
  124. {roschema-0.2.1 → roschema-0.4.0}/tests/test_fmt.py +0 -0
  125. {roschema-0.2.1 → roschema-0.4.0}/tests/test_freeze.py +0 -0
  126. {roschema-0.2.1 → roschema-0.4.0}/tests/test_parser.py +0 -0
  127. {roschema-0.2.1 → roschema-0.4.0}/tests/test_rules.py +0 -0
  128. {roschema-0.2.1 → roschema-0.4.0}/tests/test_vfs.py +0 -0
@@ -15,7 +15,7 @@ jobs:
15
15
  runs-on: ubuntu-latest
16
16
  steps:
17
17
  - uses: actions/checkout@v4
18
- - uses: astral-sh/setup-uv@v5
18
+ - uses: astral-sh/setup-uv@v10.3.3
19
19
  with:
20
20
  python-version: "3.12"
21
21
  enable-cache: true
@@ -36,10 +36,10 @@ jobs:
36
36
  strategy:
37
37
  fail-fast: false
38
38
  matrix:
39
- python-version: ["3.10", "3.11", "3.12", "3.13"]
39
+ python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
40
40
  steps:
41
41
  - uses: actions/checkout@v4
42
- - uses: astral-sh/setup-uv@v5
42
+ - uses: astral-sh/setup-uv@v10.3.3
43
43
  with:
44
44
  python-version: ${{ matrix.python-version }}
45
45
  enable-cache: true
@@ -10,7 +10,7 @@ jobs:
10
10
  runs-on: ubuntu-latest
11
11
  steps:
12
12
  - uses: actions/checkout@v4
13
- - uses: astral-sh/setup-uv@v5
13
+ - uses: astral-sh/setup-uv@v10.3.3
14
14
  with:
15
15
  python-version: "3.12"
16
16
  enable-cache: true
@@ -3,6 +3,49 @@
3
3
  The format follows [Keep a Changelog](https://keepachangelog.com/), and the
4
4
  project adheres to [Semantic Versioning](https://semver.org/).
5
5
 
6
+ ## [0.4.0] - 2026-11-03
7
+
8
+ ### Added
9
+
10
+ - JSON findings link to their rule's documentation (`help_uri`).
11
+ - `roschema explain RULE` prints a rule's reference section.
12
+ - `check --verbose` and `bag-check --verbose` list the changes that are not
13
+ reported (added, outside the level, rule off or suppressed) with the reason;
14
+ JSON adds a `changes` array.
15
+ - `--summary FILE` appends a Markdown report; the Action writes it to the job
16
+ summary, so every finding and the semver advice are visible even when GitHub
17
+ truncates the annotations.
18
+
19
+ ### Changed
20
+
21
+ - The semver advice suggests MINOR for a package that adds an interface or a
22
+ constant, and is printed even when there are no findings.
23
+ - The coverage line names the first few new interfaces and says "new" and
24
+ "removed" instead of "only in the target" and "only in the baseline".
25
+
26
+ ## [0.3.0] - 2026-11-03
27
+
28
+ ### Added
29
+
30
+ - Reports say how many interfaces were compared, and on which side the rest
31
+ exist only (JSON: `coverage`).
32
+ - `--warnings-as-errors` for `check`, `bag-check` and `lint`, and the Action's
33
+ `warnings-as-errors` input.
34
+
35
+ ### Changed
36
+
37
+ - Python 3.14 is supported and tested.
38
+ - An empty baseline is an error (exit `2`) instead of a silent pass; pass
39
+ `--allow-empty` (Action: `allow-empty`) when that is expected.
40
+
41
+ ### Fixed
42
+
43
+ - `--format github` attaches findings with no new-side location, such as a
44
+ removed field or a reorder, to the interface's file instead of leaving GitHub
45
+ to show them against the workflow file.
46
+ - The Action uses `astral-sh/setup-uv@v10.3.3` (Node 24) with its cache off,
47
+ removing the Node 20 deprecation and missing `uv.lock` warnings.
48
+
6
49
  ## [0.2.1] - 2026-11-03
7
50
 
8
51
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: roschema
3
- Version: 0.2.1
3
+ Version: 0.4.0
4
4
  Summary: Breaking-change detection, linting and formatting for ROS 2 interface definitions
5
5
  Project-URL: Homepage, https://github.com/justagist/roschema
6
6
  Project-URL: Documentation, https://github.com/justagist/roschema#readme
@@ -17,6 +17,7 @@ Classifier: Programming Language :: Python :: 3.10
17
17
  Classifier: Programming Language :: Python :: 3.11
18
18
  Classifier: Programming Language :: Python :: 3.12
19
19
  Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Programming Language :: Python :: 3.14
20
21
  Classifier: Topic :: Software Development :: Quality Assurance
21
22
  Classifier: Typing :: Typed
22
23
  Requires-Python: >=3.10
@@ -147,13 +148,17 @@ roschema bag-check recording.mcap
147
148
 
148
149
  # what would be checked? (discovered packages and interfaces)
149
150
  roschema ls
151
+
152
+ # what does a rule report, and why?
153
+ roschema explain RS004
150
154
  ```
151
155
 
152
156
  ## Checking
153
157
 
154
158
  ```
155
159
  roschema check [TARGET] --against BASELINE [--level LEVEL] [--format text|json|github]
156
- [--deep | --no-deep] [--exit-zero] [--config PATH] [--exclude GLOB]
160
+ [--deep | --no-deep] [--exit-zero] [--warnings-as-errors] [--allow-empty]
161
+ [--verbose] [--summary FILE] [--config PATH] [--exclude GLOB]
157
162
  ```
158
163
 
159
164
  `TARGET` selects what to check; omit it to check every interface package below
@@ -176,6 +181,16 @@ MCAP bag (`.mcap`) or a lockfile (`roschema.lock`); the type is detected
176
181
  automatically. Initialised git submodules are compared at the commit the
177
182
  baseline records for them.
178
183
 
184
+ Every report says how much was compared, for example `compared 12 interface(s)
185
+ in 3 package(s); 1 new (my_msgs/msg/Status)`. New interfaces are not findings,
186
+ since adding one breaks nothing; the line names the first few so that an
187
+ unexpected one (say, from an unrelated checkout below the target) is easy to
188
+ spot. A baseline with no interfaces at all is
189
+ an error (exit `2`): every current interface would look newly added, so the
190
+ check would pass without comparing anything. That usually means the wrong
191
+ baseline or target; on the pull request that adds the first interfaces, pass
192
+ `--allow-empty`.
193
+
179
194
  Limitations:
180
195
 
181
196
  - When a git ref and a directory share a name, the git ref wins; prefix the
@@ -199,7 +214,7 @@ followed.
199
214
 
200
215
  ```
201
216
  roschema lint [TARGET] [--format text|json|github] [--rules RULE=SEV] [--exit-zero]
202
- [--config PATH] [--exclude GLOB]
217
+ [--warnings-as-errors] [--config PATH] [--exclude GLOB]
203
218
  roschema fmt [TARGET] [--check] [--exclude GLOB]
204
219
  ```
205
220
 
@@ -238,7 +253,8 @@ same. Comparison ignores it.
238
253
 
239
254
  ```
240
255
  roschema bag-check BAG.mcap [TARGETS...] [--level LEVEL] [--config PATH]
241
- [--format text|json|github] [--exit-zero] [--exclude GLOB]
256
+ [--format text|json|github] [--exit-zero] [--warnings-as-errors]
257
+ [--verbose] [--summary FILE] [--exclude GLOB]
242
258
  ```
243
259
 
244
260
  Needs the `bag` extra (`uv tool install 'roschema[bag]'`).
@@ -327,19 +343,30 @@ allow every rule for that target.
327
343
 
328
344
  ## Output
329
345
 
330
- **Text** (the default) groups findings by package and ends with a summary and a
331
- semver advice per package:
346
+ **Text** (the default) starts with what was compared, groups findings by
347
+ package and ends with a summary and a semver advice per package:
332
348
 
333
349
  - **MAJOR** when the package has a wire- or source-level error;
334
- - **MINOR** when its only notable change is an added field (RS002) whose
335
- severity you lowered to `warn`;
350
+ - **MINOR** when it adds an interface or a constant (neither is a finding,
351
+ since neither breaks anything), or an added field (RS002) whose severity you
352
+ lowered to `warn`;
336
353
  - **PATCH** otherwise.
337
354
 
338
- The advice only covers reported findings. Additions such as a new interface or
339
- constant are not findings, so they never raise the advice to MINOR on their own.
355
+ **`--verbose`** also lists, under "not reported", every change that produced no
356
+ reported finding, and why: a new interface or constant (no rule reports either,
357
+ at any level; an added field is RS002), a rule whose level is outside the one
358
+ being checked, a rule that is off, or a suppression, with the allowlist reason.
359
+ Comment-only edits are not changes, so they never appear.
360
+
361
+ **`--format github`** prints what was compared, then emits `::error`/`::warning`
362
+ workflow commands, which GitHub shows as annotations on the PR diff.
340
363
 
341
- **`--format github`** emits `::error`/`::warning` workflow commands, which GitHub
342
- shows as annotations on the PR diff.
364
+ **`--summary FILE`** appends a Markdown report to `FILE` as well, whatever the
365
+ `--format`: what was compared, a table of findings linked to the rule
366
+ reference, the semver advice, and the unreported changes in a collapsed
367
+ section. Pointed at `$GITHUB_STEP_SUMMARY`, it becomes the job summary, which
368
+ matters because GitHub shows only the first 10 error and 10 warning annotations
369
+ of a step.
343
370
 
344
371
  **`--format json`** emits a stable document (`version: 1`):
345
372
 
@@ -358,7 +385,8 @@ shows as annotations on the PR diff.
358
385
  "message": "type changed float32 -> float64",
359
386
  "old": { "file": "my_msgs/msg/Pose.msg", "line": 1 },
360
387
  "new": { "file": "my_msgs/msg/Pose.msg", "line": 1 },
361
- "suppressed": false
388
+ "suppressed": false,
389
+ "help_uri": "https://github.com/justagist/roschema/blob/v0.4.0/docs/rules.md#rs003----field-type-changed"
362
390
  }
363
391
  ],
364
392
  "summary": {
@@ -367,12 +395,24 @@ shows as annotations on the PR diff.
367
395
  "warnings": 0,
368
396
  "suppressed": 0,
369
397
  "advisories": { "my_msgs": "MAJOR" }
398
+ },
399
+ "coverage": {
400
+ "packages": 1,
401
+ "compared": 1,
402
+ "target_only": 0,
403
+ "baseline_only": 0
370
404
  }
371
405
  }
372
406
  ```
373
407
 
374
408
  `old`/`new` are `null` when there is no corresponding side (a removed interface
375
- has no `new`). `lint` output has no `advisories`.
409
+ has no `new`). `coverage` counts interfaces present on both sides (`compared`)
410
+ and on one side only. `help_uri` links to the rule's section of the
411
+ [rule reference](docs/rules.md) for the installed release. With `--verbose`, a
412
+ `changes` array lists the unreported changes (each with `rule`, `package`,
413
+ `interface`, `section`, `target`, `message` and `reason`), including suppressed
414
+ findings with the reason they are suppressed. `lint` output has no
415
+ `advisories`, `coverage` or `changes`.
376
416
 
377
417
  ## Exit codes
378
418
 
@@ -380,15 +420,17 @@ has no `new`). `lint` output has no `advisories`.
380
420
  |---|---|---|---|
381
421
  | `0` | no unsuppressed errors (warnings allowed) | every file formatted | success |
382
422
  | `1` | at least one unsuppressed error | a file would change | n/a |
383
- | `2` | tool or usage error (bad target, baseline, configuration or file) | tool or usage error | tool or usage error |
423
+ | `2` | tool or usage error (bad target, baseline, configuration or file, or an empty baseline) | tool or usage error | tool or usage error |
384
424
 
385
- `--exit-zero` turns `1` into `0` for `check`, `bag-check` and `lint`; tool
386
- errors still exit `2`.
425
+ `--warnings-as-errors` makes unsuppressed warnings exit `1` too. `--exit-zero`
426
+ turns `1` into `0` for `check`, `bag-check` and `lint`; tool errors still exit
427
+ `2`.
387
428
 
388
429
  ## GitHub Action
389
430
 
390
431
  A composite Action runs `roschema check` on the runner (no setup needed) and
391
- posts the findings as annotations on the PR diff:
432
+ posts the findings as annotations on the PR diff, with a full report (findings,
433
+ semver advice and unreported changes) as the job summary:
392
434
 
393
435
  ```yaml
394
436
  # .github/workflows/interfaces.yml
@@ -403,9 +445,10 @@ jobs:
403
445
  - uses: justagist/roschema/action@main
404
446
  with:
405
447
  against: origin/${{ github.base_ref }}
406
- # version: "roschema==0.2.1" # pin a release for reproducible results
448
+ # version: "roschema==0.4.0" # pin a release for reproducible results
407
449
  # level: source
408
450
  # deep: "true"
451
+ # warnings-as-errors: "true"
409
452
  # exclude: "vendor_msgs* legacy_msgs/msg/Old"
410
453
  ```
411
454
 
@@ -416,6 +459,8 @@ jobs:
416
459
  | `level` | (empty) | reporting level; empty uses the configured level, else `source` |
417
460
  | `format` | `github` | `text`, `json` or `github` |
418
461
  | `deep` | `"false"` | `"true"` adds `--deep` |
462
+ | `allow-empty` | `"false"` | `"true"` adds `--allow-empty` |
463
+ | `warnings-as-errors` | `"false"` | `"true"` adds `--warnings-as-errors` |
419
464
  | `exclude` | (empty) | whitespace-separated globs passed as `--exclude` |
420
465
  | `version` | `roschema` | the roschema requirement installed with `uvx` |
421
466
 
@@ -426,8 +471,10 @@ because it is part of the checkout.
426
471
 
427
472
  ## Troubleshooting
428
473
 
429
- **"no breaking changes found", but you expected some.** Run `roschema ls` with
430
- the same target to see which packages and interfaces are discovered, then check
474
+ **"no breaking changes found", but you expected some.** The line under the
475
+ header says how many interfaces were compared, and `--verbose` lists the changes
476
+ that were seen but not reported, with the reason. Run `roschema ls` with the
477
+ same target to see which packages and interfaces are discovered, then check
431
478
  that:
432
479
 
433
480
  - the packages are not below a `COLCON_IGNORE` or `AMENT_IGNORE` marker;
@@ -445,6 +492,7 @@ shown in [Configuration](#configuration); the error suggests the closest one.
445
492
 
446
493
  Every rule, its levels, default severity, rationale and an example is
447
494
  documented in [docs/rules.md](docs/rules.md), generated from the rule metadata.
495
+ `roschema explain RULE` prints the same section in the terminal.
448
496
 
449
497
  ## Non-goals
450
498
 
@@ -113,13 +113,17 @@ roschema bag-check recording.mcap
113
113
 
114
114
  # what would be checked? (discovered packages and interfaces)
115
115
  roschema ls
116
+
117
+ # what does a rule report, and why?
118
+ roschema explain RS004
116
119
  ```
117
120
 
118
121
  ## Checking
119
122
 
120
123
  ```
121
124
  roschema check [TARGET] --against BASELINE [--level LEVEL] [--format text|json|github]
122
- [--deep | --no-deep] [--exit-zero] [--config PATH] [--exclude GLOB]
125
+ [--deep | --no-deep] [--exit-zero] [--warnings-as-errors] [--allow-empty]
126
+ [--verbose] [--summary FILE] [--config PATH] [--exclude GLOB]
123
127
  ```
124
128
 
125
129
  `TARGET` selects what to check; omit it to check every interface package below
@@ -142,6 +146,16 @@ MCAP bag (`.mcap`) or a lockfile (`roschema.lock`); the type is detected
142
146
  automatically. Initialised git submodules are compared at the commit the
143
147
  baseline records for them.
144
148
 
149
+ Every report says how much was compared, for example `compared 12 interface(s)
150
+ in 3 package(s); 1 new (my_msgs/msg/Status)`. New interfaces are not findings,
151
+ since adding one breaks nothing; the line names the first few so that an
152
+ unexpected one (say, from an unrelated checkout below the target) is easy to
153
+ spot. A baseline with no interfaces at all is
154
+ an error (exit `2`): every current interface would look newly added, so the
155
+ check would pass without comparing anything. That usually means the wrong
156
+ baseline or target; on the pull request that adds the first interfaces, pass
157
+ `--allow-empty`.
158
+
145
159
  Limitations:
146
160
 
147
161
  - When a git ref and a directory share a name, the git ref wins; prefix the
@@ -165,7 +179,7 @@ followed.
165
179
 
166
180
  ```
167
181
  roschema lint [TARGET] [--format text|json|github] [--rules RULE=SEV] [--exit-zero]
168
- [--config PATH] [--exclude GLOB]
182
+ [--warnings-as-errors] [--config PATH] [--exclude GLOB]
169
183
  roschema fmt [TARGET] [--check] [--exclude GLOB]
170
184
  ```
171
185
 
@@ -204,7 +218,8 @@ same. Comparison ignores it.
204
218
 
205
219
  ```
206
220
  roschema bag-check BAG.mcap [TARGETS...] [--level LEVEL] [--config PATH]
207
- [--format text|json|github] [--exit-zero] [--exclude GLOB]
221
+ [--format text|json|github] [--exit-zero] [--warnings-as-errors]
222
+ [--verbose] [--summary FILE] [--exclude GLOB]
208
223
  ```
209
224
 
210
225
  Needs the `bag` extra (`uv tool install 'roschema[bag]'`).
@@ -293,19 +308,30 @@ allow every rule for that target.
293
308
 
294
309
  ## Output
295
310
 
296
- **Text** (the default) groups findings by package and ends with a summary and a
297
- semver advice per package:
311
+ **Text** (the default) starts with what was compared, groups findings by
312
+ package and ends with a summary and a semver advice per package:
298
313
 
299
314
  - **MAJOR** when the package has a wire- or source-level error;
300
- - **MINOR** when its only notable change is an added field (RS002) whose
301
- severity you lowered to `warn`;
315
+ - **MINOR** when it adds an interface or a constant (neither is a finding,
316
+ since neither breaks anything), or an added field (RS002) whose severity you
317
+ lowered to `warn`;
302
318
  - **PATCH** otherwise.
303
319
 
304
- The advice only covers reported findings. Additions such as a new interface or
305
- constant are not findings, so they never raise the advice to MINOR on their own.
320
+ **`--verbose`** also lists, under "not reported", every change that produced no
321
+ reported finding, and why: a new interface or constant (no rule reports either,
322
+ at any level; an added field is RS002), a rule whose level is outside the one
323
+ being checked, a rule that is off, or a suppression, with the allowlist reason.
324
+ Comment-only edits are not changes, so they never appear.
325
+
326
+ **`--format github`** prints what was compared, then emits `::error`/`::warning`
327
+ workflow commands, which GitHub shows as annotations on the PR diff.
306
328
 
307
- **`--format github`** emits `::error`/`::warning` workflow commands, which GitHub
308
- shows as annotations on the PR diff.
329
+ **`--summary FILE`** appends a Markdown report to `FILE` as well, whatever the
330
+ `--format`: what was compared, a table of findings linked to the rule
331
+ reference, the semver advice, and the unreported changes in a collapsed
332
+ section. Pointed at `$GITHUB_STEP_SUMMARY`, it becomes the job summary, which
333
+ matters because GitHub shows only the first 10 error and 10 warning annotations
334
+ of a step.
309
335
 
310
336
  **`--format json`** emits a stable document (`version: 1`):
311
337
 
@@ -324,7 +350,8 @@ shows as annotations on the PR diff.
324
350
  "message": "type changed float32 -> float64",
325
351
  "old": { "file": "my_msgs/msg/Pose.msg", "line": 1 },
326
352
  "new": { "file": "my_msgs/msg/Pose.msg", "line": 1 },
327
- "suppressed": false
353
+ "suppressed": false,
354
+ "help_uri": "https://github.com/justagist/roschema/blob/v0.4.0/docs/rules.md#rs003----field-type-changed"
328
355
  }
329
356
  ],
330
357
  "summary": {
@@ -333,12 +360,24 @@ shows as annotations on the PR diff.
333
360
  "warnings": 0,
334
361
  "suppressed": 0,
335
362
  "advisories": { "my_msgs": "MAJOR" }
363
+ },
364
+ "coverage": {
365
+ "packages": 1,
366
+ "compared": 1,
367
+ "target_only": 0,
368
+ "baseline_only": 0
336
369
  }
337
370
  }
338
371
  ```
339
372
 
340
373
  `old`/`new` are `null` when there is no corresponding side (a removed interface
341
- has no `new`). `lint` output has no `advisories`.
374
+ has no `new`). `coverage` counts interfaces present on both sides (`compared`)
375
+ and on one side only. `help_uri` links to the rule's section of the
376
+ [rule reference](docs/rules.md) for the installed release. With `--verbose`, a
377
+ `changes` array lists the unreported changes (each with `rule`, `package`,
378
+ `interface`, `section`, `target`, `message` and `reason`), including suppressed
379
+ findings with the reason they are suppressed. `lint` output has no
380
+ `advisories`, `coverage` or `changes`.
342
381
 
343
382
  ## Exit codes
344
383
 
@@ -346,15 +385,17 @@ has no `new`). `lint` output has no `advisories`.
346
385
  |---|---|---|---|
347
386
  | `0` | no unsuppressed errors (warnings allowed) | every file formatted | success |
348
387
  | `1` | at least one unsuppressed error | a file would change | n/a |
349
- | `2` | tool or usage error (bad target, baseline, configuration or file) | tool or usage error | tool or usage error |
388
+ | `2` | tool or usage error (bad target, baseline, configuration or file, or an empty baseline) | tool or usage error | tool or usage error |
350
389
 
351
- `--exit-zero` turns `1` into `0` for `check`, `bag-check` and `lint`; tool
352
- errors still exit `2`.
390
+ `--warnings-as-errors` makes unsuppressed warnings exit `1` too. `--exit-zero`
391
+ turns `1` into `0` for `check`, `bag-check` and `lint`; tool errors still exit
392
+ `2`.
353
393
 
354
394
  ## GitHub Action
355
395
 
356
396
  A composite Action runs `roschema check` on the runner (no setup needed) and
357
- posts the findings as annotations on the PR diff:
397
+ posts the findings as annotations on the PR diff, with a full report (findings,
398
+ semver advice and unreported changes) as the job summary:
358
399
 
359
400
  ```yaml
360
401
  # .github/workflows/interfaces.yml
@@ -369,9 +410,10 @@ jobs:
369
410
  - uses: justagist/roschema/action@main
370
411
  with:
371
412
  against: origin/${{ github.base_ref }}
372
- # version: "roschema==0.2.1" # pin a release for reproducible results
413
+ # version: "roschema==0.4.0" # pin a release for reproducible results
373
414
  # level: source
374
415
  # deep: "true"
416
+ # warnings-as-errors: "true"
375
417
  # exclude: "vendor_msgs* legacy_msgs/msg/Old"
376
418
  ```
377
419
 
@@ -382,6 +424,8 @@ jobs:
382
424
  | `level` | (empty) | reporting level; empty uses the configured level, else `source` |
383
425
  | `format` | `github` | `text`, `json` or `github` |
384
426
  | `deep` | `"false"` | `"true"` adds `--deep` |
427
+ | `allow-empty` | `"false"` | `"true"` adds `--allow-empty` |
428
+ | `warnings-as-errors` | `"false"` | `"true"` adds `--warnings-as-errors` |
385
429
  | `exclude` | (empty) | whitespace-separated globs passed as `--exclude` |
386
430
  | `version` | `roschema` | the roschema requirement installed with `uvx` |
387
431
 
@@ -392,8 +436,10 @@ because it is part of the checkout.
392
436
 
393
437
  ## Troubleshooting
394
438
 
395
- **"no breaking changes found", but you expected some.** Run `roschema ls` with
396
- the same target to see which packages and interfaces are discovered, then check
439
+ **"no breaking changes found", but you expected some.** The line under the
440
+ header says how many interfaces were compared, and `--verbose` lists the changes
441
+ that were seen but not reported, with the reason. Run `roschema ls` with the
442
+ same target to see which packages and interfaces are discovered, then check
397
443
  that:
398
444
 
399
445
  - the packages are not below a `COLCON_IGNORE` or `AMENT_IGNORE` marker;
@@ -411,6 +457,7 @@ shown in [Configuration](#configuration); the error suggests the closest one.
411
457
 
412
458
  Every rule, its levels, default severity, rationale and an example is
413
459
  documented in [docs/rules.md](docs/rules.md), generated from the rule metadata.
460
+ `roschema explain RULE` prints the same section in the terminal.
414
461
 
415
462
  ## Non-goals
416
463
 
@@ -22,6 +22,14 @@ inputs:
22
22
  deep:
23
23
  description: Resolve nested types and report transitive breakage (RS110).
24
24
  default: "false"
25
+ allow-empty:
26
+ description: >-
27
+ Pass when the baseline has no interfaces, e.g. on the pull request that
28
+ adds the first ones. Otherwise an empty baseline fails the step.
29
+ default: "false"
30
+ warnings-as-errors:
31
+ description: Fail the step on warnings as well as errors.
32
+ default: "false"
25
33
  exclude:
26
34
  description: >-
27
35
  Whitespace-separated globs of packages/interfaces to skip. Split on
@@ -30,14 +38,18 @@ inputs:
30
38
  version:
31
39
  description: >-
32
40
  roschema requirement for uvx. Unpinned runs the latest release (refreshed
33
- on every run, even with a uv cache); pin it (e.g. "roschema==0.2.1") for
41
+ on every run, even with a uv cache); pin it (e.g. "roschema==0.4.0") for
34
42
  reproducible results.
35
43
  default: roschema
36
44
 
37
45
  runs:
38
46
  using: composite
39
47
  steps:
40
- - uses: astral-sh/setup-uv@v5
48
+ - uses: astral-sh/setup-uv@v10.3.3
49
+ with:
50
+ # Nothing to key a cache on in the caller's repo, and uvx refreshes
51
+ # roschema on every run anyway.
52
+ enable-cache: false
41
53
  - shell: bash
42
54
  env:
43
55
  ROSCHEMA_TARGET: ${{ inputs.target }}
@@ -45,6 +57,8 @@ runs:
45
57
  ROSCHEMA_LEVEL: ${{ inputs.level }}
46
58
  ROSCHEMA_FORMAT: ${{ inputs.format }}
47
59
  ROSCHEMA_DEEP: ${{ inputs.deep }}
60
+ ROSCHEMA_ALLOW_EMPTY: ${{ inputs.allow-empty }}
61
+ ROSCHEMA_WARNINGS_AS_ERRORS: ${{ inputs.warnings-as-errors }}
48
62
  ROSCHEMA_EXCLUDE: ${{ inputs.exclude }}
49
63
  ROSCHEMA_VERSION: ${{ inputs.version }}
50
64
  run: |
@@ -58,6 +72,15 @@ runs:
58
72
  if [ "$ROSCHEMA_DEEP" = "true" ]; then
59
73
  args+=(--deep)
60
74
  fi
75
+ if [ "$ROSCHEMA_ALLOW_EMPTY" = "true" ]; then
76
+ args+=(--allow-empty)
77
+ fi
78
+ if [ "$ROSCHEMA_WARNINGS_AS_ERRORS" = "true" ]; then
79
+ args+=(--warnings-as-errors)
80
+ fi
81
+ if [ -n "${GITHUB_STEP_SUMMARY:-}" ]; then
82
+ args+=(--summary "$GITHUB_STEP_SUMMARY")
83
+ fi
61
84
  read -r -a excludes <<< "${ROSCHEMA_EXCLUDE//$'\n'/ }"
62
85
  for glob in "${excludes[@]}"; do
63
86
  args+=(--exclude "$glob")
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "roschema"
7
- version = "0.2.1"
7
+ version = "0.4.0"
8
8
  description = "Breaking-change detection, linting and formatting for ROS 2 interface definitions"
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0"
@@ -19,6 +19,7 @@ classifiers = [
19
19
  "Programming Language :: Python :: 3.11",
20
20
  "Programming Language :: Python :: 3.12",
21
21
  "Programming Language :: Python :: 3.13",
22
+ "Programming Language :: Python :: 3.14",
22
23
  "Topic :: Software Development :: Quality Assurance",
23
24
  "Typing :: Typed",
24
25
  ]