rigortype 0.2.9 → 0.3.1

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 (222) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/data/builtins/ruby_core/array.yml +416 -392
  4. data/data/builtins/ruby_core/file.yml +42 -42
  5. data/data/builtins/ruby_core/hash.yml +302 -302
  6. data/data/builtins/ruby_core/io.yml +191 -191
  7. data/data/builtins/ruby_core/numeric.yml +321 -366
  8. data/data/builtins/ruby_core/proc.yml +124 -124
  9. data/data/builtins/ruby_core/range.yml +21 -21
  10. data/data/builtins/ruby_core/rational.yml +39 -39
  11. data/data/builtins/ruby_core/re.yml +65 -65
  12. data/data/builtins/ruby_core/set.yml +106 -106
  13. data/data/builtins/ruby_core/struct.yml +14 -14
  14. data/data/core_overlay/string_scanner.rbs +6 -5
  15. data/docs/handbook/01-getting-started.md +22 -34
  16. data/docs/handbook/03-narrowing.md +2 -1
  17. data/docs/handbook/04-tuples-and-shapes.md +7 -3
  18. data/docs/handbook/06-classes.md +19 -10
  19. data/docs/handbook/07-rbs-and-extended.md +76 -101
  20. data/docs/handbook/08-understanding-errors.md +114 -246
  21. data/docs/handbook/09-plugins.md +54 -144
  22. data/docs/handbook/11-sig-gen.md +11 -1
  23. data/docs/handbook/README.md +5 -3
  24. data/docs/handbook/appendix-liskov.md +4 -2
  25. data/docs/handbook/appendix-mypy.md +4 -3
  26. data/docs/handbook/appendix-phpstan.md +14 -7
  27. data/docs/handbook/appendix-steep.md +4 -2
  28. data/docs/handbook/appendix-type-theory.md +3 -1
  29. data/docs/install.md +14 -6
  30. data/docs/manual/01-installation.md +98 -4
  31. data/docs/manual/02-cli-reference.md +81 -12
  32. data/docs/manual/03-configuration.md +34 -2
  33. data/docs/manual/04-diagnostics.md +59 -4
  34. data/docs/manual/06-baseline.md +35 -1
  35. data/docs/manual/07-plugins.md +4 -4
  36. data/docs/manual/08-skills.md +6 -1
  37. data/docs/manual/09-editor-integration.md +7 -6
  38. data/docs/manual/11-ci.md +51 -0
  39. data/docs/manual/12-caching.md +65 -0
  40. data/docs/manual/plugins/rigor-actioncable.md +32 -0
  41. data/docs/manual/plugins/rigor-devise.md +4 -2
  42. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +9 -1
  43. data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +5 -3
  44. data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +128 -0
  45. data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +60 -0
  46. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +3 -2
  47. data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +117 -0
  48. data/lib/rigor/analysis/check_rules/rule_ids.rb +125 -0
  49. data/lib/rigor/analysis/check_rules/rule_walk.rb +2 -1
  50. data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +2 -1
  51. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +283 -0
  52. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +10 -1
  53. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +112 -0
  54. data/lib/rigor/analysis/check_rules.rb +581 -98
  55. data/lib/rigor/analysis/dependency_recorder.rb +93 -9
  56. data/lib/rigor/analysis/dependency_source_inference/walker.rb +2 -1
  57. data/lib/rigor/analysis/incremental_session.rb +456 -51
  58. data/lib/rigor/analysis/path_expansion.rb +42 -0
  59. data/lib/rigor/analysis/plugin_fact_fingerprint.rb +188 -0
  60. data/lib/rigor/analysis/rule_catalog.rb +226 -1
  61. data/lib/rigor/analysis/run_cache_key.rb +78 -0
  62. data/lib/rigor/analysis/run_cache_probe.rb +72 -0
  63. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +181 -28
  64. data/lib/rigor/analysis/runner/pool_coordinator.rb +77 -15
  65. data/lib/rigor/analysis/runner/project_pre_passes.rb +95 -49
  66. data/lib/rigor/analysis/runner/run_snapshots.rb +9 -2
  67. data/lib/rigor/analysis/runner.rb +344 -68
  68. data/lib/rigor/analysis/severity_stamp.rb +43 -0
  69. data/lib/rigor/analysis/worker_session.rb +30 -2
  70. data/lib/rigor/bleeding_edge.rb +37 -8
  71. data/lib/rigor/builtins/regex_refinement.rb +36 -1
  72. data/lib/rigor/cache/descriptor.rb +88 -28
  73. data/lib/rigor/cache/file_digest.rb +158 -0
  74. data/lib/rigor/cache/incremental_snapshot.rb +41 -5
  75. data/lib/rigor/cache/rbs_cache_producer.rb +11 -1
  76. data/lib/rigor/cache/rbs_descriptor.rb +56 -10
  77. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +38 -0
  78. data/lib/rigor/cache/store.rb +132 -27
  79. data/lib/rigor/ci_detector.rb +90 -0
  80. data/lib/rigor/cli/annotate_command.rb +15 -9
  81. data/lib/rigor/cli/baseline_command.rb +4 -1
  82. data/lib/rigor/cli/check_command.rb +223 -32
  83. data/lib/rigor/cli/check_invocation.rb +84 -0
  84. data/lib/rigor/cli/coverage_command.rb +6 -1
  85. data/lib/rigor/cli/docs_command.rb +0 -30
  86. data/lib/rigor/cli/doctor_command.rb +169 -31
  87. data/lib/rigor/cli/lsp_command.rb +5 -0
  88. data/lib/rigor/cli/mcp_command.rb +5 -0
  89. data/lib/rigor/cli/plugins_command.rb +36 -6
  90. data/lib/rigor/cli/plugins_renderer.rb +44 -12
  91. data/lib/rigor/cli/probe_environment.rb +85 -0
  92. data/lib/rigor/cli/sig_gen_command.rb +36 -9
  93. data/lib/rigor/cli/skill_command.rb +22 -33
  94. data/lib/rigor/cli/skill_deep_probe.rb +172 -0
  95. data/lib/rigor/cli/skill_describe.rb +75 -9
  96. data/lib/rigor/cli/trace_command.rb +5 -6
  97. data/lib/rigor/cli/type_of_command.rb +9 -8
  98. data/lib/rigor/cli/type_scan_command.rb +8 -8
  99. data/lib/rigor/cli.rb +15 -4
  100. data/lib/rigor/config_audit.rb +48 -2
  101. data/lib/rigor/configuration/severity_profile.rb +31 -0
  102. data/lib/rigor/configuration.rb +156 -8
  103. data/lib/rigor/environment/default_libraries.rb +36 -0
  104. data/lib/rigor/environment/rbs_loader.rb +164 -5
  105. data/lib/rigor/environment.rb +29 -28
  106. data/lib/rigor/inference/budget_trace.rb +77 -2
  107. data/lib/rigor/inference/closure_escape_analyzer.rb +14 -1
  108. data/lib/rigor/inference/def_handle.rb +23 -0
  109. data/lib/rigor/inference/def_node_resolver.rb +90 -0
  110. data/lib/rigor/inference/def_return_typer.rb +2 -1
  111. data/lib/rigor/inference/expression_typer.rb +361 -113
  112. data/lib/rigor/inference/method_dispatcher/data_folding.rb +30 -5
  113. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +200 -0
  114. data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +22 -16
  115. data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +33 -0
  116. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +17 -1
  117. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +42 -9
  118. data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +5 -0
  119. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +28 -35
  120. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +40 -8
  121. data/lib/rigor/inference/method_dispatcher.rb +57 -1
  122. data/lib/rigor/inference/mutation_widening.rb +42 -8
  123. data/lib/rigor/inference/narrowing.rb +127 -17
  124. data/lib/rigor/inference/parameter_inference_collector.rb +15 -0
  125. data/lib/rigor/inference/project_patched_scanner.rb +2 -1
  126. data/lib/rigor/inference/rbs_type_translator.rb +8 -1
  127. data/lib/rigor/inference/scope_indexer.rb +451 -36
  128. data/lib/rigor/inference/statement_evaluator.rb +64 -15
  129. data/lib/rigor/inference/struct_fold_safety.rb +48 -11
  130. data/lib/rigor/inference/synthetic_method_scanner.rb +15 -14
  131. data/lib/rigor/inference/void_origin.rb +25 -0
  132. data/lib/rigor/inference/void_tail_summary.rb +220 -0
  133. data/lib/rigor/language_server/buffer_resolution.rb +6 -3
  134. data/lib/rigor/language_server/buffer_table.rb +46 -6
  135. data/lib/rigor/language_server/completion_provider.rb +2 -1
  136. data/lib/rigor/language_server/diagnostic_publisher.rb +4 -0
  137. data/lib/rigor/language_server/document_symbol_provider.rb +2 -1
  138. data/lib/rigor/language_server/folding_range_provider.rb +2 -1
  139. data/lib/rigor/language_server/incremental_sync.rb +159 -0
  140. data/lib/rigor/language_server/selection_range_provider.rb +2 -1
  141. data/lib/rigor/language_server/server.rb +19 -9
  142. data/lib/rigor/language_server/signature_help_provider.rb +2 -1
  143. data/lib/rigor/language_server.rb +1 -0
  144. data/lib/rigor/plugin/base.rb +38 -24
  145. data/lib/rigor/plugin/inflector.rb +12 -3
  146. data/lib/rigor/plugin/io_boundary.rb +5 -1
  147. data/lib/rigor/plugin/isolation.rb +81 -11
  148. data/lib/rigor/plugin/load_error.rb +10 -1
  149. data/lib/rigor/plugin/loader.rb +96 -14
  150. data/lib/rigor/plugin/node_rule_walk.rb +5 -3
  151. data/lib/rigor/plugin/registry.rb +32 -23
  152. data/lib/rigor/plugin.rb +26 -0
  153. data/lib/rigor/protection/mutator.rb +3 -2
  154. data/lib/rigor/reflection.rb +64 -0
  155. data/lib/rigor/runtime/jit.rb +128 -0
  156. data/lib/rigor/scope/discovery_index.rb +12 -2
  157. data/lib/rigor/scope.rb +122 -28
  158. data/lib/rigor/sig_gen/classification.rb +5 -1
  159. data/lib/rigor/sig_gen/generator.rb +38 -6
  160. data/lib/rigor/sig_gen/observation_collector.rb +4 -3
  161. data/lib/rigor/sig_gen/rbs_validity.rb +44 -0
  162. data/lib/rigor/sig_gen/renderer.rb +10 -0
  163. data/lib/rigor/sig_gen/write_result.rb +9 -4
  164. data/lib/rigor/sig_gen/writer.rb +207 -49
  165. data/lib/rigor/source/node_children.rb +116 -0
  166. data/lib/rigor/source/node_locator.rb +3 -1
  167. data/lib/rigor/source/node_walker.rb +4 -2
  168. data/lib/rigor/source.rb +1 -0
  169. data/lib/rigor/type/difference.rb +28 -24
  170. data/lib/rigor/type/hash_shape.rb +34 -10
  171. data/lib/rigor/version.rb +1 -1
  172. data/lib/rigortype.rb +24 -0
  173. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +4 -2
  174. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +51 -1
  175. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +6 -4
  176. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +5 -3
  177. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +4 -2
  178. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +3 -1
  179. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +4 -2
  180. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +4 -2
  181. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +3 -1
  182. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +4 -2
  183. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +38 -6
  184. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +3 -1
  185. data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +3 -1
  186. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +10 -0
  187. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +11 -0
  188. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +3 -1
  189. data/plugins/rigor-rails/lib/rigor-rails.rb +11 -12
  190. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +3 -1
  191. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +3 -1
  192. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +7 -5
  193. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +83 -9
  194. data/plugins/rigor-rbs-inline/lib/rigor-rbs-inline.rb +9 -5
  195. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +3 -1
  196. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/scope_walker.rb +3 -1
  197. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +11 -0
  198. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/http_status_codes.rb +4 -1
  199. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +4 -2
  200. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +3 -1
  201. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +74 -14
  202. data/sig/prism_node_children.rbs +9 -0
  203. data/sig/rigor/cache.rbs +6 -0
  204. data/sig/rigor/inference/void_origin.rbs +18 -0
  205. data/sig/rigor/inference.rbs +7 -0
  206. data/sig/rigor/plugin/base.rbs +6 -6
  207. data/sig/rigor/reflection.rbs +1 -0
  208. data/sig/rigor/scope.rbs +12 -1
  209. data/skills/rigor-ci-setup/SKILL.md +10 -0
  210. data/skills/rigor-editor-setup/SKILL.md +11 -0
  211. data/skills/rigor-next-steps/SKILL.md +23 -2
  212. data/skills/rigor-plugin-author/SKILL.md +3 -3
  213. data/skills/rigor-plugin-author/references/02-walker-and-types.md +2 -2
  214. data/skills/rigor-plugin-review/SKILL.md +2 -2
  215. data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +5 -4
  216. data/skills/rigor-project-init/SKILL.md +29 -3
  217. data/skills/rigor-project-init/references/01-detect.md +1 -1
  218. data/skills/rigor-project-init/references/02-configure.md +39 -5
  219. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +20 -0
  220. data/skills/rigor-project-init/references/05-jit-performance.md +125 -0
  221. metadata +48 -26
  222. data/lib/rigor/cli/ci_detector.rb +0 -89
@@ -1,9 +1,16 @@
1
1
  # Understanding errors
2
2
 
3
- This chapter is the catalogue of diagnostics Rigor ships, the
4
- families they belong to, and how to suppress one when it is
5
- wrong (or move its severity around). It is the page to land on
6
- when a diagnostic surprises you, in either direction.
3
+ A diagnostic is Rigor telling you something it proved about
4
+ your code. This chapter is about *reading* one: what its parts
5
+ mean, what each rule family is really claiming, why one fires
6
+ when you did not expect it, why one stays silent when you did,
7
+ and how to work a freshly-adopted project down to a clean run.
8
+
9
+ The reference lives one door over. The full rule catalogue,
10
+ each rule's evidence tier, the severity-profile table, and the
11
+ exact syntax of every suppression form are in
12
+ [Diagnostics](../manual/04-diagnostics.md) in the manual; this
13
+ chapter links there rather than restating it.
7
14
 
8
15
  ## Anatomy of a diagnostic
9
16
 
@@ -33,250 +40,106 @@ fires, when it doesn't, the suppression token, the authored
33
40
  severity, and the per-profile severity. `rigor explain` with
34
41
  no argument prints the index of every shipped rule.
35
42
 
36
- ### Confidence and reference fields
37
-
38
- Two extra fields ride along on every built-in diagnostic, for
39
- agents and dashboards consuming `rigor check --format json`
40
- (and on each rule in `rigor explain --format json`):
41
-
42
- - **`evidence_tier`** `high` / `medium` / `low`: Rigor's own
43
- confidence that the firing is a true positive, derived from
44
- the rule's gates, not its severity. `high` means a concrete,
45
- statically-known type with no metaprogramming escape (e.g.
46
- `call.undefined-method`); `medium` rests on a flow / inference
47
- proof with a documented false-positive envelope (e.g.
48
- `flow.always-truthy-condition`); `low` is a resolution- or
49
- coverage-gap signal that often means missing context rather
50
- than a bug (e.g. `call.unresolved-toplevel`). The tier never
51
- feeds severity that stays the `severity_profile:` decision.
52
- Informational helpers (`dump.type`) carry no tier.
53
- - **`documentation_url`** a stable link to the rule's entry in
54
- the published diagnostics catalogue.
55
-
56
- Both are presentation metadata. They never change whether a
57
- diagnostic fires.
58
-
59
- ## The rule catalogue
60
-
61
- Five families, each with one or more rules:
62
-
63
- ### `call.*` — call-site rules
64
-
65
- Fire when a method call's shape is wrong.
66
-
67
- | Rule | Fires when | Default severity |
68
- | --- | --- | --- |
69
- | `call.undefined-method` | The receiver class is statically known and the method is not defined on it (RBS or in-source). | error |
70
- | `call.wrong-arity` | The number of positional arguments does not satisfy any overload's arity. | error |
71
- | `call.argument-type-mismatch` | An argument's type provably does not satisfy the parameter contract (RBS or `RBS::Extended` `param:`). | error |
72
- | `call.possible-nil-receiver` | The receiver type is `T \| nil` and the method is not defined on `NilClass`. | error (warning under `lenient`) |
73
- | `call.unresolved-toplevel` | An implicit-self call at the top level (outside any `def` / `class` / `module`) resolves against no same-file `def`, `pre_eval:` monkey-patch, or `Kernel` / `Object` method — surfacing typos in standalone scripts. | warning under `balanced`, error under `strict`, suppressed under `lenient` |
74
-
75
- `call.*` rules are the highest-volume diagnostics on
76
- real-world code. They are also the most refined — every one
77
- fires only when Rigor can prove the underlying fact.
78
-
79
- ### `flow.*` flow-analysis rules
80
-
81
- Fire when the control flow itself is unsound.
82
-
83
- | Rule | Fires when | Default severity |
84
- | --- | --- | --- |
85
- | `flow.always-raises` | Every reachable evaluation of an expression raises (e.g. `n / 0` where `n: Integer`). | error |
86
- | `flow.unreachable-branch` | An `if` / `unless` / ternary's predicate is a syntactic literal AND the corresponding dead branch is non-empty. | warning |
87
- | `flow.always-truthy-condition` | The predicate of an `if` / `unless` / ternary is provably truthy (or falsey) by inferred type, with surgical skips inside loop bodies and on defensive predicate calls. | warning |
88
- | `flow.unreachable-clause` | A `case <local>; when <Class>` (or bare-class `case`/`in`) clause whose subject narrowing proves it can never match — disjoint from the subject's type, or already exhausted by an earlier clause. | info under `balanced`, warning under `strict`, info under `lenient` |
89
- | `flow.dead-assignment` | A plain local-variable write whose target name is never read in the same `def` body. | warning |
90
-
91
- `flow.unreachable-branch`, `flow.always-truthy-condition`, and
92
- `flow.unreachable-clause` are the **reachability family** — each
93
- proves a branch or `case` clause is dead. `unreachable-clause`
94
- is the newest member: it watches `case <local>; when <Class>`
95
- (and bare-class `case`/`in`) and fires when an earlier clause
96
- already covered a member's type or the clause is disjoint from
97
- the subject. It ships at `:info` under `balanced` (one notch
98
- below its siblings) while its corpus false-positive gate
99
- finishes; bump it with `severity_overrides:` if you want it
100
- louder.
101
-
102
- ### `def.*` — method-definition rules
103
-
104
- Fire when the body of a method violates its declared
105
- contract.
106
-
107
- | Rule | Fires when | Default severity |
108
- | --- | --- | --- |
109
- | `def.return-type-mismatch` | The body's last expression's inferred type cannot satisfy the RBS-declared return type. Honors `%a{rigor:v1:return: <refinement>}` overrides. | warning under `balanced` profile, error under `strict` |
110
- | `def.ivar-write-mismatch` | A later `@var = ...` write's concrete class disagrees with the first write's class in the same class body (NilClass-to-clear is allowlisted). | warning under `balanced` profile, error under `strict` |
111
- | `def.method-visibility-mismatch` | An explicit-receiver call targets a `Nominal[X]` whose discovered method is `:private` in the surrounding class body. | error |
112
- | `def.override-visibility-reduced` | An override reduces the visibility it inherits from a project-defined ancestor (public → protected/private, protected → private), breaking a caller that holds the supertype. | warning under `balanced`, error under `strict`, suppressed under `lenient` |
113
- | `def.override-return-widened` | An override's declared return widens the inherited return (covariance). Fires only on a proven violation when both sides carry an authored RBS signature. | warning under `balanced`, error under `strict`, suppressed under `lenient` |
114
- | `def.override-param-narrowed` | An override narrows an inherited parameter type (contravariance), comparing matching positional parameters. Requires an authored single-overload RBS signature on both sides. | warning under `balanced`, error under `strict`, suppressed under `lenient` |
115
-
116
- The three `def.override-*` rules are the Liskov Substitution
117
- Principle signature rule applied across a project-defined
118
- class/module hierarchy (superclass chain + included/prepended
119
- modules, resolved cross-file). They are the conceptual subject of
43
+ ### Confidence reading the evidence tier
44
+
45
+ Every built-in diagnostic carries an `evidence_tier` —
46
+ `high` / `medium` / `low` which is Rigor's own confidence
47
+ that the firing is a *true positive*, derived from the rule's
48
+ gates rather than from its severity. It is worth internalising
49
+ as a reading habit rather than a config knob: a `high` firing
50
+ (`call.undefined-method` on a concrete receiver) is almost
51
+ always a real bug and can be acted on directly, while a `low`
52
+ one (`call.unresolved-toplevel`) usually means the analyzer is
53
+ missing context an unanalyzed file, a monkey-patch it never
54
+ saw and reads better as "look at this" than "fix this". The
55
+ tier never feeds severity, and never changes whether a
56
+ diagnostic fires; it only routes your attention.
57
+
58
+ The per-rule tiers, and the `documentation_url` field that
59
+ rides alongside them in `--format json`, are in
60
+ [manualEvidence tier](../manual/04-diagnostics.md#evidence-tier).
61
+
62
+ ## The five families
63
+
64
+ Every rule ID reads `family.rule`, and the family tells you
65
+ what kind of proof failed. The catalogue — every rule, what it
66
+ fires on, its evidence tier — is in
67
+ [manual — Diagnostics](../manual/04-diagnostics.md#catalogue).
68
+ What follows is what each family is *about*.
69
+
70
+ **`call.*` — the call site's shape is wrong.** An undefined
71
+ method, an arity no signature accepts, an argument whose type
72
+ provably violates the parameter contract, a receiver that
73
+ might be `nil`. These are the highest-volume diagnostics on
74
+ real-world code, and also the most refined: every one of them
75
+ fires only when Rigor can prove the underlying fact about a
76
+ statically-known receiver, which is why a `call.*` firing is
77
+ usually worth reading first.
78
+
79
+ **`flow.*` the control flow itself is unsound.** Something
80
+ provably raises on every path, a branch is dead, a `case`
81
+ clause can never match, a local is written and never read, a
82
+ Hash literal repeats a key. `flow.unreachable-branch`,
83
+ `flow.always-truthy-condition` and `flow.unreachable-clause`
84
+ form the **reachability family** each proves that a piece of
85
+ code cannot run. `unreachable-clause` is the newest member and
86
+ deliberately quieter than its siblings (`:info` under
87
+ `balanced`) while its corpus false-positive gate finishes;
88
+ bump it with `severity_overrides:` if you want it louder.
89
+
90
+ **`def.*` a definition violates the contract it declares.**
91
+ A body whose return drifts from the declared RBS return, an
92
+ instance variable written with two disagreeing types, an
93
+ explicit-receiver call into a private method. The three
94
+ `def.override-*` rules are the Liskov Substitution Principle's
95
+ signature rule applied across a project-defined hierarchy
96
+ (superclass chain plus included and prepended modules, resolved
97
+ cross-file): returns may narrow, parameters may widen,
98
+ visibility may not shrink. They are the conceptual subject of
120
99
  [appendix: Liskov substitution](appendix-liskov.md).
121
100
 
122
- ### `assert.*` — runtime assertion rules
123
-
124
- | Rule | Fires when | Default severity |
125
- | --- | --- | --- |
126
- | `assert.type-mismatch` | An `assert_type("expected", value)` call's actual inferred type does not match the expected string. | error |
127
-
128
- ### `dump.*` debug helpers
129
-
130
- | Rule | Fires when | Default severity |
131
- | --- | --- | --- |
132
- | `dump.type` | `dump_type(value)` was called — emits an info diagnostic naming the inferred type. | info |
133
-
134
- `dump_type` is your introspection probe during debugging:
135
- sprinkle it through suspicious code, run `rigor check`, read
136
- the inferred types from the diagnostic stream.
137
-
138
- ## Severity profiles
139
-
140
- Rigor ships three named severity profiles that re-stamp the
141
- shipped severities:
142
-
143
- | Profile | Behaviour |
144
- | --- | --- |
145
- | `lenient` | Only proven rules stay `error` (`call.undefined-method`, `wrong-arity`, `assert.type-mismatch`); uncertain rules drop to `warning`, and several to `off`. For incremental adoption on legacy code. |
146
- | `balanced` (default) | Most rules → `error`; uncertain rules `warning`; `dump.type` → `info`. The shipped behaviour. |
147
- | `strict` | Nearly every rule → `error`. The exceptions: `call.self-undefined-method` stays `off` (opt-in only), and `flow.unreachable-clause` is `warning` (pending its false-positive gate). Suitable for new projects with no legacy noise. |
148
-
149
- Set in `.rigor.yml`:
150
-
151
- ```yaml
152
- severity_profile: strict
153
- ```
154
-
155
- ## Per-rule overrides
156
-
157
- Override a single rule's severity:
158
-
159
- ```yaml
160
- severity_overrides:
161
- call.argument-type-mismatch: warning
162
- def.return-type-mismatch: off
163
- ```
164
-
165
- `off` drops the diagnostic from the result entirely — useful
166
- when you want a profile-wide setting for most rules but
167
- silence one specifically.
168
-
169
- Family wildcards work in overrides too:
170
-
171
- ```yaml
172
- severity_overrides:
173
- call: warning # demote every call.* rule
174
- dump: off # drop every dump.* rule
175
- ```
176
-
177
- Per-rule entries beat family-wildcard entries:
178
-
179
- ```yaml
180
- severity_overrides:
181
- call: warning # every call.* → warning
182
- call.undefined-method: error # except undefined-method, still error
183
- ```
184
-
185
- YAML reserves the bareword `off`. If the stripped severity
186
- seems not to apply, quote it: `"off"`. Same for `on`.
187
-
188
- ## In-source suppression
189
-
190
- ```ruby
191
- "hello".no_such_method # rigor:disable call.undefined-method
192
- ```
193
-
194
- The comment must be on the same line as the diagnostic. Use
195
- the qualified rule, the family wildcard, or `all`:
196
-
197
- ```ruby
198
- "hello".no_such_method # rigor:disable call
199
- "hello".no_such_method # rigor:disable all
200
- ```
201
-
202
- For multiline blocks, suppress at every line — Rigor does
203
- not yet ship a `disable-block` syntax.
204
-
205
- ### File-scope suppression
206
-
207
- When you need to silence a rule everywhere in a file —
208
- typically a generated file, a fixture, or a vendored snippet
209
- that triggers a known false positive — drop a single
210
- `# rigor:disable-file` comment anywhere in the file:
211
-
212
- ```ruby
213
- # rigor:disable-file call.undefined-method
214
-
215
- # This whole file is generated; the analyzer's call surface
216
- # is mismatched with the runtime layer for these stubs.
217
- ```
218
-
219
- Convention is to put the comment near the top, but Rigor
220
- scans every comment in the file so any placement works. The
221
- same token forms apply: qualified rule, family wildcard, or
222
- `all`. The line-scope `# rigor:disable` form continues to
223
- work — the two compose, and any project-wide
224
- `disable: [...]` in `.rigor.yml` also still applies.
225
-
226
- ## Project-wide suppression
227
-
228
- ```yaml
229
- # .rigor.yml
230
- disable:
231
- - call.possible-nil-receiver
232
- ```
233
-
234
- Drops the rule project-wide. Heavier hammer than
235
- `severity_overrides: { call.possible-nil-receiver: off }` —
236
- both work; the choice is stylistic.
237
-
238
- ## Baseline diffing for CI
239
-
240
- When you adopt Rigor on an existing codebase, you usually
241
- inherit a long tail of legitimate-but-pre-existing diagnostics
242
- that nobody is going to fix today. The pragmatic move is to
243
- **snapshot the current state as a baseline** and then have CI
244
- fail only on *new* diagnostics introduced by a PR:
245
-
246
- ```sh
247
- # Once: capture the current diagnostic surface.
248
- rigor check --format=json > rigor.baseline.json
249
- git add rigor.baseline.json
250
- git commit
251
-
252
- # Per PR: compare against the committed baseline.
253
- rigor diff rigor.baseline.json
254
- ```
255
-
256
- `rigor diff` prints `+ NEW` rows for each diagnostic that
257
- wasn't in the baseline and `- FIXED` rows for each that has
258
- been resolved since. The exit code is `1` when any new
259
- diagnostic appears and `0` otherwise — so adding a new
260
- violation fails CI, but the legacy diagnostics recorded in
261
- the baseline don't.
262
-
263
- When you fix a row in the baseline, regenerate it with the
264
- same `rigor check --format=json > rigor.baseline.json` so
265
- the project tightens monotonically over time. The
266
- `--format=json` form of `rigor diff` itself is also
267
- available for editor / dashboard integrations.
268
-
269
- `rigor diff` is the lightweight, ad-hoc form — a JSON file you
270
- diff by hand in a CI script. Most projects instead adopt the
271
- **managed baseline**: `rigor baseline generate` writes a
272
- `.rigor-baseline.yml`, you point at it with the `baseline:`
273
- config key, and from then on `rigor check` itself exits clean
274
- on recorded diagnostics and surfaces only new ones — no
275
- separate diff step. That is the path the
276
- [`rigor-project-init` skill](../manual/14-rails-quickstart.md)
277
- sets up for you; see [Baselines](../manual/06-baseline.md) for
278
- the full workflow ([ADR-22](../adr/22-baseline-and-project-onboarding.md)
279
- for the design).
101
+ **`assert.*` and `dump.*` — the introspection helpers.**
102
+ `assert.type-mismatch` fires when an `assert_type("expected",
103
+ value)` call disagrees with the inferred type, so a snippet in
104
+ this handbook is a test of the engine as much as an
105
+ illustration. `dump.type` is not a problem report at all it
106
+ is your probe during debugging: sprinkle `dump_type(value)`
107
+ through suspicious code, run `rigor check`, and read the
108
+ inferred types straight out of the diagnostic stream.
109
+
110
+ ## Turning a diagnostic down
111
+
112
+ Rigor gives you five layers, and picking the right one is
113
+ mostly a question of *how much* you want to say:
114
+
115
+ 1. **`severity_profile:`** the project's overall stance.
116
+ `lenient` for a legacy codebase you are easing Rigor into,
117
+ `balanced` for everyday work, `strict` for a project with
118
+ no legacy noise.
119
+ 2. **`severity_overrides:`** one rule (or one family) at a
120
+ different severity from the rest of the profile. The right
121
+ layer when a rule is *useful but not blocking* for you.
122
+ 3. **`disable:`** the rule is off project-wide. Heavier than
123
+ an override to `off`; both work, and the choice is mostly
124
+ stylistic.
125
+ 4. **`# rigor:disable` / `# rigor:disable-file`** this line,
126
+ or this file. The right layer when the analyzer is wrong
127
+ *here* and right everywhere else. Prefer it to a
128
+ project-wide switch: it keeps the exception visible next to
129
+ the code that needed it, and it is the thing you later
130
+ promote into an `RBS::Extended` directive.
131
+ 5. **A [baseline](../manual/06-baseline.md)** — the whole
132
+ existing backlog, recorded rather than hidden, so new
133
+ diagnostics still surface. This is the layer to reach for
134
+ on adoption day, and the one to reach for *instead of*
135
+ `disable:` when the rule is genuinely finding things you
136
+ have simply not fixed yet.
137
+
138
+ The exact syntax of all five — profile table, override
139
+ precedence, the three suppression forms, the baseline file and
140
+ its `rigor baseline` commands — is in
141
+ [manual — Diagnostics](../manual/04-diagnostics.md#severity-profiles)
142
+ and [manual — Baselines](../manual/06-baseline.md).
280
143
 
281
144
  ## Why a diagnostic might NOT fire when you expected one
282
145
 
@@ -356,6 +219,11 @@ The pragmatic loop on a project that just adopted Rigor:
356
219
  `# rigor:disable` lines into `RBS::Extended` directives
357
220
  so the analyzer learns the real contract.
358
221
 
222
+ On a codebase too large for step 2 in one sitting, record the
223
+ existing diagnostics as a
224
+ [baseline](../manual/06-baseline.md) first and run the loop
225
+ against what CI newly surfaces.
226
+
359
227
  A clean `rigor check` run is the goal; a green CI badge says
360
228
  "every diagnostic that fires is one we accept."
361
229
 
@@ -9,9 +9,10 @@ It does **not** teach plugin *authoring*. That lives in
9
9
  [`examples/`](../../examples/README.md) — six tutorial
10
10
  walkthroughs, each spotlighting one extension surface.
11
11
  Ready-to-install gems for real frameworks live in
12
- [`plugins/`](../../plugins/README.md). Read on to decide
13
- whether you need a plugin; go to `examples/` once you want to
14
- write one, or `plugins/` to install an existing one.
12
+ [`plugins/`](../../plugins/README.md), and activating one is
13
+ [manual Using plugins](../manual/07-plugins.md). Read on to
14
+ decide whether you need a plugin; go to `examples/` once you
15
+ want to write one.
15
16
 
16
17
  ## When you reach for a plugin
17
18
 
@@ -51,151 +52,60 @@ compares the six worked examples on architectural axes
51
52
  engine-collaboration via `Scope#type_of`, cross-plugin facts,
52
53
  return-type contributions, …) and recommends a reading order.
53
54
 
54
- ## What a plugin can do today
55
+ ## Two authoring paths
55
56
 
56
57
  > Still here? Most readers should jump to
57
58
  > [Should you write one?](#should-you-write-one) first — the
58
59
  > answer is usually "no, RBS and `RBS::Extended` get you
59
- > there." The surfaces below are for when it is "yes."
60
-
61
- The v0.1.0+ plugin contract pinned at
62
- [`docs/internal-spec/plugin.md`](../internal-spec/plugin.md)
63
- and laid out across a handful of slice specs in the same
64
- directory gives a plugin five primary surfaces:
65
-
66
- 1. **`#diagnostics_for_file(path:, scope:, root:)`** the
67
- per-file emission hook. Walk the parsed AST, return an
68
- array of `Rigor::Analysis::Diagnostic` rows. The runner
69
- stamps each with `source_family: "plugin.<your-id>"`.
70
- 2. **`dynamic_return(receivers:, methods:, file_methods:)` /
71
- `narrowing_facts(methods:)`** the per-call-site return-type
72
- and flow-narrowing contribution surface (ADR-37 Slice 2).
73
- A `dynamic_return` block names the inferred return type at a
74
- matching call site; the analyzer's dispatcher merges the
75
- contribution and uses it as if it were RBS-declared. A
76
- `narrowing_facts` block contributes branch-narrowing facts.
77
- (`narrowing_facts` was renamed from `type_specifier` in ADR-80;
78
- `type_specifier` remains as a deprecating alias removed in 0.3.0,
79
- so use `narrowing_facts` in new plugins.)
80
- (These replaced the removed `flow_contribution_for` hook
81
- ADR-52 WD3; a plugin that still defines it raises at load.)
82
- 3. **`Plugin::IoBoundary#read_file`** / **`#open_url`**
83
- sandboxed file and (since v0.1.2) HTTPS reads under the
84
- active `TrustPolicy`. Use this when the plugin needs to
85
- read project files (route tables, schemas, locale files)
86
- or fetch a stable URL.
87
- 4. **`Plugin::Base.producer` + `#cache_for`** — plugin-side
88
- cache producers. Use these for parses / lookups expensive
89
- enough to want cross-run caching. Auto-invalidates on
90
- the digest of every file (and content hash of every URL)
91
- the IoBoundary read while building the result.
92
- 5. **`Plugin::FactStore` + `#prepare(services)`** — the
93
- cross-plugin fact-publication surface (v0.1.1 Track 2,
94
- ADR-9). Plugins publish facts in `prepare`; downstream
95
- plugins consume them through `services.fact_store` so
96
- producer-side parsing (e.g., `config/routes.rb`) can be
97
- reused by every consumer (controller-side validators,
98
- factory-side validators, …).
99
-
100
- Several worked examples (`rigor-lisp-eval`, `rigor-pattern`,
101
- `rigor-units`, `rigor-activerecord`) contribute a narrowed
102
- return type via `dynamic_return` rather than only emitting
103
- diagnostics, so chained calls on plugin-typed values resolve
104
- through the analyzer's normal dispatch rather than the
105
- RBS-level `untyped` envelope. See the per-plugin README for
106
- which surface each one demonstrates.
107
-
108
- ## Macro / DSL expansion substrate (ADR-16)
109
-
110
- A second authoring path was added on top of the hand-rolled
111
- walker contract above: the **macro expansion substrate**
112
- (ADR-16). For metaprogramming-heavy DSLs — Rails-style
113
- `has_one_attached`, dry-struct's `attribute`, Devise's
114
- `devise :strategy`, Sinatra's `get '/foo' do ... end` — the
115
- substrate lets a plugin author **declare** the call shape
116
- instead of walking the AST by hand. The plugin's body becomes
117
- a single manifest entry; the substrate handles literal-symbol
118
- extraction, name interpolation, registry lookup, and per-method
119
- synthesis.
120
-
121
- Four tier shapes are recognised. The
122
- [per-library survey](../notes/20260515-macro-expansion-library-survey.md)
123
- identifies which libraries fit each tier and which fall
124
- outside the substrate's scope.
125
-
126
- | Tier | Shape | Manifest declaration | Worked example |
127
- | --- | --- | --- | --- |
128
- | **A — block-as-method** | DSL call's block runs as an instance method on the receiver class (`Sinatra::Base#generate_method`) | `block_as_methods: [Macro::BlockAsMethod.new(receiver_constraint:, method_names:)]` | [`rigor-sinatra`](../../plugins/rigor-sinatra/) |
129
- | **B — trait-inlining registry** | Class-level call enumerates symbols → bundled registry maps each to a module → substrate explodes the module's RBS methods onto the calling class | `trait_registries: [Macro::TraitRegistry.new(receiver_constraint:, method_name:, modules_by_symbol:, always_included:)]` | [`rigor-devise`](../../plugins/rigor-devise/) |
130
- | **C — heredoc template** | Class-level call interpolates a literal symbol into a method-name template; substrate emits synthetic readers | `heredoc_templates: [Macro::HeredocTemplate.new(receiver_constraint:, method_name:, symbol_arg_position:, emit:)]` | [`rigor-dry-struct`](../../plugins/rigor-dry-struct/) |
131
-
132
- The three Tier-A/B/C plugins above are each ~60–110 LoC of
133
- **purely declarative** Ruby — no walker, no
134
- `diagnostics_for_file`, no plugin-side state. The substrate's
135
- pre-pass + dispatcher integration do the work.
136
-
137
- ### Concern re-targeting
138
-
139
- `ActiveSupport::Concern.included do ... end` is a *deferred
140
- class_eval*: any DSL calls inside the block fire on whoever
141
- includes the concern, not on the concern module itself. The
142
- substrate's scanner handles this re-targeting automatically.
143
- For source like:
144
-
145
- ```ruby
146
- module Auditable
147
- extend ActiveSupport::Concern
148
- included do
149
- attribute :audited_at, Types::Time
150
- end
151
- end
152
-
153
- class Address < Dry::Struct
154
- include Auditable
155
- attribute :city, Types::String
156
- end
157
- ```
158
-
159
- `Address` gets BOTH `city` (direct) AND `audited_at` (re-targeted
160
- from `Auditable`) as synthetic readers. The same pattern works
161
- for Tier B traits (Devise modules included via Concerns).
162
-
163
- ### Floor / ceiling
164
-
165
- Per ADR-16 § WD13, the **floor** is that synthetic methods emit
166
- by NAME so cross-file dispatch resolves (no more
167
- `call.undefined-method`). The common cases also recover precise
168
- return types: **Tier B** redispatches on the origin module's
169
- authored RBS (a Devise `valid_password?` resolves to `bool`,
170
- not `Dynamic[T]`), and **Tier C** resolves a plain class-name
171
- return to its `Nominal`. What still degrades to `Dynamic[T]` is
172
- the parameterised / utility-type-shaped Tier C return
173
- (`Array[String]`, `Pick<T, K>`); routing those through the
174
- [ADR-13](../adr/13-typenode-resolver-plugin.md) resolver chain
175
- is the **ceiling**, demand-driven. The substrate never
176
- *fabricates* precision per ADR-5 robustness.
177
-
178
- ### Choosing between the substrate and a hand-rolled walker
179
-
180
- | If the DSL is… | Use the substrate | Use a hand-rolled walker |
181
- | --- | --- | --- |
182
- | `class-level call with literal symbol args + framework class_eval'd heredoc` | ✓ Tier C | — |
183
- | `class-level call with literal symbol args + registry-driven module include` | ✓ Tier B | — |
184
- | `class-level call with do…end block running as an instance method` | ✓ Tier A | — |
185
- | `external Ruby files instance_eval'd under a declared self` | ✓ Tier D (contract only as of v0.1.x) | — |
186
- | `domain DSL whose return type depends on argument shape` | — | `dynamic_return` ([`rigor-lisp-eval`](../../examples/rigor-lisp-eval/)) |
187
- | `cross-file validation (collect declarations, then validate uses)` | — | Two-pass walker ([`rigor-statesman`](../../plugins/rigor-statesman/)) |
188
- | `parsing an external project file (routes, schema, locale)` | — | `IoBoundary` + cache producer ([`rigor-routes`](../../examples/rigor-routes/)) |
189
- | `schema-graph recorder (GraphQL-Ruby-style)` | — | Schema-resolution pass (no plugin authored yet) |
190
-
191
- The substrate and the hand-rolled walker contract coexist —
192
- a plugin can mix `manifest`-declared substrate entries with a
193
- `diagnostics_for_file` walker. The
194
- [`skills/rigor-plugin-author/SKILL.md`](../../skills/rigor-plugin-author/SKILL.md)
195
- SKILL captures the decision flow in detail; the survey at
196
- [`docs/notes/20260515-macro-expansion-library-survey.md`](../notes/20260515-macro-expansion-library-survey.md)
197
- records which Ruby libraries the substrate covers and which
198
- fall outside.
60
+ > there." What follows is for when it is "yes."
61
+
62
+ The decision that shapes everything else is *which* of the two
63
+ authoring paths your DSL falls into.
64
+
65
+ **Declare it.** If the DSL is a class-level call with literal
66
+ symbol arguments — a Rails-style `has_one_attached`, a
67
+ dry-struct `attribute`, a Devise `devise :strategy`, a Sinatra
68
+ `get "/foo" do end` the **macro-expansion substrate**
69
+ ([ADR-16](../adr/16-macro-expansion.md)) already knows that
70
+ shape. You write a manifest entry describing the call, and the
71
+ substrate does the literal-symbol extraction, the name
72
+ interpolation and the per-method synthesis. The three bundled
73
+ plugins on this path are 60–110 lines of declarative Ruby with
74
+ no AST walking at all. The substrate also understands
75
+ `ActiveSupport::Concern`'s deferred `included do end` block,
76
+ so a DSL call written inside a concern lands on the class that
77
+ includes it rather than on the concern.
78
+
79
+ **Walk it.** If the type depends on something the shape of the
80
+ call cannot tell you argument *values* (`Lisp.eval` above), a
81
+ declaration made elsewhere in the project, or the contents of
82
+ an external file such as a route table or a schema dump you
83
+ write a walker instead, and the plugin contract gives you the
84
+ hooks for it: a per-file emission pass, per-call-site
85
+ return-type and flow-narrowing contributions, sandboxed file
86
+ and HTTPS reads under a trust policy, cached producers for
87
+ expensive parses, and a cross-plugin fact store so one plugin's
88
+ parse feeds another plugin's checks.
89
+
90
+ The two paths coexist one plugin can declare substrate
91
+ entries *and* walk files and where you go next depends on
92
+ which of them you need:
93
+
94
+ - [`examples/README.md`](../../examples/README.md) the six
95
+ walkthroughs, each spotlighting one contract surface, with a
96
+ map of which example demonstrates which one.
97
+ - [`docs/internal-spec/plugin.md`](../internal-spec/plugin.md)
98
+ the binding plugin contract: manifest, hooks, services,
99
+ registry, load order. Its siblings
100
+ [`plugin-trust.md`](../internal-spec/plugin-trust.md) and
101
+ [`plugin-cache-producers.md`](../internal-spec/plugin-cache-producers.md)
102
+ cover the I/O and caching surfaces.
103
+ - [`docs/internal-spec/macro-substrate.md`](../internal-spec/macro-substrate.md)
104
+ the substrate's tiers, the manifest field each one
105
+ declares, and how much return-type precision each recovers.
106
+ - [The macro-expansion library survey](../notes/20260515-macro-expansion-library-survey.md)
107
+ which real Ruby libraries fit which tier, and which fall
108
+ outside the substrate entirely.
199
109
 
200
110
  ## Should you write one?
201
111
 
@@ -93,7 +93,7 @@ states:
93
93
  | `equivalent` | The inferred return is not a strict subtype of the declared one — identical, wider, or unrelated — so there is nothing to tighten. Silently skipped. |
94
94
  | `skipped` | Disqualified for one of the reasons below. |
95
95
 
96
- The three `sig.skipped.*` reasons are:
96
+ The `sig.skipped.*` reasons are:
97
97
 
98
98
  - `sig.skipped.complex-shape` — the method has optional, rest,
99
99
  keyword, block, or forwarding parameters. The MVP's
@@ -105,6 +105,16 @@ The three `sig.skipped.*` reasons are:
105
105
  - `sig.skipped.user-authored` — `--overwrite` was not set
106
106
  and the method's existing RBS declaration would have to
107
107
  be replaced.
108
+ - `sig.skipped.unrenderable-rbs` — the signature Rigor
109
+ rendered for this method does not parse as RBS. This one
110
+ is a **bug in Rigor**, not a property of your code: every
111
+ generated line is parsed before it is emitted, and a line
112
+ `rbs` rejects is dropped rather than written, because an
113
+ unparseable `.rbs` is quarantined *whole* by `rigor check`
114
+ — one bad line would take every other type in the file
115
+ down with it. The rest of the signatures are unaffected;
116
+ the skipped method is reported on stderr, and it is worth
117
+ reporting to us.
108
118
 
109
119
  The three `sig.generated.*` identifiers
110
120
  (`sig.generated.new-file` / `new-method` / `tighter-return`)