rigortype 0.3.5 → 0.3.7

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 (266) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -5
  3. data/data/builtins/ruby_core/date.yml +6 -3
  4. data/data/builtins/ruby_core/enumerable.yml +1 -0
  5. data/data/builtins/ruby_core/exception.yml +2 -1
  6. data/data/builtins/ruby_core/file.yml +28 -14
  7. data/data/builtins/ruby_core/hash.yml +5 -2
  8. data/data/builtins/ruby_core/io.yml +33 -16
  9. data/data/builtins/ruby_core/random.yml +2 -1
  10. data/data/builtins/ruby_core/re.yml +6 -3
  11. data/data/builtins/ruby_core/struct.yml +2 -0
  12. data/data/builtins/ruby_core/time.yml +2 -1
  13. data/data/gem_overlay/activesupport/core_ext.rbs +442 -11
  14. data/docs/handbook/02-everyday-types.md +1 -1
  15. data/docs/handbook/04-tuples-and-shapes.md +1 -1
  16. data/docs/handbook/08-understanding-errors.md +1 -1
  17. data/docs/handbook/09-plugins.md +2 -2
  18. data/docs/handbook/10-sorbet.md +1 -1
  19. data/docs/handbook/README.md +2 -2
  20. data/docs/handbook/appendix-go.md +1 -1
  21. data/docs/handbook/appendix-java-csharp.md +2 -2
  22. data/docs/handbook/appendix-mypy.md +1 -1
  23. data/docs/handbook/appendix-protocols-and-structural-typing.md +2 -2
  24. data/docs/handbook/appendix-rust.md +1 -1
  25. data/docs/handbook/appendix-type-theory.md +2 -2
  26. data/docs/handbook/appendix-typescript.md +4 -4
  27. data/docs/manual/02-cli-reference.md +29 -16
  28. data/docs/manual/03-configuration.md +1 -1
  29. data/docs/manual/04-diagnostics.md +12 -0
  30. data/docs/manual/05-inspecting-types.md +20 -8
  31. data/docs/manual/07-plugins.md +4 -0
  32. data/docs/manual/08-skills.md +1 -1
  33. data/docs/manual/11-ci.md +1 -1
  34. data/docs/manual/12-caching.md +1 -1
  35. data/docs/manual/15-type-protection-coverage.md +1 -1
  36. data/docs/manual/18-removing-dead-code.md +13 -4
  37. data/docs/manual/19-effect-labels.md +19 -12
  38. data/docs/manual/README.md +2 -2
  39. data/docs/manual/plugins/rigor-actionmailer.md +4 -4
  40. data/docs/manual/plugins/rigor-actionpack.md +61 -0
  41. data/docs/manual/plugins/rigor-activejob.md +3 -3
  42. data/docs/manual/plugins/rigor-activerecord.md +78 -5
  43. data/docs/manual/plugins/rigor-activesupport-core-ext.md +114 -12
  44. data/docs/manual/plugins/rigor-rails-i18n.md +5 -5
  45. data/docs/manual/plugins/rigor-rspec.md +6 -2
  46. data/docs/manual/plugins/rigor-sidekiq.md +22 -0
  47. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +12 -0
  48. data/lib/rigor/analysis/check_rules/dead_version_guard_arms.rb +98 -0
  49. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +3 -3
  50. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +199 -0
  51. data/lib/rigor/analysis/check_rules/rule_walk.rb +1 -2
  52. data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +2 -2
  53. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +6 -12
  54. data/lib/rigor/analysis/check_rules.rb +383 -48
  55. data/lib/rigor/analysis/crash_signature.rb +116 -0
  56. data/lib/rigor/analysis/dependency_recorder.rb +18 -3
  57. data/lib/rigor/analysis/dependency_source_inference/walker.rb +2 -1
  58. data/lib/rigor/analysis/diagnostic.rb +17 -6
  59. data/lib/rigor/analysis/effects_cache_probe.rb +132 -0
  60. data/lib/rigor/analysis/incremental.rb +28 -0
  61. data/lib/rigor/analysis/incremental_session.rb +44 -9
  62. data/lib/rigor/analysis/plugin_fact_fingerprint.rb +1 -2
  63. data/lib/rigor/analysis/reachability/graph.rb +44 -16
  64. data/lib/rigor/analysis/reachability/plugin_roots.rb +11 -8
  65. data/lib/rigor/analysis/reachability/scan.rb +27 -7
  66. data/lib/rigor/analysis/reachability/scan_cache.rb +130 -0
  67. data/lib/rigor/analysis/result.rb +32 -0
  68. data/lib/rigor/analysis/run_cache_key.rb +53 -1
  69. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +93 -1
  70. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +14 -8
  71. data/lib/rigor/analysis/runner/pool_coordinator.rb +34 -0
  72. data/lib/rigor/analysis/runner/project_pre_passes.rb +15 -11
  73. data/lib/rigor/analysis/runner/run_snapshots.rb +7 -2
  74. data/lib/rigor/analysis/runner.rb +298 -38
  75. data/lib/rigor/analysis/worker_session.rb +10 -1
  76. data/lib/rigor/bleeding_edge.rb +7 -6
  77. data/lib/rigor/builtins/hkt_builtins.rb +45 -6
  78. data/lib/rigor/builtins/predefined_constant_refinements.rb +108 -59
  79. data/lib/rigor/builtins/static_return_refinements.rb +20 -1
  80. data/lib/rigor/cache/descriptor.rb +42 -2
  81. data/lib/rigor/cache/file_digest.rb +20 -2
  82. data/lib/rigor/cache/incremental_snapshot.rb +40 -3
  83. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +31 -8
  84. data/lib/rigor/cache/store.rb +10 -1
  85. data/lib/rigor/cli/check_command.rb +67 -48
  86. data/lib/rigor/cli/coverage_command.rb +28 -24
  87. data/lib/rigor/cli/coverage_mutation.rb +13 -17
  88. data/lib/rigor/cli/coverage_scan.rb +47 -6
  89. data/lib/rigor/cli/doc_links.rb +100 -0
  90. data/lib/rigor/cli/docs_command.rb +32 -2
  91. data/lib/rigor/cli/effects_command.rb +27 -2
  92. data/lib/rigor/cli/effects_diff_renderer.rb +82 -12
  93. data/lib/rigor/cli/effects_explain_renderer.rb +6 -3
  94. data/lib/rigor/cli/effects_snapshot_command.rb +52 -7
  95. data/lib/rigor/cli/fused_protection_renderer.rb +19 -2
  96. data/lib/rigor/cli/fused_protection_report.rb +23 -2
  97. data/lib/rigor/cli/measurement_integrity_warning.rb +57 -0
  98. data/lib/rigor/cli/mutation_protection_renderer.rb +22 -2
  99. data/lib/rigor/cli/mutation_protection_report.rb +31 -2
  100. data/lib/rigor/cli/sig_gen_command.rb +18 -0
  101. data/lib/rigor/cli/type_of_command.rb +188 -41
  102. data/lib/rigor/cli/type_of_renderer.rb +72 -3
  103. data/lib/rigor/cli/type_scan_command.rb +10 -1
  104. data/lib/rigor/cli/unused_command.rb +65 -16
  105. data/lib/rigor/cli.rb +22 -9
  106. data/lib/rigor/configuration.rb +7 -4
  107. data/lib/rigor/effects/definition_lines.rb +100 -0
  108. data/lib/rigor/effects/envelope_check.rb +18 -1
  109. data/lib/rigor/effects/liskov_check.rb +17 -8
  110. data/lib/rigor/effects/plugin_facts.rb +1 -1
  111. data/lib/rigor/effects/scanner.rb +2 -3
  112. data/lib/rigor/effects/signature_sources.rb +13 -2
  113. data/lib/rigor/effects/snapshot.rb +53 -21
  114. data/lib/rigor/effects/snapshot_diff.rb +26 -3
  115. data/lib/rigor/environment/bundle_sig_discovery.rb +64 -13
  116. data/lib/rigor/environment/lockfile_resolver.rb +15 -5
  117. data/lib/rigor/environment/missing_gem_constant_index.rb +45 -4
  118. data/lib/rigor/environment/rbs_hierarchy.rb +16 -22
  119. data/lib/rigor/environment/rbs_loader.rb +525 -46
  120. data/lib/rigor/environment.rb +38 -15
  121. data/lib/rigor/inference/acceptance.rb +172 -20
  122. data/lib/rigor/inference/captured_locals.rb +62 -0
  123. data/lib/rigor/inference/content_join.rb +347 -0
  124. data/lib/rigor/inference/def_handle.rb +15 -3
  125. data/lib/rigor/inference/def_node_resolver.rb +62 -3
  126. data/lib/rigor/inference/expression_typer.rb +1020 -141
  127. data/lib/rigor/inference/fork_map.rb +6 -1
  128. data/lib/rigor/inference/hkt_reducer.rb +12 -1
  129. data/lib/rigor/inference/hkt_registry.rb +46 -0
  130. data/lib/rigor/inference/hkt_sugar_translator.rb +93 -0
  131. data/lib/rigor/inference/index_write_widening.rb +48 -0
  132. data/lib/rigor/inference/indexed_narrowing.rb +14 -0
  133. data/lib/rigor/inference/method_dispatcher/block_folding.rb +7 -5
  134. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +10 -0
  135. data/lib/rigor/inference/method_dispatcher/json_folding.rb +58 -0
  136. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +81 -38
  137. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +54 -17
  138. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +1 -2
  139. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +63 -0
  140. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +63 -10
  141. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +104 -0
  142. data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +81 -0
  143. data/lib/rigor/inference/method_dispatcher.rb +155 -14
  144. data/lib/rigor/inference/method_parameter_binder.rb +3 -3
  145. data/lib/rigor/inference/mutation_widening.rb +244 -174
  146. data/lib/rigor/inference/narrowing.rb +331 -27
  147. data/lib/rigor/inference/parameter_inference_collector.rb +1 -0
  148. data/lib/rigor/inference/pre_eval_constants.rb +15 -0
  149. data/lib/rigor/inference/precision_scanner.rb +5 -2
  150. data/lib/rigor/inference/project_patched_scanner.rb +2 -1
  151. data/lib/rigor/inference/rbs_type_translator.rb +89 -39
  152. data/lib/rigor/inference/scope_indexer.rb +1493 -140
  153. data/lib/rigor/inference/statement_evaluator.rb +276 -91
  154. data/lib/rigor/inference/struct_fold_safety.rb +186 -27
  155. data/lib/rigor/inference/synthetic_method_scanner.rb +7 -0
  156. data/lib/rigor/inference/version_guard.rb +229 -0
  157. data/lib/rigor/language_server/selection_range_provider.rb +1 -1
  158. data/lib/rigor/plugin/base.rb +3 -2
  159. data/lib/rigor/plugin/inflector.rb +14 -5
  160. data/lib/rigor/plugin/io_boundary.rb +107 -4
  161. data/lib/rigor/plugin/loader.rb +14 -0
  162. data/lib/rigor/protection/analysis_guard.rb +60 -0
  163. data/lib/rigor/protection/closure_kill_oracle.rb +43 -38
  164. data/lib/rigor/protection/diagnostic_oracle.rb +12 -4
  165. data/lib/rigor/protection/discovery_seed.rb +1 -1
  166. data/lib/rigor/protection/measurement_integrity.rb +36 -0
  167. data/lib/rigor/protection/mutation_scanner.rb +61 -6
  168. data/lib/rigor/protection/mutator.rb +121 -18
  169. data/lib/rigor/rbs_extended/envelope_scanner.rb +9 -0
  170. data/lib/rigor/reflection.rb +182 -30
  171. data/lib/rigor/scope/discovery_index.rb +39 -1
  172. data/lib/rigor/scope.rb +385 -4
  173. data/lib/rigor/sig_gen/classification.rb +12 -1
  174. data/lib/rigor/sig_gen/generator.rb +254 -5
  175. data/lib/rigor/sig_gen/renderer.rb +1 -6
  176. data/lib/rigor/sig_gen/writer.rb +3 -0
  177. data/lib/rigor/source/constant_path.rb +79 -2
  178. data/lib/rigor/triage/catalogue.rb +1 -1
  179. data/lib/rigor/type/combinator.rb +10 -0
  180. data/lib/rigor/type/maybe.rb +47 -0
  181. data/lib/rigor/type/refined.rb +1 -2
  182. data/lib/rigor/type/result.rb +53 -0
  183. data/lib/rigor/type.rb +2 -0
  184. data/lib/rigor/version.rb +1 -1
  185. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +3 -1
  186. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +58 -8
  187. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +15 -2
  188. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +4 -1
  189. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +3 -1
  190. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +39 -14
  191. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +36 -3
  192. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +6 -2
  193. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +21 -5
  194. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +47 -8
  195. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +13 -3
  196. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +242 -4
  197. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +3 -1
  198. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +7 -3
  199. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +62 -11
  200. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +15 -2
  201. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +3 -1
  202. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +5 -1
  203. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +2 -2
  204. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +798 -22
  205. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +156 -13
  206. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +140 -40
  207. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +2 -2
  208. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +20 -6
  209. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +14 -4
  210. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +6 -3
  211. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +23 -6
  212. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +264 -9
  213. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +876 -23
  214. data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +99 -0
  215. data/plugins/rigor-dry-monads/lib/rigor-dry-monads.rb +5 -0
  216. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +34 -0
  217. data/plugins/rigor-ethon/lib/rigor-ethon.rb +3 -0
  218. data/plugins/rigor-ethon/sig/ethon.rbs +27 -0
  219. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +3 -2
  220. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +198 -0
  221. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/binding_recognizer.rb +75 -0
  222. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/catalog.rb +64 -0
  223. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +173 -0
  224. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/target_detector.rb +46 -0
  225. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/types.rb +161 -0
  226. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +121 -0
  227. data/plugins/rigor-ffi/lib/rigor-ffi.rb +3 -0
  228. data/plugins/rigor-ffi/sig/ffi.rbs +67 -0
  229. data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +19 -0
  230. data/plugins/rigor-ffi-rzmq/lib/rigor-ffi-rzmq.rb +3 -0
  231. data/plugins/rigor-ffi-rzmq/sig/ffi_rzmq.rbs +29 -0
  232. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +2 -4
  233. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -1
  234. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
  235. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +2 -1
  236. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +2 -1
  237. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +2 -1
  238. data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +156 -11
  239. data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +30 -0
  240. data/plugins/rigor-rbnacl/lib/rigor-rbnacl.rb +3 -0
  241. data/plugins/rigor-rbnacl/sig/rbnacl.rbs +22 -0
  242. data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +55 -0
  243. data/plugins/rigor-sassc/lib/rigor-sassc.rb +3 -0
  244. data/plugins/rigor-sassc/sig/sassc.rbs +24 -0
  245. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +32 -11
  246. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +3 -1
  247. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +4 -1
  248. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +63 -10
  249. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +15 -2
  250. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +61 -2
  251. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +2 -2
  252. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +4 -2
  253. data/sig/rigor/analysis/baseline.rbs +1 -1
  254. data/sig/rigor/environment.rbs +3 -0
  255. data/sig/rigor/inference.rbs +4 -1
  256. data/sig/rigor/plugin/io_boundary.rbs +2 -0
  257. data/sig/rigor/reflection.rbs +3 -1
  258. data/sig/rigor/scope.rbs +25 -1
  259. data/sig/rigor/type.rbs +95 -1
  260. data/sig/rigor.rbs +16 -0
  261. data/skills/rigor-ci-setup/SKILL.md +2 -2
  262. data/skills/rigor-editor-setup/SKILL.md +2 -2
  263. data/skills/rigor-mcp-setup/SKILL.md +2 -2
  264. data/skills/rigor-monkeypatch-resolve/SKILL.md +1 -1
  265. data/skills/rigor-plugin-review/SKILL.md +3 -3
  266. metadata +73 -3
@@ -91,7 +91,7 @@ The reflex to drop is `unwrap()`. In Rust you reach for
91
91
  `.unwrap()` / `.expect()` when you *know* it is `Some`. Rigor
92
92
  has no in-source assertion that lies to the checker; the
93
93
  equivalents are a `nil?` guard (checked, not asserted) or
94
- `T.must` via the [`rigor-sorbet`](../../plugins/rigor-sorbet/)
94
+ `T.must` via the [`rigor-sorbet`](../../plugins/rigor-sorbet)
95
95
  plugin (see [Chapter 10](10-sorbet.md)).
96
96
 
97
97
  **`Result<T, E>` ↔ exceptions.** Here the models diverge. Ruby
@@ -709,7 +709,7 @@ Rigor maps onto this as:
709
709
  | Dynamic type `?` | **`Dynamic[T]`** — a carrier that *wraps* a "best-guess" type `T` while marking the value as not-statically-verified. `Dynamic[top]` is the maximally-dynamic form. |
710
710
  | Consistency `~` | The `maybe` arm of the trinary certainty — `Dynamic[T] ~ U` holds whenever `T ~ U` does. |
711
711
  | Static/dynamic boundary | Per-method, per-file, per-plugin contribution — Rigor records *why* a value became `Dynamic[T]` in its dynamic-origin algebra. |
712
- | Casts | No in-source cast operator. The opt-in [`rigor-sorbet`](../../plugins/rigor-sorbet/) plugin reads `T.let` / `T.cast` / `T.must` as cast forms; `RBS::Extended` `assert_type` directives serve the same role from `.rbs`. |
712
+ | Casts | No in-source cast operator. The opt-in [`rigor-sorbet`](../../plugins/rigor-sorbet) plugin reads `T.let` / `T.cast` / `T.must` as cast forms; `RBS::Extended` `assert_type` directives serve the same role from `.rbs`. |
713
713
 
714
714
  Two Rigor-specific extensions matter:
715
715
 
@@ -794,7 +794,7 @@ against:
794
794
  ### What Rigor explicitly does NOT do
795
795
 
796
796
  - **Runtime contract insertion at the static / dynamic boundary.**
797
- The opt-in [`rigor-sorbet`](../../plugins/rigor-sorbet/) plugin
797
+ The opt-in [`rigor-sorbet`](../../plugins/rigor-sorbet) plugin
798
798
  reads Sorbet's `T.let` / `T.cast` / `T.must` as cast forms, but
799
799
  the contract *enforcement* is `sorbet-runtime`'s job, not
800
800
  Rigor's. Rigor's static analysis uses the cast as a hint, not
@@ -51,7 +51,7 @@ inference cannot see further).
51
51
  | `{ name: string; age: number }` | `HashShape{name: String, age: Integer}` | Same per-key model; Ruby uses Symbol keys idiomatically. |
52
52
  | `Array<T>` / `T[]` | `Array[T]` | Same. |
53
53
  | `Record<K, V>` | `Hash[K, V]` | Same. |
54
- | `Readonly<T>` | `readonly_of[T]` (via opt-in [`rigor-typescript-utility-types`](../../plugins/rigor-typescript-utility-types/) plugin) | View-level read-only marker on every entry of a `HashShape`. Does NOT prove the underlying object is frozen — ADR-13 § "Readonly". |
54
+ | `Readonly<T>` | `readonly_of[T]` (via opt-in [`rigor-typescript-utility-types`](../../plugins/rigor-typescript-utility-types) plugin) | View-level read-only marker on every entry of a `HashShape`. Does NOT prove the underlying object is frozen — ADR-13 § "Readonly". |
55
55
  | `Partial<T>` / `Required<T>` | `partial_of[T]` / `required_of[T]` (same plugin) | Flips every entry's required-ness on a `HashShape`. `Partial` does NOT widen value types to `nil` — Rigor's `HashShape` distinguishes "key absent" from "key present with nil value" (ADR-13 WD on required-ness flips). |
56
56
  | `Pick<T, K>` / `Omit<T, K>` | `pick_of[T, K]` / `omit_of[T, K]` (same plugin) | Restrict / remove `HashShape` entries by literal-key union; Tuple receivers project by integer index. Non-shape carriers degrade conservatively and surface `dynamic.shape.lossy-projection`. |
57
57
  | Conditional types `T extends U ? A : B` | (none in core; plugin contributions) | A plugin can vary return type by argument shape. |
@@ -159,7 +159,7 @@ inferred call-site instantiation as routinely as TypeScript.
159
159
  | `Array<T>` | `Array[T]` |
160
160
  | `Map<K, V>` | `Hash[K, V]` |
161
161
  | `Promise<T>` | (no analogue — Ruby has no built-in Promise) |
162
- | `Pick<T, K>` / `Omit<T, K>` / `Partial<T>` / `Required<T>` / `Readonly<T>` | Opt-in [`rigor-typescript-utility-types`](../../plugins/rigor-typescript-utility-types/) plugin maps each onto `pick_of` / `omit_of` / `partial_of` / `required_of` / `readonly_of` over `HashShape` (and `pick_of` / `omit_of` over `Tuple`). |
162
+ | `Pick<T, K>` / `Omit<T, K>` / `Partial<T>` / `Required<T>` / `Readonly<T>` | Opt-in [`rigor-typescript-utility-types`](../../plugins/rigor-typescript-utility-types) plugin maps each onto `pick_of` / `omit_of` / `partial_of` / `required_of` / `readonly_of` over `HashShape` (and `pick_of` / `omit_of` over `Tuple`). |
163
163
  | Conditional types | (no analogue — would need a plugin) |
164
164
 
165
165
  Rigor reads RBS generics through its dispatcher and instantiates
@@ -214,7 +214,7 @@ Be honest about what you give up:
214
214
  variation, not type-level expressions.
215
215
  - **Mapped types.** `Pick`, `Omit`, `Partial`, `Required`, and
216
216
  `Readonly` ship as opt-in plugin-supplied vocabulary via
217
- [`rigor-typescript-utility-types`](../../plugins/rigor-typescript-utility-types/),
217
+ [`rigor-typescript-utility-types`](../../plugins/rigor-typescript-utility-types),
218
218
  which maps them onto the Rigor-canonical `pick_of` / `omit_of`
219
219
  / `partial_of` / `required_of` / `readonly_of` shape-projection
220
220
  type functions on `HashShape` (and `pick_of` / `omit_of` on
@@ -294,7 +294,7 @@ def pick: [K, V] (Hash[K, V] obj, Array[K] keys) -> Hash[K, V]
294
294
 
295
295
  The RBS sig stays generic. If you want `Pick<T, K>`'s exact-
296
296
  key-set tracking back, opt into the
297
- [`rigor-typescript-utility-types`](../../plugins/rigor-typescript-utility-types/)
297
+ [`rigor-typescript-utility-types`](../../plugins/rigor-typescript-utility-types)
298
298
  plugin and annotate the return type with the `Pick` spelling:
299
299
 
300
300
  ```rbs
@@ -38,7 +38,7 @@ the `paths:` list from the configuration file.
38
38
  | `--incremental` | Re-analyse only the files changed since the last run plus the files that depend on them, serving the rest from a cross-process disk snapshot (ADR-46). Diagnostics are identical to a full run; a config / gem / version change (or a file added or removed) transparently forces a full re-analysis. See [Caching](12-caching.md). |
39
39
  | `--verify-incremental` | Acceptance gate: run the incremental analyzer against a full `--no-cache` run and assert the diagnostics are byte-identical, then exit (0 on match, 1 with the differing diagnostics on mismatch). Used in CI to guarantee `--incremental` never serves a stale result. |
40
40
  | `--clear-cache` | Delete the cache directory before running. |
41
- | `--cache-stats` | Print the on-disk cache inventory when finished. |
41
+ | `--cache-stats` | Print the on-disk cache inventory when finished — on stdout under `--format text`, on stderr under every other format, so machine-readable output stays a parseable document. `--clear-cache`'s and `--verify-incremental`'s notes follow the same rule. |
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
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. |
@@ -114,16 +114,25 @@ missing file.
114
114
 
115
115
  ## `rigor type-of`
116
116
 
117
- Print the inferred type at one source position.
117
+ Print inferred types at one or more source positions.
118
118
 
119
119
  ```sh
120
- rigor type-of FILE:LINE:COL
121
- rigor type-of FILE LINE COL
120
+ rigor type-of [options] FILE:LINE[:COL] [FILE:LINE[:COL] ...]
121
+ rigor type-of [options] FILE LINE COL
122
122
  ```
123
123
 
124
- Accepts the position as a single `file:line:col` triple or as
125
- three arguments. `--format=json` emits a machine-readable
126
- form; `--trace` records fail-soft fallbacks. The editor-mode
124
+ The colon form is repeatable and keeps argument order while
125
+ parsing and scope-indexing each file once. Omit `COL` to print a
126
+ table of up to 40 expressions that start on that line, outermost
127
+ first at each 1-based column; the table marks when further
128
+ expressions were omitted. The legacy three-argument form accepts
129
+ one exact position.
130
+
131
+ `--format=json` keeps one result as the original flat object and
132
+ wraps several results in a `results` array. Line queries add a
133
+ `line_enumerations` array whose `shown` and `total` counts make
134
+ truncation explicit. `--trace` records fail-soft fallbacks,
135
+ after the rows of a line table in text output. The editor-mode
127
136
  `--tmp-file` / `--instead-of` pair is accepted as on `check`.
128
137
 
129
138
  ## `rigor trace`
@@ -304,8 +313,8 @@ supposed to fan out — the fan-out is the blast radius.
304
313
 
305
314
  ```yaml
306
315
  # .rigor-effects.yml — generated by `rigor effects update`. Commit it; review its diff.
307
- schema: 1
308
- rigor: "0.3.3"
316
+ schema: 2
317
+ rigor: "0.3.5"
309
318
  vocabulary: 1
310
319
  config_digest: "9ec82bfc…"
311
320
  methods:
@@ -314,7 +323,7 @@ methods:
314
323
  "Reports::Nightly#perform":
315
324
  effects: ["io.db.read"]
316
325
  exhaustive: false
317
- unresolved: ["dynamic-send"]
326
+ unresolved: 1
318
327
  reach:
319
328
  "OrdersController#create":
320
329
  effects: ["io.db.read", "io.net.http", "job.enqueue"]
@@ -376,14 +385,17 @@ spelled out:
376
385
  Effect drift against .rigor-effects.yml:
377
386
 
378
387
  methods:
379
- PaymentGateway#charge + io.net.http
388
+ PaymentGateway#charge + io.net.http (app/services/payment_gateway.rb:18)
380
389
 
381
390
  reach:
382
- OrdersController#create + io.net.http
391
+ OrdersController#create + io.net.http (app/controllers/orders_controller.rb:7)
383
392
 
384
- Run `rigor effects update` and commit the result if this change is intended.
393
+ Run `rigor effects explain` to see what caused this, and `rigor effects update` to accept it.
385
394
  ```
386
395
 
396
+ Each row names where the method is defined, so the reviewer reads the report
397
+ rather than searching for the method.
398
+
387
399
  The author runs `rigor effects explain` to see the route —
388
400
 
389
401
  ```
@@ -556,7 +568,7 @@ hand-adjudicated corpus target only **7% of the rows were genuinely
556
568
  unused**; the rest were reachable by means static analysis cannot
557
569
  see. That is why this is a separate command and never a `rigor check`
558
570
  diagnostic — see
559
- [ADR-102](https://github.com/rigortype/rigor/blob/master/docs/adr/102-unused-code-reachability-report.md).
571
+ [ADR-102](../adr/102-unused-code-reachability-report.md).
560
572
 
561
573
  Reachability is computed from **roots**, not by counting references,
562
574
  so a cluster of classes that only reference each other is still
@@ -998,7 +1010,7 @@ Queued today:
998
1010
 
999
1011
  | Feature id | Kind | What it changes |
1000
1012
  | --- | --- | --- |
1001
- | `reject-unparseable-signatures` | severity | An unparseable `.rbs` under `signature_paths:` **fails the run** (`rbs.coverage.quarantined-signature` → `error`) instead of being skipped with a warning. |
1013
+ | `reject-unparseable-signatures` | severity | A broken RBS set **fails the run** instead of degrading it silently: an unparseable `.rbs` under `signature_paths:` (`rbs.coverage.quarantined-signature` → `error`), a declaration that collides on resolve and collapses the whole environment (`rbs.coverage.environment-build-failed` → `error`), and a duplicate method definition that collapses one class's method surface (`rbs.coverage.definition-build-failed` → `error`). |
1002
1014
  | `use-of-void-value` | severity | Using a value recovered from an author-declared `-> void` return in value context is reported as `static.value-use.void` (`warning`). |
1003
1015
  | `discovery-seeded-mutation-sites` | behaviour | [`rigor coverage --protection --mutation`](15-type-protection-coverage.md) measures against the same cross-file project discovery Tier 1 already uses — both when picking the sites and when deciding whether a breakage was caught — so a call on a project class declared in a *sibling* file is measured instead of dropped, and a breakage there can actually be caught. **Adds sites to the denominator, so the reported effectiveness ratio moves** — check it against any `--threshold` you pin in CI before adopting. |
1004
1016
  | `dependent-closure-kill-oracle` | behaviour | [`rigor coverage --protection --mutation`](15-type-protection-coverage.md) counts a breakage as caught when the diagnostic appears anywhere in the mutated file **or the files that depend on it**, instead of in the mutated file alone — so changing what a method returns counts as caught when the error lands in its callers. Can only **add** kills, so the ratio moves up or not at all; it costs about a third more wall time per mutant, and a ratio measured under it is not comparable with one measured without it. |
@@ -1092,9 +1104,10 @@ operational knobs read the environment instead.
1092
1104
  | `RIGOR_CI_DETECT=0` | Turn off CI auto-detection — the same as `--no-ci-detect`. See [Running Rigor in CI § auto-detection](11-ci.md). |
1093
1105
  | `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). |
1094
1106
  | `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. |
1107
+ | `RIGOR_LSP_POOL_MIN_BATCH=N` | Fewest buffers an [`rigor lsp`](#rigor-lsp) batch must carry before analysis is dispatched across the worker pool rather than run in-process (default `16`). Lower it if your project's per-file analysis is expensive enough that pooling pays off sooner. |
1095
1108
  | `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`. |
1096
1109
  | `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). |
1097
- | `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. |
1110
+ | `RIGOR_DISABLE_YJIT=1` | Opt out of Rigor's deferred YJIT enablement. Rigor turns YJIT on partway through any long 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. |
1098
1111
  | `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. |
1099
1112
 
1100
1113
  Three further variables (`RIGOR_BUDGET_TRACE`,
@@ -251,7 +251,7 @@ stanza it broke:
251
251
  ```
252
252
  app/presenters/user_presenter.rb:14:1: warning: Method Presenters::User#render performs io.fs.read
253
253
  (File.read), but is declared effect: [] at .rigor.yml effects.envelopes[0], so io.fs.read exceeds
254
- the envelope.
254
+ the envelope. [effect.envelope-exceeded]
255
255
  ```
256
256
 
257
257
  When one method is a deliberate exception, write the narrower envelope on it in RBS — nearest wins,
@@ -19,6 +19,18 @@ Every rule has a two-segment `family.rule` identifier:
19
19
  | `assert` | `assert_type` checks. |
20
20
  | `dump` | `dump_type` notices. |
21
21
 
22
+ Every diagnostic carries its identifier in brackets at the end of the
23
+ line, so the ID you need for `# rigor:disable`, for `disable:` and for
24
+ `severity_profile:` is the one you are already looking at:
25
+
26
+ ```text
27
+ app/user.rb:11:3: error: undefined method `lenght' for "hello" [call.undefined-method]
28
+ ```
29
+
30
+ A diagnostic no rule produced — a parse error, a path error, an
31
+ internal analyzer error — has nothing to suppress and carries no
32
+ bracket.
33
+
22
34
  `rigor explain <rule>` prints the full catalogue entry for any
23
35
  built-in rule ID; `rigor explain` with no argument lists them all.
24
36
 
@@ -63,19 +63,31 @@ syntax-highlighted for a tty — through
63
63
  colorizer; `--no-color` (and the `NO_COLOR` environment
64
64
  variable) disable the colour.
65
65
 
66
- ## `rigor type-of` — one position
66
+ ## `rigor type-of` — exact positions or a whole line
67
67
 
68
- When you only need one expression's type — typically while
69
- chasing down why a diagnostic did or did not fire — query a
70
- single position:
68
+ When you need a few expression types — typically while chasing
69
+ down why a diagnostic did or did not fire — query the exact
70
+ positions together so Rigor loads the project and each source
71
+ file once:
71
72
 
72
73
  ```sh
73
- rigor type-of lib/example.rb:12:8
74
+ rigor type-of lib/example.rb:12:8 lib/example.rb:12:14
74
75
  ```
75
76
 
76
- `--format=json` emits a machine-readable result for tooling.
77
- This is the same query the editor integration answers on
78
- hover.
77
+ Leave off the column to avoid counting it by hand. Rigor prints
78
+ a table of the first 40 expressions starting on the line,
79
+ outermost first at each 1-based column, and marks a truncated
80
+ table:
81
+
82
+ ```sh
83
+ rigor type-of lib/example.rb:12
84
+ ```
85
+
86
+ `--format=json` emits a machine-readable result for tooling: one
87
+ result stays a flat object, while several results use a `results`
88
+ array. Line queries add `line_enumerations` metadata with the
89
+ shown and total expression counts. An exact position is the same
90
+ query the editor integration answers on hover.
79
91
 
80
92
  ## `rigor trace` — watch the inference happen
81
93
 
@@ -49,6 +49,10 @@ each plugin's options — but the families today are:
49
49
  `rigor-factorybot`.
50
50
  - **dry-rb** — `rigor-dry-types`, `rigor-dry-schema`,
51
51
  `rigor-dry-struct`, `rigor-dry-validation`.
52
+ - **FFI** — `rigor-ffi` and sub-plugins (`rigor-ffi-rzmq`,
53
+ `rigor-rbnacl`, `rigor-sassc`, `rigor-ethon`). For plugin authors,
54
+ `rigor-ffi` provides the `ffi_binding_recognizer` class DSL on
55
+ `Rigor::Plugin::Base` to recognize custom binding definitions.
52
56
  - **Other ecosystems** — `rigor-sinatra`, `rigor-hanami`,
53
57
  `rigor-devise`, `rigor-pundit`, `rigor-sidekiq`,
54
58
  `rigor-graphql`, `rigor-statesman`, `rigor-sorbet`,
@@ -2,7 +2,7 @@
2
2
 
3
3
  Rigor bundles a set of **Agent Skills** — structured workflows an AI
4
4
  coding agent (Claude Code and compatible tools) can run on your behalf.
5
- They live in [`skills/`](../../skills/) and are auto-discovered when an
5
+ They live in [`skills/`](../../skills) and are auto-discovered when an
6
6
  agent works inside a project that has Rigor available.
7
7
 
8
8
  Skills are optional. Everything they do, you can do by hand with the
data/docs/manual/11-ci.md CHANGED
@@ -73,7 +73,7 @@ platform reads to annotate the diff directly, instead of leaving them in
73
73
  the job log ([ADR-51](../adr/51-ci-diagnostic-output-formats.md)). These
74
74
  layer on the generic `--format json` stream; they add no new diagnostics,
75
75
  only a platform-native rendering. Ready-to-copy template files live under
76
- [`ci-templates/`](ci-templates/).
76
+ [`ci-templates/`](ci-templates).
77
77
 
78
78
  ### GitHub — inline annotations (the default)
79
79
 
@@ -137,7 +137,7 @@ CI detection that `auto` relies on.
137
137
  | --- | --- |
138
138
  | `rigor check --no-cache` | Run without reading or writing the persistent cache. |
139
139
  | `rigor check --clear-cache` | Delete the cache directory, then run. |
140
- | `rigor check --cache-stats` | Print the on-disk cache inventory when the run finishes. |
140
+ | `rigor check --cache-stats` | Print the on-disk cache inventory when the run finishes (on stderr under every `--format` but `text`, so the document on stdout stays parseable). |
141
141
  | `rigor check --incremental` | Re-analyse only what changed; serve the rest from the incremental snapshot (see below). |
142
142
 
143
143
  There is no config key to disable caching permanently — the
@@ -333,7 +333,7 @@ ones a type actually catches.
333
333
  > [Configuration](03-configuration.md)). A project whose gems live in
334
334
  > the active Ruby's default gem home — the common `rbenv` / `mise`
335
335
  > case with no `--path` set — is invisible to the isolated analyzer by
336
- > design ([ADR-27](https://github.com/rigortype/rigor/blob/master/docs/adr/27-tool-distribution-model.md)):
336
+ > design ([ADR-27](../adr/27-tool-distribution-model.md)):
337
337
  > point Rigor at it with `bundler.bundle_path:`. Until you do, these
338
338
  > holes keep the generic `engine_gap` cause instead of `add_rbs` —
339
339
  > the label is missing, never wrong.
@@ -140,7 +140,7 @@ Four sections, needing four different decisions:
140
140
 
141
141
  | Section | What it means | What to do |
142
142
  | --- | --- | --- |
143
- | **Reachable only from test code** | Live test, no production caller | Work these first |
143
+ | **Reachable only from test code** | Live test, no production caller Rigor can see or suspect | Work these first |
144
144
  | **Candidates** | Nothing reachable names it | Adjudicate — most are still live |
145
145
  | **Cannot decide** | Something can name it at runtime | Read the reason; do not delete from here |
146
146
  | **Namespace-only** | A module wrapping live code | Excluded from candidates; count only |
@@ -209,14 +209,23 @@ skip most of a list quickly:
209
209
 
210
210
  ### Cannot decide: read the reason, do not delete
211
211
 
212
- These were demoted out of `candidates` because something can name the
213
- class at runtime. Each row says what:
212
+ These were demoted because something can name the class at runtime —
213
+ out of `candidates`, or out of **reachable only from test code**. Each
214
+ row says what:
214
215
 
215
216
  ```
216
217
  1 Handlers::Alpha lib/handlers.rb:2
217
218
  constantize on an interpolated string (lib/dispatch.rb:14)
218
219
  ```
219
220
 
221
+ A row demoted out of the test-only section is the second kind, and it
222
+ is the one worth knowing about: a class your specs reference and a
223
+ data file also names — a job in `config/recurring.yml`, a class named
224
+ from a YAML setting — is not a dead production path, because the
225
+ configuration may well be what drives it. The test-only section makes
226
+ a claim about production, so a row Rigor holds evidence against
227
+ belongs here instead, with the file named.
228
+
220
229
  `"Foo".constantize` names `Foo` exactly, so it counts as an ordinary
221
230
  reference and never reaches this section. `"Foo::#{key}".constantize`
222
231
  can only bound the namespace, so everything under `Foo` is demoted. A
@@ -322,5 +331,5 @@ Running it as a **reporting** job is fine, and is the intended way to
322
331
  notice the drift described above: run it on a schedule, publish the
323
332
  JSON, and let a person read the trend.
324
333
 
325
- [adr-102]: https://github.com/rigortype/rigor/blob/master/docs/adr/102-unused-code-reachability-report.md
334
+ [adr-102]: ../adr/102-unused-code-reachability-report.md
326
335
  [issue-363]: https://github.com/rigortype/rigor/issues/363
@@ -313,8 +313,8 @@ digest of your `effects:` block, so an upgrade or a policy edit shows up as a
313
313
 
314
314
  ```yaml
315
315
  # .rigor-effects.yml — generated by `rigor effects update`. Commit it; review its diff.
316
- schema: 1
317
- rigor: "0.3.4"
316
+ schema: 2
317
+ rigor: "0.3.5"
318
318
  vocabulary: 1
319
319
  config_digest: "44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a"
320
320
  methods:
@@ -324,10 +324,12 @@ methods:
324
324
 
325
325
  Two notes on reading the file:
326
326
 
327
- - **Do not read the `unresolved:` arrays.** They are inference-quality metadata,
328
- they are what makes half the file's bytes, and they churn on Rigor upgrades
329
- and on unrelated edits. They exist so `check` can print
330
- `exhaustive not`. The lines a reviewer reads are `effects:` and `declared:`.
327
+ - **`unresolved:` is a count, not a list.** It says how many calls the analyzer
328
+ could not follow, which is why `exhaustive:` is false. The causes themselves
329
+ are not recorded they are inference-quality metadata that churns on Rigor
330
+ upgrades and on unrelated edits so ask for them when you need them:
331
+ `rigor effects explain` names them, including for an `exhaustive → not` row.
332
+ The lines a reviewer reads are `effects:` and `declared:`.
331
333
  - Trivial and synthesised entries are left out, as in the report. `--full`
332
334
  records everything, and produces a much larger and much noisier file.
333
335
 
@@ -398,11 +400,11 @@ CI runs `rigor effects check` and fails, exit 1:
398
400
  Effect drift against .rigor-effects.yml:
399
401
 
400
402
  methods:
401
- Change#init_path + io.fs.write
402
- Change#init_path + nondet.time
403
- Change#init_path exhaustive → not
403
+ Change#init_path + io.fs.write (app/models/change.rb:41)
404
+ Change#init_path + nondet.time (app/models/change.rb:41)
405
+ Change#init_path exhaustive → not (app/models/change.rb:41)
404
406
 
405
- Run `rigor effects update` and commit the result if this change is intended.
407
+ Run `rigor effects explain` to see what caused this, and `rigor effects update` to accept it.
406
408
  ```
407
409
 
408
410
  `+ label` and `- label` are the proven lane; `≤+` / `≤-` the declared one;
@@ -410,6 +412,11 @@ Run `rigor effects update` and commit the result if this change is intended.
410
412
  someone introduced a call Rigor cannot follow; `+symbol` / `-symbol` are methods
411
413
  that appeared or vanished, and a rename is one of each.
412
414
 
415
+ The parenthetical is where the method is defined — the file, and the `def`'s own
416
+ line when the file has one. A method defined by a reopening spans several files
417
+ and the row names them all; a `-symbol` row carries no position, because a method
418
+ this run no longer sees is one it cannot locate.
419
+
413
420
  Before regenerating, ask why:
414
421
 
415
422
  ```sh
@@ -518,7 +525,7 @@ A method that exceeds its bound gets one diagnostic per (method, label) pair, at
518
525
  its `def`, naming the route:
519
526
 
520
527
  ```
521
- app/helpers/application_helper.rb:59:1: warning: Method ApplicationHelper#link_to_principal performs io.fs.read (Dir.glob via IconsHelper#principal_icon → IconsHelper#sprite_icon → IconsHelper#sprite_source → Redmine::Themes::Helper#current_theme → Redmine::Themes.theme → Redmine::Themes.themes → Redmine::Themes.scan_themes), but is declared effect: [] at .rigor.yml effects.envelopes[0], so io.fs.read exceeds the envelope.
528
+ app/helpers/application_helper.rb:59:1: warning: Method ApplicationHelper#link_to_principal performs io.fs.read (Dir.glob via IconsHelper#principal_icon → IconsHelper#sprite_icon → IconsHelper#sprite_source → Redmine::Themes::Helper#current_theme → Redmine::Themes.theme → Redmine::Themes.themes → Redmine::Themes.scan_themes), but is declared effect: [] at .rigor.yml effects.envelopes[0], so io.fs.read exceeds the envelope. [effect.envelope-exceeded]
522
529
  ```
523
530
 
524
531
  **Budget for a big first number.** That one stanza, on Redmine, is **343
@@ -608,7 +615,7 @@ app/serializers/rest/v1/instance_serializer.rb:89:1: warning: Method
608
615
  REST::V1::InstanceSerializer#invites_enabled performs mutate.self
609
616
  (receiver-mutation via UserRole.everyone → UserRole.create! → UserRole#set_position),
610
617
  but is declared effect: [] at .rigor.yml effects.envelopes[0], so mutate.self
611
- exceeds the envelope.
618
+ exceeds the envelope. [effect.envelope-exceeded]
612
619
  ```
613
620
 
614
621
  Rigor walked *through* the database write and reported the ivar assignment beyond
@@ -80,7 +80,7 @@ flag, key, or command that *acts* on it.
80
80
  Cline, …) via `rigor mcp`.
81
81
  11. [Running Rigor in CI](11-ci.md) — a clean CI job, inline
82
82
  PR/MR diagnostics (SARIF / GitHub Actions / GitLab Code
83
- Quality), copy-paste [templates](ci-templates/), and
83
+ Quality), copy-paste [templates](ci-templates), and
84
84
  version pinning.
85
85
  12. [Caching](12-caching.md) — where the cache lives, what
86
86
  invalidates it, and how to clear it.
@@ -94,4 +94,4 @@ flag, key, or command that *acts* on it.
94
94
  - [`docs/types.md`](../types.md) — one-page type-system guide.
95
95
  - [`docs/type-specification/`](../type-specification/README.md)
96
96
  — the normative spec corpus.
97
- - [`docs/adr/`](../adr/) — architecture decision records.
97
+ - [`docs/adr/`](../adr) — architecture decision records.
@@ -17,10 +17,10 @@ plugins:
17
17
  ## What it checks
18
18
 
19
19
  ```text
20
- demo.rb:7:1: info: `UserMailer.welcome` matches mailer action (arity 1..2)
21
- errors_demo.rb:7:1: error: `UserMailer.welcome` expects 1..2 argument(s), got 0
22
- errors_demo.rb:15:1: error: `UserMailer.does_not_exist` is not a defined mailer action (known actions: digest, reset_password, welcome)
23
- app/mailers/user_mailer.rb:14:7: warning: `UserMailer#digest` has no view template under `app/views/user_mailer/`
20
+ demo.rb:7:1: info: `UserMailer.welcome` matches mailer action (arity 1..2) [plugin.actionmailer.mailer-call]
21
+ errors_demo.rb:10:1: error: `UserMailer.welcome` expects 1..2 argument(s), got 0 [plugin.actionmailer.wrong-arity]
22
+ errors_demo.rb:18:1: error: `UserMailer.does_not_exist` is not a defined mailer action (known actions: digest, reset_password, welcome) [plugin.actionmailer.unknown-action]
23
+ app/mailers/user_mailer.rb:26:7: warning: `UserMailer#digest` has no view template under `app/views/user_mailer/` [plugin.actionmailer.missing-view]
24
24
  ```
25
25
 
26
26
  1. **Action existence** — `Mailer.unknown_action(...)` →
@@ -56,6 +56,43 @@ plugins:
56
56
  view_search_paths: ["app/views"] # default
57
57
  ```
58
58
 
59
+ ## What it types
60
+
61
+ Inside a controller, `params`, `request`, `session`, `flash` and
62
+ `cookies` type as their Action Pack classes, and so do the chains
63
+ built on them:
64
+
65
+ ```ruby
66
+ request.post? # bool — and so do get? / put? / patch? / delete? /
67
+ # head? / options? / trace? / link? / unlink? /
68
+ # xhr? / xml_http_request? / ssl? / local? / form_data?
69
+ flash.now # ActionDispatch::Flash::FlashNow
70
+ flash.keep # ActionDispatch::Flash::FlashHash
71
+ flash[:notice] = "hi" # "hi" — an assignment is its right-hand side
72
+ ```
73
+
74
+ Rigor ships **no signature** for these Action Pack classes, on
75
+ purpose: the receiver becomes concrete (so `rigor coverage
76
+ --protection` counts the site) while the method surface stays
77
+ lenient, so `request.headers`, `flash.now[:alert] = x` and anything
78
+ else the framework adds resolve without a diagnostic. A partial
79
+ signature would be worse than none — every member it omitted would
80
+ become a false `call.undefined-method`.
81
+
82
+ The predicates are typed `bool` — the union of `true` and `false` —
83
+ which is both the real contract (every one of them is an `==`,
84
+ `match?` or `include?` in Rails or Rack) and the reason they are safe
85
+ to type at all: a condition that folds needs to prove *one* constant,
86
+ and a union of both never does. `return unless request.post?` and
87
+ `mode = request.get? ? :a : :b` read exactly as they did before.
88
+
89
+ `request.format` is **not** typed. That inertness argument is
90
+ narrower than it looks — it holds for a union of the two boolean
91
+ constants, not for a union of ordinary classes, which is nil-free and
92
+ so *can* fold a condition — and `Mime::NullType`, the value `format`
93
+ returns when there is no format, answers `nil?` with `true` while
94
+ being a real object. Typing it needs a nil-aware answer.
95
+
59
96
  ## Limitations
60
97
 
61
98
  - **Implicit-self helpers only.** `*_path` / `*_url` calls with an
@@ -69,6 +106,30 @@ plugins:
69
106
  knows what `rigor-rails-routes` published, and `permit`
70
107
  validation only what `rigor-activerecord` published — enabling
71
108
  those producers widens what this plugin can check.
109
+ - **`params[:key]` stays untyped.** Inside a controller, `params`
110
+ types as `ActionController::Parameters`, and so does the result
111
+ of every builder method that always returns one — `require`,
112
+ `permit`, `permit!`, `expect`, `slice`, `slice!`, `except`,
113
+ `without`, `extract!`, `merge`, `merge!`, `reverse_merge`,
114
+ `reverse_merge!`, `with_defaults`, `with_defaults!`, `compact`,
115
+ `compact_blank`, `deep_dup` — so a chain built from them keeps a
116
+ concrete receiver throughout. A subscript read is deliberately
117
+ left untyped: `params[:missing]` is `nil` at runtime, and a type
118
+ that says otherwise would let the flow rules fold live
119
+ conditions (`if params[:q]`, `url.nil?`) to a constant and report
120
+ working code. Methods whose result depends on the call — `dig`,
121
+ `fetch`, `compact!`, and the block-less `select` / `reject` /
122
+ `transform_keys` / `transform_values` — are untyped for the same
123
+ reason.
124
+ - **`flash[:key]` and `session[:key]` stay untyped too**, for that
125
+ same reason and measured the same way. Both are leaf reads that
126
+ return whatever was stored — or `nil` for a key that is not set.
127
+ A non-nil type folds `mode = flash[:notice] ? … : …` to one arm and
128
+ reports the live guard after it; a nullable one puts
129
+ `call.possible-nil-receiver` on `note = flash[:notice];
130
+ note.upcase`. Writing through them is unaffected: `flash[:k] = v`
131
+ is `v` because that is what an assignment expression means in Ruby,
132
+ with no rule needed.
72
133
 
73
134
  ## Plugin internals
74
135
 
@@ -18,9 +18,9 @@ Given a job whose `#perform` takes one required and one optional
18
18
  argument (arity `1..2`):
19
19
 
20
20
  ```text
21
- demo.rb:6:1: info: `WelcomeEmailJob.perform_later` matches `#perform` (arity 1..2)
22
- demo.rb:9:1: error: `WelcomeEmailJob.perform_later` expects 1..2 argument(s), got 0
23
- demo.rb:12:1: error: `WelcomeEmailJob.perform_later` expects 1..2 argument(s), got 3
21
+ demo.rb:8:1: info: `WelcomeEmailJob.perform_later` matches `#perform` (arity 1..2) [plugin.activejob.job-call]
22
+ errors_demo.rb:10:1: error: `WelcomeEmailJob.perform_later` expects 1..2 argument(s), got 0 [plugin.activejob.wrong-arity]
23
+ errors_demo.rb:14:1: error: `WelcomeEmailJob.perform_later` expects 1..2 argument(s), got 3 [plugin.activejob.wrong-arity]
24
24
  ```
25
25
 
26
26
  A `*rest` parameter yields an unbounded upper bound (`arity 0+`).