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
@@ -56,9 +56,11 @@ up the flag, key, or command that *acts* on it.
56
56
  how to nudge it through `.rbs` files and `%a{rigor:v1:…}`
57
57
  directives.
58
58
  8. [**Understanding errors**](08-understanding-errors.md) —
59
- the rule catalogue (`call.undefined-method`,
60
- `call.argument-type-mismatch`, `flow.always-raises`, …),
61
- severity profiles, and `# rigor:disable` suppression.
59
+ reading a diagnostic: what each rule family claims, why one
60
+ fires when you did not expect it (and stays silent when you
61
+ did), and which layer to reach for when you want it quieter.
62
+ The catalogue itself is
63
+ [manual — Diagnostics](../manual/04-diagnostics.md).
62
64
  9. [**Plugins**](09-plugins.md) — when to author one,
63
65
  pointer to the [examples/](../../examples/README.md)
64
66
  landing page.
@@ -571,8 +571,10 @@ model"](appendix-type-theory.md#what-rigor-does-not-model) records.
571
571
  authoring the declared contracts `def.return-type-mismatch` checks
572
572
  against.
573
573
  - [Chapter 8 — Understanding errors](08-understanding-errors.md) for
574
- the `def.return-type-mismatch` / `call.argument-type-mismatch`
575
- rules and severity profiles.
574
+ what the `def.*` family claims, and
575
+ [manual Diagnostics](../manual/04-diagnostics.md) for the
576
+ `def.return-type-mismatch` / `call.argument-type-mismatch`
577
+ entries and the severity profiles.
576
578
 
577
579
  If you want to compare against another *tool* rather than the
578
580
  *principle*, the sibling appendices cover
@@ -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
 
@@ -72,8 +72,10 @@ in another tab, almost every advanced refinement transfers.
72
72
  ## The `@phpstan-assert` family
73
73
 
74
74
  PHPStan's assertion-narrowing PHPDoc tags map directly onto
75
- Rigor's `RBS::Extended` directive grammar. Chapter 7 covers
76
- the table in depth; here it is again for reference:
75
+ Rigor's `RBS::Extended` directive grammar. This is the mapping;
76
+ [Chapter 7](07-rbs-and-extended.md) works the Rigor side
77
+ through examples, and the directive reference itself is
78
+ [manual — RBS::Extended annotations](../manual/16-rbs-extended-annotations.md):
77
79
 
78
80
  | PHPStan PHPDoc | Rigor RBS::Extended | Effect |
79
81
  | --- | --- | --- |
@@ -149,7 +151,8 @@ Rigor has two baseline mechanisms: a **managed** baseline
149
151
  the next `rigor check` via the `baseline:` config key — the
150
152
  closest match to PHPStan's `--baseline`), and a **lightweight**
151
153
  ad-hoc snapshot (`rigor diff` over a `--format=json` dump).
152
- Chapter 8 has the walkthrough.
154
+ [Manual Baselines](../manual/06-baseline.md) walks through
155
+ both.
153
156
 
154
157
  The `includes:` semantics also match PHPStan's: declaration
155
158
  order, later overrides earlier, the current file's keys win
@@ -322,11 +325,15 @@ You probably do not need to read the rest of this appendix
322
325
  section sequentially. Three useful pointers:
323
326
 
324
327
  - [Chapter 7 — RBS and `RBS::Extended`](07-rbs-and-extended.md)
325
- has the full directive grammar, including the PHPStan-mapping
326
- table that this page summarises.
328
+ works the directive grammar through examples; the reference
329
+ table is
330
+ [manual — RBS::Extended annotations](../manual/16-rbs-extended-annotations.md).
327
331
  - [Chapter 8 — Understanding errors](08-understanding-errors.md)
328
- covers the rule catalogue, severity profiles, baseline
329
- diffing every PHPStan onboarding analogue.
332
+ explains how to read a diagnostic and which knob to reach
333
+ for; the catalogue, severity profiles and baselines — every
334
+ PHPStan onboarding analogue — are
335
+ [manual — Diagnostics](../manual/04-diagnostics.md) and
336
+ [manual — Baselines](../manual/06-baseline.md).
330
337
  - [Chapter 9 — Plugins](09-plugins.md) for the
331
338
  Type-Specifying / Dynamic-Return analogues.
332
339
 
@@ -321,8 +321,10 @@ section sequentially. Three useful pointers:
321
321
  if you want to see how the directive grammar layers on top
322
322
  of the RBS you already write.
323
323
  - [Chapter 8 — Understanding errors](08-understanding-errors.md)
324
- for the rule catalogue, severity profiles, and baseline
325
- diffingthe analogue to Steep's diagnostic config.
324
+ for how to read a diagnostic, and
325
+ [manualDiagnostics](../manual/04-diagnostics.md) for the
326
+ rule catalogue and severity profiles — the analogue to
327
+ Steep's diagnostic config.
326
328
  - [`docs/notes/20260503-steep-cross-check-triage.md`](../notes/20260503-steep-cross-check-triage.md)
327
329
  for a worked side-by-side run of Steep and Rigor on the
328
330
  same project (the project itself).
@@ -1651,7 +1651,9 @@ practical companion:
1651
1651
  for the directive grammar that lets you teach Rigor about a
1652
1652
  custom predicate.
1653
1653
  - [Chapter 8 — Understanding errors](08-understanding-errors.md)
1654
- for the rule catalogue (the user-visible end of the trinary
1654
+ for how a diagnostic reads, and
1655
+ [manual — Diagnostics](../manual/04-diagnostics.md) for the
1656
+ rule catalogue (the user-visible end of the trinary
1655
1657
  certainty).
1656
1658
 
1657
1659
  If you want to compare against another *tool* rather than the
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
  ```
@@ -463,6 +479,7 @@ so a skill can never be shadowed by a verb.
463
479
 
464
480
  ```sh
465
481
  rigor skill [<name>] [--full <name>] [--path <name>] [--list] [--describe]
482
+ rigor skill describe [--deep]
466
483
  ```
467
484
 
468
485
  | Form | Purpose |
@@ -472,10 +489,38 @@ rigor skill [<name>] [--full <name>] [--path <name>] [--list] [--describe]
472
489
  | `--full <name>` | Print the `SKILL.md` body **followed by every `references/*.md` inline** — the complete, version-current procedure in one call. This is what a skill's "First: load the version-current copy" directive points at, so a copy vendored into a project (e.g. via `npx skills add`) re-fetches its current steps from the installed gem instead of following a frozen copy. |
473
490
  | `--path <name>` | Print the single-line absolute `SKILL.md` path, suitable as input to a file-reading tool. |
474
491
  | `--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. |
492
+ | `describe --deep` | The same report, except it **runs `rigor check` first** and routes the headline recommendation on the result. Opt-in, because it costs a full analysis and writes `.rigor/cache` — the un-flagged form stays presence-only and side-effect-free. |
493
+
494
+ ### `describe --deep`
495
+
496
+ By default the recommendation comes from a presence-only probe, so it can
497
+ tell that a project *has* a config but not whether the analysis is
498
+ healthy. `--deep` runs the check for you and lets the result pick the
499
+ headline, using the same routing the `## For the agent` section already
500
+ teaches:
501
+
502
+ | Deep check result | Headline becomes |
503
+ | --- | --- |
504
+ | RBS environment built to 0 classes, or a `configuration-error` diagnostic | `rigor-doctor` — the analysis is hollow until the setup is fixed. |
505
+ | Call sites resolving to the project's own definitions (reopened core / gem classes), as at least a third of the errors | `rigor-monkeypatch-resolve` — list them in `pre_eval:` and they clear wholesale. |
506
+ | Any remaining error diagnostics | `rigor-baseline-reduce`. Proven monkey-patch sites below that share are still reported here, so the finding survives even when the headline stays on the larger problem. |
507
+ | Clean, or the project has no config yet | Unchanged — the presence-only recommendation stands. |
508
+
509
+ If the check **cannot run at all** (no config, an unloadable plugin, a
510
+ malformed config) `--deep` does not fail and does not pretend the project
511
+ is clean: it reports what went wrong, falls back to the presence-only
512
+ recommendation, and points you at `rigor doctor`. It deliberately does
513
+ *not* route on weaker signals — "framework calls typing as `Dynamic`" is
514
+ still a judgement call left to you and your agent.
515
+
516
+ ```sh
517
+ rigor skill describe --deep # also: rigor describe --deep
518
+ ```
475
519
 
476
520
  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.
521
+ were **removed in v0.3.0**the positional slot is a skill name, so
522
+ they now read as an unknown skill. Use the forms above.
523
+ `describe` / `--describe` stay first-class.
479
524
 
480
525
  ## `rigor describe`
481
526
 
@@ -494,6 +539,10 @@ It reports a presence-only project-state probe (does a `.rigor.yml`,
494
539
  recommended next skill. It is read-only and side-effect-free — it never
495
540
  runs `rigor check`. Identical output to `rigor skill describe`.
496
541
 
542
+ `rigor describe --deep` forwards to
543
+ [`rigor skill describe --deep`](#rigor-skill), which opts into running the
544
+ check first — slow, and it writes the cache.
545
+
497
546
  ## `rigor docs`
498
547
 
499
548
  Print the documentation bundled inside the `rigortype` gem
@@ -518,8 +567,9 @@ rigor docs [<name>] [--path <name>] [--list [<category>]]
518
567
  | `--path <name>` | Print the single-line absolute path of a doc, suitable as input to a file-reading tool. |
519
568
  | `--list [<category>]` | Table of every bundled doc (name + absolute path); pass `manual` or `handbook` to filter. |
520
569
 
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`.
570
+ The verb spellings `rigor docs list` / `path <name>` were **removed in
571
+ v0.3.0**the positional slot is a doc name, so they now read as an
572
+ unknown doc. Use `--list` / `--path`.
523
573
 
524
574
  The canonical web copy of the index is
525
575
  <https://rigor.typedduck.fail/llms.txt>; `rigor docs` serves the same
@@ -543,12 +593,16 @@ rigor show-bleedingedge [--config PATH] [--format text|json]
543
593
  | `--config PATH` | Use this `.rigor.yml` instead of auto-discovery. |
544
594
  | `--format text\|json` | Output format. Default `text`. |
545
595
 
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.
596
+ Each queued feature appears with its stable id, the severity it imposes,
597
+ and whether your configuration adopts it. See
598
+ [`docs/compatibility.md`](../compatibility.md) for how bleeding-edge fits
599
+ the stability model.
600
+
601
+ Queued today:
602
+
603
+ | Feature id | What it changes |
604
+ | --- | --- |
605
+ | `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
606
 
553
607
  ## `rigor doctor`
554
608
 
@@ -575,6 +629,18 @@ Runs a scoped analysis and audits:
575
629
  from the saved baseline.
576
630
  - **Rails plugin gap** — whether `Gemfile.lock` contains Rails gems
577
631
  but no Rails plugin is enabled.
632
+ - **Gemfile install** — whether Rigor itself resolves as one of the
633
+ project's dependencies, which
634
+ [Installing Rigor](01-installation.md) tells you not to do. Only a
635
+ `rigortype` resolved from a **GEM** remote counts: a `PATH` or `GIT`
636
+ source means you are developing or vendoring Rigor deliberately,
637
+ and Rigor's own repository looks exactly like that.
638
+ - **Plugin installation skew** — whether a plugin Rigor bundles
639
+ loaded from a different `rigortype` installation than the engine,
640
+ naming both paths. The engine and its bundled plugins are
641
+ versioned together, so a copy from another installation can run
642
+ the engine with a mismatched plugin. A warning, not a failure;
643
+ third-party plugins from your own bundle are never flagged.
578
644
 
579
645
  Text output prints `[PASS]`, `[FAIL]`, or `[WARN]` per check plus a
580
646
  routed hint (e.g. "Run `rigor baseline regenerate`"). JSON output
@@ -618,6 +684,9 @@ operational knobs read the environment instead.
618
684
  | `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
685
  | `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
686
  | `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`. |
687
+ | `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). |
688
+ | `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. |
689
+ | `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
690
 
622
691
  Three further variables (`RIGOR_BUDGET_TRACE`,
623
692
  `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