rigortype 0.2.9 → 0.3.0

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 (184) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/docs/handbook/03-narrowing.md +2 -1
  4. data/docs/handbook/04-tuples-and-shapes.md +7 -3
  5. data/docs/handbook/06-classes.md +18 -9
  6. data/docs/handbook/08-understanding-errors.md +1 -0
  7. data/docs/handbook/11-sig-gen.md +11 -1
  8. data/docs/handbook/appendix-mypy.md +4 -3
  9. data/docs/install.md +14 -6
  10. data/docs/manual/01-installation.md +98 -4
  11. data/docs/manual/02-cli-reference.md +49 -12
  12. data/docs/manual/03-configuration.md +34 -2
  13. data/docs/manual/04-diagnostics.md +23 -0
  14. data/docs/manual/07-plugins.md +4 -4
  15. data/docs/manual/09-editor-integration.md +4 -4
  16. data/docs/manual/11-ci.md +51 -0
  17. data/docs/manual/12-caching.md +65 -0
  18. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +9 -1
  19. data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +5 -3
  20. data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +128 -0
  21. data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +60 -0
  22. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +3 -2
  23. data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +117 -0
  24. data/lib/rigor/analysis/check_rules/rule_ids.rb +125 -0
  25. data/lib/rigor/analysis/check_rules/rule_walk.rb +2 -1
  26. data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +2 -1
  27. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +283 -0
  28. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +10 -1
  29. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +93 -0
  30. data/lib/rigor/analysis/check_rules.rb +557 -95
  31. data/lib/rigor/analysis/dependency_recorder.rb +93 -9
  32. data/lib/rigor/analysis/dependency_source_inference/walker.rb +2 -1
  33. data/lib/rigor/analysis/incremental_session.rb +456 -51
  34. data/lib/rigor/analysis/path_expansion.rb +42 -0
  35. data/lib/rigor/analysis/plugin_fact_fingerprint.rb +188 -0
  36. data/lib/rigor/analysis/rule_catalog.rb +226 -1
  37. data/lib/rigor/analysis/run_cache_key.rb +68 -0
  38. data/lib/rigor/analysis/run_cache_probe.rb +72 -0
  39. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +181 -28
  40. data/lib/rigor/analysis/runner/pool_coordinator.rb +77 -15
  41. data/lib/rigor/analysis/runner/project_pre_passes.rb +95 -49
  42. data/lib/rigor/analysis/runner/run_snapshots.rb +9 -2
  43. data/lib/rigor/analysis/runner.rb +343 -68
  44. data/lib/rigor/analysis/severity_stamp.rb +43 -0
  45. data/lib/rigor/analysis/worker_session.rb +30 -2
  46. data/lib/rigor/bleeding_edge.rb +37 -8
  47. data/lib/rigor/builtins/regex_refinement.rb +36 -1
  48. data/lib/rigor/cache/descriptor.rb +88 -28
  49. data/lib/rigor/cache/file_digest.rb +158 -0
  50. data/lib/rigor/cache/incremental_snapshot.rb +41 -5
  51. data/lib/rigor/cache/rbs_descriptor.rb +56 -10
  52. data/lib/rigor/cache/store.rb +33 -3
  53. data/lib/rigor/ci_detector.rb +90 -0
  54. data/lib/rigor/cli/annotate_command.rb +15 -9
  55. data/lib/rigor/cli/baseline_command.rb +4 -1
  56. data/lib/rigor/cli/check_command.rb +212 -27
  57. data/lib/rigor/cli/coverage_command.rb +6 -1
  58. data/lib/rigor/cli/docs_command.rb +0 -30
  59. data/lib/rigor/cli/doctor_command.rb +163 -23
  60. data/lib/rigor/cli/lsp_command.rb +5 -0
  61. data/lib/rigor/cli/mcp_command.rb +5 -0
  62. data/lib/rigor/cli/plugins_command.rb +36 -6
  63. data/lib/rigor/cli/plugins_renderer.rb +44 -12
  64. data/lib/rigor/cli/probe_environment.rb +85 -0
  65. data/lib/rigor/cli/sig_gen_command.rb +36 -9
  66. data/lib/rigor/cli/skill_command.rb +1 -32
  67. data/lib/rigor/cli/trace_command.rb +5 -6
  68. data/lib/rigor/cli/type_of_command.rb +9 -8
  69. data/lib/rigor/cli/type_scan_command.rb +8 -8
  70. data/lib/rigor/cli.rb +15 -4
  71. data/lib/rigor/config_audit.rb +48 -2
  72. data/lib/rigor/configuration/severity_profile.rb +31 -0
  73. data/lib/rigor/configuration.rb +156 -8
  74. data/lib/rigor/environment/default_libraries.rb +35 -0
  75. data/lib/rigor/environment/rbs_loader.rb +164 -5
  76. data/lib/rigor/environment.rb +19 -27
  77. data/lib/rigor/inference/budget_trace.rb +77 -2
  78. data/lib/rigor/inference/closure_escape_analyzer.rb +14 -1
  79. data/lib/rigor/inference/def_handle.rb +23 -0
  80. data/lib/rigor/inference/def_node_resolver.rb +90 -0
  81. data/lib/rigor/inference/def_return_typer.rb +2 -1
  82. data/lib/rigor/inference/expression_typer.rb +361 -113
  83. data/lib/rigor/inference/method_dispatcher/data_folding.rb +30 -5
  84. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +200 -0
  85. data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +22 -16
  86. data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +33 -0
  87. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +11 -0
  88. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +42 -9
  89. data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +5 -0
  90. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +28 -35
  91. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +40 -8
  92. data/lib/rigor/inference/method_dispatcher.rb +57 -1
  93. data/lib/rigor/inference/mutation_widening.rb +42 -8
  94. data/lib/rigor/inference/narrowing.rb +127 -17
  95. data/lib/rigor/inference/parameter_inference_collector.rb +15 -0
  96. data/lib/rigor/inference/project_patched_scanner.rb +2 -1
  97. data/lib/rigor/inference/rbs_type_translator.rb +8 -1
  98. data/lib/rigor/inference/scope_indexer.rb +451 -36
  99. data/lib/rigor/inference/statement_evaluator.rb +64 -15
  100. data/lib/rigor/inference/struct_fold_safety.rb +48 -11
  101. data/lib/rigor/inference/synthetic_method_scanner.rb +15 -14
  102. data/lib/rigor/inference/void_origin.rb +25 -0
  103. data/lib/rigor/inference/void_tail_summary.rb +220 -0
  104. data/lib/rigor/language_server/completion_provider.rb +2 -1
  105. data/lib/rigor/language_server/document_symbol_provider.rb +2 -1
  106. data/lib/rigor/language_server/folding_range_provider.rb +2 -1
  107. data/lib/rigor/language_server/selection_range_provider.rb +2 -1
  108. data/lib/rigor/language_server/signature_help_provider.rb +2 -1
  109. data/lib/rigor/plugin/base.rb +9 -22
  110. data/lib/rigor/plugin/inflector.rb +12 -3
  111. data/lib/rigor/plugin/io_boundary.rb +5 -1
  112. data/lib/rigor/plugin/isolation.rb +81 -11
  113. data/lib/rigor/plugin/load_error.rb +10 -1
  114. data/lib/rigor/plugin/loader.rb +96 -14
  115. data/lib/rigor/plugin/node_rule_walk.rb +5 -3
  116. data/lib/rigor/plugin/registry.rb +32 -23
  117. data/lib/rigor/plugin.rb +26 -0
  118. data/lib/rigor/protection/mutator.rb +3 -2
  119. data/lib/rigor/reflection.rb +64 -0
  120. data/lib/rigor/runtime/jit.rb +128 -0
  121. data/lib/rigor/scope/discovery_index.rb +12 -2
  122. data/lib/rigor/scope.rb +122 -28
  123. data/lib/rigor/sig_gen/classification.rb +5 -1
  124. data/lib/rigor/sig_gen/generator.rb +38 -6
  125. data/lib/rigor/sig_gen/observation_collector.rb +4 -3
  126. data/lib/rigor/sig_gen/rbs_validity.rb +44 -0
  127. data/lib/rigor/sig_gen/renderer.rb +10 -0
  128. data/lib/rigor/sig_gen/write_result.rb +9 -4
  129. data/lib/rigor/sig_gen/writer.rb +24 -2
  130. data/lib/rigor/source/node_children.rb +116 -0
  131. data/lib/rigor/source/node_locator.rb +3 -1
  132. data/lib/rigor/source/node_walker.rb +4 -2
  133. data/lib/rigor/source.rb +1 -0
  134. data/lib/rigor/type/difference.rb +28 -24
  135. data/lib/rigor/type/hash_shape.rb +34 -10
  136. data/lib/rigor/version.rb +1 -1
  137. data/lib/rigortype.rb +24 -0
  138. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +4 -2
  139. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +6 -4
  140. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +5 -3
  141. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +4 -2
  142. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +3 -1
  143. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +4 -2
  144. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +4 -2
  145. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +3 -1
  146. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +4 -2
  147. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +38 -6
  148. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +3 -1
  149. data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +3 -1
  150. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +10 -0
  151. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +11 -0
  152. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +3 -1
  153. data/plugins/rigor-rails/lib/rigor-rails.rb +11 -12
  154. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +3 -1
  155. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +3 -1
  156. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +7 -5
  157. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +83 -9
  158. data/plugins/rigor-rbs-inline/lib/rigor-rbs-inline.rb +9 -5
  159. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +3 -1
  160. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/scope_walker.rb +3 -1
  161. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +11 -0
  162. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/http_status_codes.rb +4 -1
  163. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +4 -2
  164. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +3 -1
  165. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +74 -14
  166. data/sig/prism_node_children.rbs +9 -0
  167. data/sig/rigor/inference.rbs +7 -0
  168. data/sig/rigor/plugin/base.rbs +2 -3
  169. data/sig/rigor/reflection.rbs +1 -0
  170. data/sig/rigor/scope.rbs +12 -1
  171. data/skills/rigor-ci-setup/SKILL.md +10 -0
  172. data/skills/rigor-editor-setup/SKILL.md +11 -0
  173. data/skills/rigor-next-steps/SKILL.md +23 -2
  174. data/skills/rigor-plugin-author/SKILL.md +3 -3
  175. data/skills/rigor-plugin-author/references/02-walker-and-types.md +2 -2
  176. data/skills/rigor-plugin-review/SKILL.md +2 -2
  177. data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +5 -4
  178. data/skills/rigor-project-init/SKILL.md +29 -3
  179. data/skills/rigor-project-init/references/01-detect.md +1 -1
  180. data/skills/rigor-project-init/references/02-configure.md +39 -5
  181. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +20 -0
  182. data/skills/rigor-project-init/references/05-jit-performance.md +125 -0
  183. metadata +42 -24
  184. data/lib/rigor/cli/ci_detector.rb +0 -89
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 565d25fe6c480013419e1e7f6d2bcb60512f7cf56c6a34b612e5951dad8b6c62
4
- data.tar.gz: c48e0fcde278beb45ac4b8cb0c5acb272540d8bbc5c37111e983ffacee4c772d
3
+ metadata.gz: 7cea93d0b50519bad210f33907d4135c61043d910618153ad76f1f25b0163b4d
4
+ data.tar.gz: 45cba56af68f2ee34c7e8674310fb35bb74e366a3fc67bc89262ad8cb9556cc7
5
5
  SHA512:
6
- metadata.gz: 8859749b0ee71c8dca814c164de8e47bd3004f52773299181f8aeca2bb30b5c96dc47bf46278a3378704f548a30ab68c3cb273b71a2c7346b6bba67bfbfb9365
7
- data.tar.gz: 450f2f074e21d3a3fd7cff2e9f7f8971dd838bdfe1fadc0e80300e7c3dc9a98eb13b9890033436b77f5e82196238c91503f598fce78435f9ce9d598c64fd2e87
6
+ metadata.gz: 626ef3d0d0967cfd0f8e887a7ab2f53ed741a848fd25f90239c69e7a40e84c3b4a658acd945da0fd4d344d9de9cc650c3be8236f107468b70e2d32441ee9d7d3
7
+ data.tar.gz: e23af83633c18c05dce1a01814f235838143a31f3a34cf719ca35239a40bc854fff8f8e4bd55f4d0c4f39547b4b600c7c543019055576e297fed36ef60a70537
data/README.md CHANGED
@@ -231,8 +231,8 @@ rigor docs --list # list every bundled page
231
231
 
232
232
  ## Status
233
233
 
234
- Current release: **`v0.2.9`** (2026-07-11) — the latest cut on the
235
- `0.2.x` evaluation line opened by `v0.2.0`, the first
234
+ Current release: **`v0.3.0`** (2026-07-19) — the first cut on the
235
+ `0.3.x` line, continuing the evaluation era opened by `v0.2.0`, the first
236
236
  publicly-announced (general / evaluation) release. The line publishes
237
237
  an enumerated [compatibility surface](docs/compatibility.md) as a
238
238
  minor-non-break trial, rehearsing the contract that hard-freezes at
@@ -275,7 +275,8 @@ end
275
275
  (Narrowing the truthy edge further to specific refinement
276
276
  carriers — so `\d{4}` would produce `decimal-int-string` — is a
277
277
  demand-driven follow-up on the regex-pattern → refinement-name
278
- recogniser track; see [`docs/ROADMAP.md`](../ROADMAP.md).)
278
+ recogniser track, tracked as
279
+ [#164](https://github.com/rigortype/rigor/issues/164).)
279
280
 
280
281
  ## Negation and `unless`
281
282
 
@@ -221,9 +221,13 @@ arr = [1, ARGV.first]
221
221
  # Array[Constant<1> | String?]
222
222
  ```
223
223
 
224
- The same goes for hashes whose keys are not provably symbol /
225
- string literals Rigor produces `Hash[K, V]` rather than
226
- `HashShape`.
224
+ The same goes for hashes whose keys are not provably scalar
225
+ literals (symbols, plain strings, integers, floats, `true` /
226
+ `false` / `nil`) — Rigor produces `Hash[K, V]` rather than
227
+ `HashShape`. Within a shape, key identity follows `Hash#eql?`:
228
+ `1` and `1.0` are distinct keys, and a repeated literal key
229
+ keeps the last entry, exactly like the runtime. Non-symbol,
230
+ non-string keys display in hashrocket form (`{ 1 => 2 }`).
227
231
 
228
232
  ## Deriving new shapes — `pick_of` / `omit_of` / `partial_of` / `required_of` / `readonly_of`
229
233
 
@@ -170,8 +170,8 @@ discovery composes uniformly across both shapes.
170
170
 
171
171
  `Struct.new(*Symbol)` produces a positional-arg constructor
172
172
  plus the same accessors as `Data.define`. Rigor folds struct
173
- member reads too, but — because a `Struct` is mutable — only
174
- where the value cannot have changed:
173
+ member reads too, and — because a `Struct` is mutable — tracks a
174
+ straight-line setter so the read stays precise:
175
175
 
176
176
  ```ruby
177
177
  Coord = Struct.new(:x, :y)
@@ -181,18 +181,27 @@ c = Coord.new(10, 20)
181
181
  assert_type("10", c.x)
182
182
  assert_type("20", c.y)
183
183
 
184
- # A local that is mutated, aliased, or escapes is not fold-safe —
185
- # its reads degrade to Dynamic, never a stale value.
184
+ # A straight-line setter re-types the member; the sibling stays precise.
186
185
  m = Coord.new(1, 2)
187
186
  m.x = 9
188
- assert_type("Dynamic[top]", m.x)
187
+ assert_type("9", m.x)
188
+ assert_type("2", m.y)
189
+
190
+ # A local that is aliased, escapes, or is mutated inside a loop or
191
+ # block is not fold-safe — its reads degrade to Dynamic, never a
192
+ # stale value, because a write Rigor cannot see could have changed it.
193
+ a = Coord.new(1, 2)
194
+ alias_ref = a
195
+ a.x = 9
196
+ assert_type("Dynamic[top]", a.x)
189
197
  ```
190
198
 
191
199
  Because `Struct` accessors are also writers, the fold is gated:
192
- a member read off a freshly constructed instance (`Coord.new(1,
193
- 2).x`) or a local the analysis proves is never written, aliased,
194
- or passed away folds to the member's type; anything else widens
195
- to `Dynamic[top]`. `Data` is read-only, so its reads always fold.
200
+ a member read folds off a freshly constructed instance (`Coord.new(1,
201
+ 2).x`), off a local the analysis proves is never written, aliased, or
202
+ passed away, or off one whose only writes are straight-line member
203
+ setters (their assigned type flows into the later read); anything else
204
+ widens to `Dynamic[top]`. `Data` is read-only, so its reads always fold.
196
205
 
197
206
  ## Inheritance and method resolution
198
207
 
@@ -87,6 +87,7 @@ Fire when the control flow itself is unsound.
87
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
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
89
  | `flow.dead-assignment` | A plain local-variable write whose target name is never read in the same `def` body. | warning |
90
+ | `flow.duplicate-hash-key` | Two entries of one Hash literal carry the same literal key (`{ a: 1, a: 2 }`, `m("x" => 1, "x" => 2)`) — the last entry wins silently at runtime. Literal keys only; `:a` vs `"a"` and `1` vs `1.0` are distinct keys and never compared. | warning |
90
91
 
91
92
  `flow.unreachable-branch`, `flow.always-truthy-condition`, and
92
93
  `flow.unreachable-clause` are the **reachability family** — each
@@ -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`)
@@ -345,9 +345,10 @@ def shout: (String s) -> non-empty-string
345
345
 
346
346
  You gain: `s.empty?` is a recognised refinement narrower (no
347
347
  need for `assert s`). `match?(/\A\d+\z/)` does not yet narrow
348
- to `numeric-string` (this is on the v0.1.1 roadmap — see
349
- [`docs/ROADMAP.md`](../ROADMAP.md)), but the eventual
350
- behaviour will mirror `s.isdigit()` narrowing in Pyright.
348
+ to `numeric-string` (queued as
349
+ [#164](https://github.com/rigortype/rigor/issues/164)), but the
350
+ eventual behaviour will mirror `s.isdigit()` narrowing in
351
+ Pyright.
351
352
 
352
353
  ## What's next
353
354
 
data/docs/install.md CHANGED
@@ -37,19 +37,27 @@ Then proceed to the **first** matching case below.
37
37
  and pins language runtimes (Ruby, Node, Python, …) and tool gems
38
38
  (like `rigortype`) per project, recording versions in a `mise.toml`
39
39
  that can be committed alongside the code. Other contributors — and CI
40
- — run `mise install` to restore the exact same versions with no
41
- `Gemfile` involvement.
40
+ — run `mise install` to restore those versions with no `Gemfile`
41
+ involvement.
42
42
 
43
43
  Run in the project root:
44
44
 
45
45
  ```sh
46
46
  mise use ruby@4.0
47
- mise use gem:rigortype
47
+ mise use --pin gem:rigortype
48
48
  ```
49
49
 
50
50
  `mise use` installs the tools and writes their versions to `mise.toml`
51
51
  in one step. Commit `mise.toml` so the version is shared.
52
52
 
53
+ `--pin` records the exact Rigor version (`"gem:rigortype" = "0.2.9"`).
54
+ Without it mise writes `"gem:rigortype" = "latest"`, which every
55
+ machine re-resolves to whatever is newest when it first installs — so
56
+ a committed `latest` does not give the team one shared version. The
57
+ trade-off: a pin will not move on its own, and `mise outdated` cannot
58
+ report a pinned tool as behind. Upgrade with `mise upgrade --bump
59
+ gem:rigortype`.
60
+
53
61
  Then verify:
54
62
 
55
63
  ```sh
@@ -184,6 +192,6 @@ project is set up, re-run `rigor skill describe` for the step after
184
192
  that.
185
193
 
186
194
  If `rigor skill describe` is not recognised, your Rigor version predates
187
- it. Run `rigor --version` and upgrade with `mise use gem:rigortype` (or
188
- `gem update rigortype` for Case C/B); on an older version, run
189
- `rigor skill rigor-project-init` directly.
195
+ it. Run `rigor --version` and upgrade with `mise upgrade --bump
196
+ gem:rigortype` (or `gem update rigortype` for Case B/C); on an older
197
+ version, run `rigor skill rigor-project-init` directly.
@@ -244,9 +244,23 @@ A few things worth knowing if you have not used mise before:
244
244
  *current directory* recording the chosen versions, and installs
245
245
  the tools as part of the same command — there is no separate
246
246
  install step. (mise also reads the asdf-style `.tool-versions`.)
247
- - **Commit the config to share versions.** Check the generated
248
- `mise.toml` into Git so every contributor — and every CI run —
249
- resolves the same Ruby 4.0 and the same Rigor version.
247
+ - **Commit the config to share versions and pass `--pin` if you
248
+ mean it.** Check the generated `mise.toml` into Git so every
249
+ contributor, and every CI run, resolves the same versions. Note
250
+ what the two commands above actually record, because they differ:
251
+ `mise use ruby@4.0` preserves the precision you asked for and
252
+ writes `ruby = "4.0"` (any 4.0.x), but `mise use gem:rigortype` has
253
+ no requested version to preserve and writes `"gem:rigortype" =
254
+ "latest"` — which resolves to whatever is newest *on each machine,
255
+ whenever that machine first installs it*. A committed `latest` is
256
+ not a shared version. To share one, pin it:
257
+
258
+ ```sh
259
+ mise use --pin gem:rigortype # records e.g. "gem:rigortype" = "0.2.9"
260
+ ```
261
+
262
+ Then read [Keeping Rigor up to date](#keeping-rigor-up-to-date):
263
+ a pin is a version that will not move until you move it.
250
264
  - **For a machine-wide install, add `-g`.** `mise use -g
251
265
  gem:rigortype` writes mise's global config
252
266
  (`~/.config/mise/config.toml`) instead of a project `mise.toml`,
@@ -284,6 +298,48 @@ explicitly with `mise exec gem:rigortype -- rigor`. See
284
298
  [Editor integration](09-editor-integration.md) for the editor
285
299
  side.
286
300
 
301
+ ### Keeping Rigor up to date
302
+
303
+ Rigor ships often. How you upgrade depends on what your config
304
+ records — and in one case, on knowing that mise will not tell you an
305
+ upgrade exists.
306
+
307
+ - **`"gem:rigortype" = "latest"`** — what a plain `mise use
308
+ gem:rigortype` writes. `mise upgrade gem:rigortype` moves you to the
309
+ newest release.
310
+ - **An exact pin** — `"gem:rigortype" = "0.2.9"`, from `--pin` or from
311
+ `mise use gem:rigortype@0.2.9`. Here `mise upgrade` **will not move
312
+ it, and `mise outdated` will not report it.** Both compare the
313
+ installed version against the range the config asks for, and an
314
+ exact pin is a range containing only itself, so a pinned Rigor
315
+ reports itself up to date forever, however far behind it has fallen.
316
+ Use `--bump`, which installs the newest release *and* rewrites the
317
+ pin:
318
+
319
+ ```sh
320
+ mise upgrade --bump gem:rigortype
321
+ ```
322
+
323
+ Neither is a bug to work around — a pin doing nothing is a pin working.
324
+ But it does mean a pinned setup has no passive signal that a new Rigor
325
+ exists: `--bump` is both how you look and how you move.
326
+
327
+ **After a Ruby change.** mise's `gem:` backend installs each tool
328
+ against the Ruby that was active at install time, and its
329
+ [gem backend docs](https://mise.jdx.dev/dev-tools/backends/gem.html)
330
+ note that "if the ruby version used by a gem package changes, (by mise
331
+ or system ruby), you may need to reinstall the gem". Rigor's pinned
332
+ `ruby@4.0` makes this rare — patch upgrades within 4.0.x are followed
333
+ automatically — but if you remove or replace that Ruby, reinstall:
334
+
335
+ ```sh
336
+ mise install -f gem:rigortype # or, for every gem-backend tool:
337
+ mise install -f "gem:*"
338
+ ```
339
+
340
+ On a plain `gem install` (below), the equivalent is `gem update
341
+ rigortype`.
342
+
287
343
  ## asdf
288
344
 
289
345
  `asdf` follows the same model. Install a Ruby 4.0.x with the
@@ -300,7 +356,8 @@ gem install rigortype
300
356
  `asdf` has no general-purpose gem backend, so the gem itself is
301
357
  installed with `gem install` rather than an `asdf` command. `mise`
302
358
  (above) is the more integrated option because its `gem:` backend
303
- pins the gem the same way it pins Ruby.
359
+ records the gem in the same config that records Ruby, and — as the
360
+ next section explains — keeps the two from interfering.
304
361
 
305
362
  ## Simple alternative — gem install
306
363
 
@@ -316,6 +373,43 @@ quickest path, but it records nothing per project: a version
316
373
  manager keeps the Rigor version pinned next to the project, so
317
374
  local runs and CI cannot drift apart.
318
375
 
376
+ It also leaves Rigor sharing a Ruby with your work, which is the
377
+ deeper reason the version managers are listed first. The executable
378
+ RubyGems installs begins with `#!/usr/bin/env ruby`, so it runs under
379
+ whatever `ruby` is first on `PATH` *at the moment you invoke it* — and
380
+ because Rigor requires Ruby 4.0 while your projects pin their own,
381
+ running `rigor` inside a project on Ruby 3.x fails before it starts.
382
+ mise's `gem:` backend does not have this failure mode: it installs each
383
+ tool into its own private gem directory and points the executable at
384
+ the Ruby the tool was installed with, so a project's Ruby pin cannot
385
+ reach it. A `gem install` under a Ruby 4.0 you never switch away from
386
+ is perfectly fine; one that shares a Ruby with projects you switch
387
+ between is a footgun.
388
+
389
+ ## If you want Bundler to manage the version
390
+
391
+ The rule at the top of this chapter is about your *application's*
392
+ `Gemfile` — the one Bundler resolves against your app's gems, and whose
393
+ entries `Bundler.require` loads at boot. It is not a rule against
394
+ Bundler. A `Gemfile` holding nothing but Rigor, resolved separately and
395
+ selected with `BUNDLE_GEMFILE`, keeps every property the prohibition
396
+ exists to protect: your application's dependency graph never meets
397
+ Rigor's, and your application's Ruby is never constrained by Rigor's.
398
+
399
+ That arrangement — a `Gemfile` under `.github/rigor/`, plus the
400
+ Dependabot config that keeps it current — is written up under
401
+ [Pinning Rigor's version](11-ci.md#pinning-rigors-version) in the CI
402
+ chapter. Nothing about it is CI-specific:
403
+
404
+ ```sh
405
+ BUNDLE_GEMFILE=.github/rigor/Gemfile bundle exec rigor check
406
+ ```
407
+
408
+ It is more moving parts than `mise use --pin gem:rigortype`, which
409
+ records the same version in one line and needs no `bundle exec`. Reach
410
+ for it when your team already runs everything through Bundler and wants
411
+ Rigor on the same footing — not as a first choice.
412
+
319
413
  ## Nix
320
414
 
321
415
  If you use Nix, Rigor's flake exposes the executable as a package,
@@ -41,12 +41,12 @@ the `paths:` list from the configuration file.
41
41
  | `--cache-stats` | Print the on-disk cache inventory when finished. |
42
42
  | `--[no-]stats` | Print a run summary (files, classes, memory, wall time) to stderr. Default on. |
43
43
  | `--coverage` | Add a type-precision coverage block to the output (`coverage` object under `--format json`; a one-line summary in text mode). Off by default — it is a second precision pass over the analyzed files, the same scan [`rigor coverage`](#rigor-coverage) runs, so it is opt-in. |
44
- | `--workers=N` | Dispatch analysis across `N` parallel worker processes (fork-based pool today; ADR-15). Default `0` (sequential). |
44
+ | `--workers=N` | Dispatch analysis across `N` parallel worker processes (fork-based pool today; ADR-15). Default `0` (sequential). Applies to `--incremental` re-checks as well as full runs. |
45
45
  | `--baseline=PATH` | Load a baseline file, overriding config. |
46
46
  | `--no-baseline` | Ignore any configured baseline. |
47
47
  | `--baseline-strict` | Fail the run on any baseline drift — a CI gate. |
48
48
  | `--treat-all-as-inline-rbs` | Force-load `rigor-rbs-inline` with `require_magic_comment: false`, so every analysed file is treated as inline-RBS without the `# rbs_inline: enabled` comment (ADR-32). |
49
- | `--bleeding-edge[=ids]` | Adopt the bleeding-edge overlay for this run, overriding the configured [`bleeding_edge:`](03-configuration.md) selection (ADR-50 § WD2). Bare adopts every queued feature; `--bleeding-edge=a,b` adopts only the named feature ids. The overlay is empty in this release, so this is currently a no-op. Inspect it with [`rigor show-bleedingedge`](#rigor-show-bleedingedge). |
49
+ | `--bleeding-edge[=ids]` | Adopt the bleeding-edge overlay for this run, overriding the configured [`bleeding_edge:`](03-configuration.md) selection (ADR-50 § WD2). Bare adopts every queued feature; `--bleeding-edge=a,b` adopts only the named feature ids. Inspect it with [`rigor show-bleedingedge`](#rigor-show-bleedingedge). |
50
50
  | `--no-bleeding-edge` | Ignore any configured `bleeding_edge:` selection for this run (adopt none). |
51
51
  | `--tmp-file=PATH --instead-of=PATH` | Editor mode: analyse `PATH` using the buffer in `--tmp-file`. Both required together. |
52
52
 
@@ -183,6 +183,17 @@ rigor sig-gen [paths]
183
183
  | `--new-files` / `--new-methods` / `--tighter-returns` | Emit only that classification. |
184
184
  | `--format=text\|json` | Output format. |
185
185
 
186
+ Every signature is parsed before it is emitted. A method whose
187
+ generated RBS does not parse is **skipped** (`sig.skipped.unrenderable-rbs`)
188
+ and reported on stderr rather than written — an unparseable
189
+ `.rbs` is quarantined *whole* by `rigor check`, so one bad line
190
+ would take every other type in the file down with it. Under
191
+ `--write`, a file whose assembled content does not parse is
192
+ **refused** (the existing file is left untouched) and the command
193
+ exits `1`: you asked for a write and did not get one. Such a skip
194
+ is a bug in Rigor's RBS rendering, not in your code — please
195
+ report it.
196
+
186
197
  ## `rigor lsp`
187
198
 
188
199
  Run the Language Server over stdio. See
@@ -400,6 +411,11 @@ Report the activation status of every plugin configured in
400
411
  `.rigor.yml` — loaded, load-error (with reason), and each
401
412
  plugin's declared extension surfaces. See [Plugins](07-plugins.md).
402
413
 
414
+ Each loaded plugin's row also reports the resolved file it
415
+ loaded from (a `path:` line in text, a `"path"` key in JSON), so
416
+ if a stale installed gem is shadowing a newer checkout's bundled
417
+ plugin copy the mismatch is visible at a glance.
418
+
403
419
  ```sh
404
420
  rigor plugins [--format=text|json] [--strict] [--capabilities] [--config=PATH]
405
421
  ```
@@ -474,8 +490,9 @@ rigor skill [<name>] [--full <name>] [--path <name>] [--list] [--describe]
474
490
  | `--describe` | Probe the project's state (config / baseline / `sig/` / CI — presence only, never runs `rigor check`) and recommend the next skill to run. Also spelled `describe`; surfaced top-level as [`rigor describe`](#rigor-describe) below. |
475
491
 
476
492
  The verb spellings `rigor skill list` / `print <name>` / `path <name>`
477
- are **deprecated** (a one-line stderr notice; removed in v0.3.0)use
478
- the forms above. `describe` / `--describe` stay first-class.
493
+ were **removed in v0.3.0**the positional slot is a skill name, so
494
+ they now read as an unknown skill. Use the forms above.
495
+ `describe` / `--describe` stay first-class.
479
496
 
480
497
  ## `rigor describe`
481
498
 
@@ -518,8 +535,9 @@ rigor docs [<name>] [--path <name>] [--list [<category>]]
518
535
  | `--path <name>` | Print the single-line absolute path of a doc, suitable as input to a file-reading tool. |
519
536
  | `--list [<category>]` | Table of every bundled doc (name + absolute path); pass `manual` or `handbook` to filter. |
520
537
 
521
- The verb spellings `rigor docs list` / `path <name>` are **deprecated**
522
- (a one-line stderr notice; removed in v0.3.0)use `--list` / `--path`.
538
+ The verb spellings `rigor docs list` / `path <name>` were **removed in
539
+ v0.3.0**the positional slot is a doc name, so they now read as an
540
+ unknown doc. Use `--list` / `--path`.
523
541
 
524
542
  The canonical web copy of the index is
525
543
  <https://rigor.typedduck.fail/llms.txt>; `rigor docs` serves the same
@@ -543,12 +561,16 @@ rigor show-bleedingedge [--config PATH] [--format text|json]
543
561
  | `--config PATH` | Use this `.rigor.yml` instead of auto-discovery. |
544
562
  | `--format text\|json` | Output format. Default `text`. |
545
563
 
546
- The overlay is **empty in this release** the mechanism is wired and
547
- ready, but no discipline has been queued for a major yet, so the command
548
- currently reports an empty set. When a feature is queued it appears here
549
- with its stable id, the severity it would impose, and whether your config
550
- adopts it. See [`docs/compatibility.md`](../compatibility.md) for how
551
- bleeding-edge fits the stability model.
564
+ Each queued feature appears with its stable id, the severity it imposes,
565
+ and whether your configuration adopts it. See
566
+ [`docs/compatibility.md`](../compatibility.md) for how bleeding-edge fits
567
+ the stability model.
568
+
569
+ Queued today:
570
+
571
+ | Feature id | What it changes |
572
+ | --- | --- |
573
+ | `reject-unparseable-signatures` | An unparseable `.rbs` under `signature_paths:` **fails the run** (`rbs.coverage.quarantined-signature` → `error`) instead of being skipped with a warning. |
552
574
 
553
575
  ## `rigor doctor`
554
576
 
@@ -575,6 +597,18 @@ Runs a scoped analysis and audits:
575
597
  from the saved baseline.
576
598
  - **Rails plugin gap** — whether `Gemfile.lock` contains Rails gems
577
599
  but no Rails plugin is enabled.
600
+ - **Gemfile install** — whether Rigor itself resolves as one of the
601
+ project's dependencies, which
602
+ [Installing Rigor](01-installation.md) tells you not to do. Only a
603
+ `rigortype` resolved from a **GEM** remote counts: a `PATH` or `GIT`
604
+ source means you are developing or vendoring Rigor deliberately,
605
+ and Rigor's own repository looks exactly like that.
606
+ - **Plugin installation skew** — whether a plugin Rigor bundles
607
+ loaded from a different `rigortype` installation than the engine,
608
+ naming both paths. The engine and its bundled plugins are
609
+ versioned together, so a copy from another installation can run
610
+ the engine with a mismatched plugin. A warning, not a failure;
611
+ third-party plugins from your own bundle are never flagged.
578
612
 
579
613
  Text output prints `[PASS]`, `[FAIL]`, or `[WARN]` per check plus a
580
614
  routed hint (e.g. "Run `rigor baseline regenerate`"). JSON output
@@ -618,6 +652,9 @@ operational knobs read the environment instead.
618
652
  | `RIGOR_RACTOR_WORKERS=N` | Worker count for parallel analysis. Sits between the CLI flag and the config key in precedence: `--workers=N` > `RIGOR_RACTOR_WORKERS` > `parallel.workers:` > `0` (sequential). |
619
653
  | `RIGOR_POOL_BACKEND=ractor` | Opt back into the (off-by-default) Ractor worker pool instead of the active fork-based pool ([ADR-15](../adr/15-ractor-concurrency.md)). Only relevant with a non-zero worker count; the fork pool is the supported backend. |
620
654
  | `RIGOR_PLUGIN_ISOLATION=none\|process\|ruby_box` | How a plugin's direct calls into its target library are isolated. Default `process`. See [Using plugins § Isolation strategy](07-plugins.md). `RIGOR_BOX` is a legacy alias for `ruby_box`. |
655
+ | `RIGOR_STRICT_VALIDATION=1` | Force full-content cache validation for one run (the same as `cache.validation: digest`, and winning over it) — re-hash every file's content instead of trusting its stat metadata. Use it if a filesystem's timestamps or inode numbers cannot be trusted. See [Caching § How a file is checked for changes](12-caching.md#how-a-file-is-checked-for-changes). |
656
+ | `RIGOR_DISABLE_YJIT=1` | Opt out of Rigor's deferred YJIT enablement. Rigor turns YJIT on partway through a long `check` / `coverage` run so short runs never pay the JIT warm-up; this variable leaves it off entirely. Diagnostics and allocations are identical either way — the effect is wall-time only. |
657
+ | `RIGOR_YJIT_DEADLINE=<seconds>` | Advanced: tune how long a run must last before deferred YJIT enables (default `5.0`). Lower it if your runs are long and you want the JIT sooner; raise it to protect short runs. Ignored when `RIGOR_DISABLE_YJIT=1` is set or YJIT is unavailable. |
621
658
 
622
659
  Three further variables (`RIGOR_BUDGET_TRACE`,
623
660
  `RIGOR_HEAP_PROFILE`, `RIGOR_HEAP_TRACE`) enable developer-facing
@@ -22,6 +22,24 @@ discovery entirely.
22
22
  All relative paths in a config file resolve against that
23
23
  file's own directory.
24
24
 
25
+ ## Editor validation
26
+
27
+ Rigor ships a JSON Schema for this file. Editors that
28
+ understand the [`yaml-language-server`](https://github.com/redhat-developer/yaml-language-server)
29
+ magic comment — VS Code's YAML extension, the IntelliJ
30
+ family, Helix, Neovim with `yaml-ls` — give you
31
+ autocomplete, hover docs, and structural validation as you
32
+ type:
33
+
34
+ ```yaml
35
+ # yaml-language-server: $schema=https://github.com/rigortype/rigor/raw/master/schemas/rigor-config.schema.json
36
+ ```
37
+
38
+ `rigor init` writes that line for you. The schema is not a
39
+ copy of this page — it is a source of truth in its own
40
+ right, kept in step with the loader by a spec, so a key it
41
+ rejects is a key Rigor does not accept.
42
+
25
43
  ## A minimal config
26
44
 
27
45
  ```yaml
@@ -44,6 +62,7 @@ cache:
44
62
  | `exclude` | Array | `[]` | Glob patterns to skip. `vendor/bundle`, `.bundle`, and `node_modules` are always excluded. |
45
63
  | `includes` | Array | `[]` | Other config files to layer underneath this one. |
46
64
  | `fold_platform_specific_paths` | Boolean | `false` | Resolve Ruby-version-conditional load paths when discovering sources. |
65
+ | `parameter_inference` | Boolean | `false` | Opt-in call-site parameter type inference on the `check` walk ([ADR-67](../adr/67-parameter-type-inference.md) WD6). When `true`, an undeclared `def` / `initialize` / setter parameter is typed to the union of its resolved call-site argument types, sharpening downstream ivar reads, folds, and protection coverage. Precision-additive only — the negative rules never fire against an inferred parameter. Cannot be combined with `--incremental`. |
47
66
 
48
67
  ### Type sources
49
68
 
@@ -65,6 +84,7 @@ types it was meant to describe into a high-confidence
65
84
  of real type errors. The audit covers:
66
85
 
67
86
  ```
87
+ rigor: `excludee` is not a recognized configuration key; it has no effect. Did you mean `exclude`?
68
88
  rigor: signature_paths: "/path/to/sig" does not exist (no signatures loaded from it)
69
89
  rigor: signature_paths: "/path/to/sig" matched 0 signature files
70
90
  rigor: libraries: "csb" is not an available RBS library (no signatures loaded from it)
@@ -73,6 +93,11 @@ rigor: severity_overrides: "flow.bogus" is not a recognized rule id; the overrid
73
93
  rigor: bundler.lockfile: "./missing/Gemfile.lock" does not exist
74
94
  ```
75
95
 
96
+ The unrecognised-key check covers **top-level** keys, and skips
97
+ the namespaces reserved for other implementations (see below).
98
+ A typo *inside* a group — `cache: { pth: … }` — is caught by
99
+ the JSON schema as you type rather than at check time.
100
+
76
101
  These are warnings, not errors — partial or optional bundles and
77
102
  forward-looking config are valid setups. The audit only fires on explicit,
78
103
  working-setup-safe signals: an unset default (auto-detected `<root>/sig`,
@@ -91,7 +116,7 @@ statically and may resolve at run time. The same findings appear in the
91
116
  | `severity_profile` | String | `"balanced"` | `lenient`, `balanced`, or `strict` — see [Diagnostics](04-diagnostics.md). |
92
117
  | `severity_overrides` | Hash | `{}` | Per-rule / per-family severity, e.g. `{ call: warning, flow.always-truthy-condition: off }`. |
93
118
  | `baseline` | String / `false` | `nil` | Path to a `.rigor-baseline.yml`, or `false` to disable an inherited one. See [Baselines](06-baseline.md). |
94
- | `bleeding_edge` | Boolean / Array / Hash | `false` | Adopt the next major's queued diagnostic disciplines early ([ADR-50](../adr/50-release-engineering-and-stability-strategy.md) § WD2). `false` adopts none; `true` adopts the whole overlay; a list of feature ids adopts only those; `{ all: true, except: [ids] }` adopts all but the named. Orthogonal to `severity_profile`. Override it for a single run with [`rigor check --bleeding-edge[=ids]`](02-cli-reference.md#rigor-check) / `--no-bleeding-edge`. Inspect with [`rigor show-bleedingedge`](02-cli-reference.md#rigor-show-bleedingedge). The overlay is empty in this release, so every form is currently a no-op. |
119
+ | `bleeding_edge` | Boolean / Array / Hash | `false` | Adopt the next major's queued diagnostic disciplines early ([ADR-50](../adr/50-release-engineering-and-stability-strategy.md) § WD2). `false` adopts none; `true` adopts the whole overlay; a list of feature ids adopts only those; `{ all: true, except: [ids] }` adopts all but the named. Orthogonal to `severity_profile`. Override it for a single run with [`rigor check --bleeding-edge[=ids]`](02-cli-reference.md#rigor-check) / `--no-bleeding-edge`. Inspect with [`rigor show-bleedingedge`](02-cli-reference.md#rigor-show-bleedingedge). |
95
120
 
96
121
  ### Dependency RBS discovery
97
122
 
@@ -128,11 +153,18 @@ explicitly with `bundler.bundle_path:`, or supply signatures another way:
128
153
  | --- | --- | --- | --- |
129
154
  | `cache.path` | String | `.rigor/cache` | Persistent cache directory. See [Caching](12-caching.md). |
130
155
  | `cache.max_bytes` | Integer or `null` | `268435456` (256 MB) | LRU eviction cap for the cache directory; `null` disables eviction. See [Caching § Size and eviction](12-caching.md#size-and-eviction). |
131
- | `parallel.workers` | Integer | `0` | Parallel worker processes for per-file analysis (fork-based pool today; ADR-15); `0` is sequential. CLI `--workers` and `RIGOR_RACTOR_WORKERS` take precedence. |
156
+ | `cache.validation` | String | `"auto"` | How the cache checks whether a file is unchanged: `auto` behaves as `digest` when a CI environment is detected and as `stat` otherwise; `stat` compares size + nanosecond timestamps + inode and only re-hashes a file whose stat moved; `digest` re-hashes every file's content every run. Both keep the content hash as the sole change authority — `stat` just skips the hash when the stat proves a file untouched. See [Caching § How a file is checked for changes](12-caching.md#how-a-file-is-checked-for-changes). The `RIGOR_STRICT_VALIDATION=1` environment variable forces `digest` for one run and wins over this key; `RIGOR_CI_DETECT=0` disables the CI detection. |
157
+ | `parallel.workers` | Integer | `0` | Parallel worker processes for per-file analysis (fork-based pool today; ADR-15); `0` is sequential. CLI `--workers` and `RIGOR_RACTOR_WORKERS` take precedence. Applies to `--incremental` re-checks as well as full runs. |
132
158
  | `plugins_io.network` | String | `"disabled"` | Plugin network policy — `disabled` or `allowlist`. |
133
159
  | `plugins_io.allowed_paths` | Array | `[]` | Filesystem paths plugins may read. |
134
160
  | `plugins_io.allowed_url_hosts` | Array | `[]` | URL hosts plugins may fetch from when `network: allowlist`. |
135
161
 
162
+ ### Reserved for other implementations
163
+
164
+ | Key | Type | Default | Meaning |
165
+ | --- | --- | --- | --- |
166
+ | `rigor_rs` | Hash | — | **Reserved; this implementation skips it.** Another Rigor implementation reads keys under this namespace, so one `.rigor.yml` can serve both. Rigor validates its shape at the schema level only — it never reads the value, and an invalid one is never a runtime error here. Leave it alone unless the tool that reads it tells you otherwise. |
167
+
136
168
  ## A worked example
137
169
 
138
170
  ```yaml
@@ -41,18 +41,26 @@ carries no `documentation_url`.
41
41
  | <a id="rule-call-wrong-arity"></a>`call.wrong-arity` | The positional-argument count matches no signature. | high |
42
42
  | <a id="rule-call-argument-type-mismatch"></a>`call.argument-type-mismatch` | An argument's type provably violates the parameter contract. | high |
43
43
  | <a id="rule-call-possible-nil-receiver"></a>`call.possible-nil-receiver` | The receiver is `T \| nil` and the method is not defined on `NilClass`. | high |
44
+ | <a id="rule-call-raise-non-exception"></a>`call.raise-non-exception` | A `raise` / `fail` argument's concrete type is provably not an Exception class, an Exception instance, a String, or an object defining `#exception` — a runtime `TypeError`. | high |
44
45
  | <a id="rule-call-unresolved-toplevel"></a>`call.unresolved-toplevel` | A top-level implicit-self call resolves against no same-file `def`, `pre_eval:` patch, or `Kernel` / `Object` method. | low |
45
46
  | <a id="rule-flow-always-raises"></a>`flow.always-raises` | The expression provably raises on every reachable path. | high |
46
47
  | <a id="rule-flow-unreachable-branch"></a>`flow.unreachable-branch` | An `if` / `unless` / ternary branch is statically dead. | high |
47
48
  | <a id="rule-flow-always-truthy-condition"></a>`flow.always-truthy-condition` | A condition is provably always truthy or always falsey. | medium |
48
49
  | <a id="rule-flow-dead-assignment"></a>`flow.dead-assignment` | A local is written but never read in the same method. | medium |
49
50
  | <a id="rule-flow-unreachable-clause"></a>`flow.unreachable-clause` | A `case`/`when` or `case`/`in` clause is statically dead — its subject type is disjoint with the pattern, or a prior clause already exhausted the subject. | medium |
51
+ | <a id="rule-flow-duplicate-hash-key"></a>`flow.duplicate-hash-key` | A Hash literal repeats a literal key (symbol, plain string, integer, float, `true`/`false`/`nil`) — the last entry silently overwrites the earlier one at runtime. Literal keys only; symbol vs string and `1` vs `1.0` never collide, and interpolated / constant / computed keys are never compared. A `**splat` between two identical literal keys does not rescue the pair. | high |
52
+ | <a id="rule-flow-return-in-ensure"></a>`flow.return-in-ensure` | An explicit `return` inside an `ensure` clause — it overrides the method's in-flight return value and silently swallows any in-flight exception. A `return` in a nested `def`, lambda, or `define_method` block inside the `ensure` does not fire (it exits that inner frame). | high |
53
+ | <a id="rule-flow-shadowed-rescue-clause"></a>`flow.shadowed-rescue-clause` | A `rescue` clause can never run because an earlier clause of the same chain already catches a superclass (or the same class) of every exception class it names. | high |
50
54
  | <a id="rule-def-return-type-mismatch"></a>`def.return-type-mismatch` | The method body's result violates its declared RBS return type. | medium |
51
55
  | <a id="rule-def-ivar-write-mismatch"></a>`def.ivar-write-mismatch` | An instance variable is written with a type disagreeing with its first write. | high |
52
56
  | <a id="rule-def-method-visibility-mismatch"></a>`def.method-visibility-mismatch` | An explicit-receiver call reaches a private method. | high |
53
57
  | <a id="rule-def-override-visibility-reduced"></a>`def.override-visibility-reduced` | An override reduces the visibility it inherits from a project-defined ancestor. | high |
54
58
  | <a id="rule-def-override-return-widened"></a>`def.override-return-widened` | An override's declared return type widens the inherited return (covariance). | high |
55
59
  | <a id="rule-def-override-param-narrowed"></a>`def.override-param-narrowed` | An override narrows an inherited parameter type (contravariance). | high |
60
+ | <a id="rule-static-value-use-void"></a>`static.value-use.void` | A value recovered from an author-declared `-> void` return is used in value context (an assignment right-hand side, a call receiver, or a call argument). Off by default; reaches a run only through the `use-of-void-value` bleeding-edge feature (ADR-100). A bare-statement `void` call and a legitimate `top` value both stay silent. | high |
61
+ | <a id="rule-suppression-unknown-rule"></a>`suppression.unknown-rule` | A `# rigor:disable[-file]` comment names a rule that does not exist (typically a typo), so the suppression silently does nothing. `plugin.`-prefixed tokens are never flagged. | high |
62
+ | <a id="rule-suppression-empty"></a>`suppression.empty` | A `# rigor:disable[-file]` comment lists no rules, so it suppresses nothing. | high |
63
+ | <a id="rule-suppression-unknown-marker"></a>`suppression.unknown-marker` | A comment uses a suppression marker Rigor does not recognise — typically the RuboCop reflex `# rigor:disable-next-line <rule>` or `# rigor:enable <rule>`. Rigor's only markers are `# rigor:disable <rules>` (suppresses on its own line) and `# rigor:disable-file <rules>`, so the comment suppresses nothing. | high |
56
64
  | <a id="rule-rbs_extended-unsatisfied-conformance"></a>`rbs_extended.unsatisfied-conformance` | A class declares `%a{rigor:v1:conforms-to _Interface}` in its RBS but is missing a method the interface requires. Presence-based: only definitively-absent required methods fire. | — |
57
65
  | <a id="rule-assert-type-mismatch"></a>`assert.type-mismatch` | An `assert_type` expectation does not match the inferred type. | high |
58
66
  | <a id="rule-dump-type"></a>`dump.type` | A `dump_type` call — informational, prints the inferred type. | — |
@@ -192,6 +200,21 @@ config.merge(extra) # rigor:disable call.undefined-method
192
200
  It accepts qualified IDs, family wildcards (`call`), a
193
201
  comma- or space-separated list, or `all`.
194
202
 
203
+ A marker that cannot work is flagged rather than silently
204
+ ignored: a token that names no known rule (a typo like
205
+ `call.undefined-metod`) fires
206
+ [`suppression.unknown-rule`](#rule-suppression-unknown-rule),
207
+ a bare marker with no rules at all fires
208
+ [`suppression.empty`](#rule-suppression-empty), and a marker
209
+ word outside Rigor's grammar (the RuboCop reflex
210
+ `# rigor:disable-next-line <rule>`, or `# rigor:enable`)
211
+ fires
212
+ [`suppression.unknown-marker`](#rule-suppression-unknown-marker)
213
+ — all `:warning` in every profile. Tokens under the `plugin.`
214
+ prefix are never flagged (plugin rule vocabularies load
215
+ dynamically), and the surveillance diagnostics are themselves
216
+ suppressible like any other rule.
217
+
195
218
  **In-source, whole file.** `# rigor:disable-file <rules>`
196
219
  anywhere in a file suppresses those rules for every line;
197
220
  `# rigor:disable-file all` silences the file.
@@ -40,10 +40,10 @@ each plugin's options — but the families today are:
40
40
  - **Rails** — `rigor-activerecord`, `rigor-actionpack`,
41
41
  `rigor-rails-routes`, `rigor-rails-i18n`,
42
42
  `rigor-actionmailer`, `rigor-activejob`,
43
- `rigor-activestorage`, `rigor-actioncable`. The
44
- `rigor-rails` meta-gem bundles the Rails set for Gemfile
45
- convenience; you still enumerate the individual plugins you
46
- want under `plugins:`.
43
+ `rigor-activestorage`, `rigor-actioncable`. Enumerate the ones
44
+ you want under `plugins:` there is no umbrella entry that
45
+ turns on the Rails set as a group
46
+ ([ADR-96](../adr/96-plugin-target-gems.md) WD3 proposes one).
47
47
  - **Testing** — `rigor-rspec`, `rigor-rspec-rails`,
48
48
  `rigor-minitest`, `rigor-shoulda-matchers`,
49
49
  `rigor-factorybot`.
@@ -201,8 +201,8 @@ For a legacy bundler-based install, swap the connection list to
201
201
  analyzer assigns the receiver.
202
202
  - Mid-edit buffer support is best-effort. If parse fails AND the
203
203
  cursor isn't right after `.` / `::`, the v1 LSP returns no
204
- completions; deeper recovery is queued (see ROADMAP §
205
- "Editor / IDE integration").
204
+ completions; deeper recovery is queued (see the open
205
+ [`area:editor` issues](https://github.com/rigortype/rigor/issues?q=is%3Aissue+is%3Aopen+label%3Aarea%3Aeditor)).
206
206
 
207
207
  **Hover shows `untyped` everywhere.**
208
208
 
@@ -236,8 +236,8 @@ LSP v1 + v2 landed in v0.1.6 and ship in the `0.1.x` line. Queued
236
236
  follow-ups (`textDocument/signatureHelp`, hash-key completion,
237
237
  `textDocument/definition`, incremental `didChange` sync, Ractor
238
238
  pool dispatch, codeAction / rename / semanticTokens / inlayHint)
239
- are demand-driven; see ROADMAP § "Editor / IDE integration" for
240
- the current queue.
239
+ are demand-driven; the current queue is the open
240
+ [`area:editor` issues](https://github.com/rigortype/rigor/issues?q=is%3Aissue+is%3Aopen+label%3Aarea%3Aeditor).
241
241
 
242
242
  To request a queued feature or report an LSP issue, open a GitHub
243
243
  issue with: the editor + version, the Rigor version