rigortype 0.4.6 → 0.4.8

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 (550) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -2
  3. data/data/core_overlay/bigdecimal.rbs +33 -0
  4. data/data/core_overlay/resolv.rbs +30 -0
  5. data/data/gem_overlay/activesupport/core_ext.rbs +45 -13
  6. data/data/gem_overlay/addressable/addressable.rbs +40 -0
  7. data/data/vendored_gem_sigs/bundler/bundler.rbs +1 -1
  8. data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +4 -3
  9. data/docs/handbook/01-getting-started.md +6 -3
  10. data/docs/handbook/11-sig-gen.md +85 -3
  11. data/docs/handbook/appendix-phpstan.md +4 -4
  12. data/docs/install.md +20 -0
  13. data/docs/manual/01-installation.md +67 -26
  14. data/docs/manual/02-cli-reference.md +455 -113
  15. data/docs/manual/03-configuration.md +76 -5
  16. data/docs/manual/04-diagnostics.md +5 -4
  17. data/docs/manual/06-baseline.md +6 -4
  18. data/docs/manual/08-skills.md +11 -0
  19. data/docs/manual/09-editor-integration.md +21 -2
  20. data/docs/manual/10-mcp-server.md +38 -2
  21. data/docs/manual/12-caching.md +4 -3
  22. data/docs/manual/14-rails-quickstart.md +1 -1
  23. data/docs/manual/19-effect-labels.md +3 -4
  24. data/docs/manual/plugins/rigor-actionpack.md +32 -15
  25. data/docs/manual/plugins/rigor-activesupport-core-ext.md +23 -1
  26. data/docs/manual/plugins/rigor-sorbet.md +17 -3
  27. data/lib/rigor/analysis/baseline.rb +24 -27
  28. data/lib/rigor/analysis/check_rules/always_raises_shield.rb +147 -0
  29. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +5 -6
  30. data/lib/rigor/analysis/check_rules/call_facts.rb +58 -0
  31. data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +2 -2
  32. data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +12 -4
  33. data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +2 -2
  34. data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +85 -13
  35. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +16 -4
  36. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +6 -1
  37. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -8
  38. data/lib/rigor/analysis/check_rules/plugin_api_surface.rb +195 -0
  39. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +41 -12
  40. data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +27 -1
  41. data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +3 -3
  42. data/lib/rigor/analysis/check_rules/replaced_singleton_defs.rb +96 -0
  43. data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
  44. data/lib/rigor/analysis/check_rules/rule_ids.rb +9 -2
  45. data/lib/rigor/analysis/check_rules/rule_walk.rb +9 -7
  46. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +3 -4
  47. data/lib/rigor/analysis/check_rules/special_global_setters.rb +3 -3
  48. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
  49. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +2 -2
  50. data/lib/rigor/analysis/check_rules.rb +720 -141
  51. data/lib/rigor/analysis/crash_signature.rb +45 -3
  52. data/lib/rigor/analysis/dependency_recorder.rb +237 -80
  53. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +14 -3
  54. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +1 -1
  55. data/lib/rigor/analysis/dependency_source_inference/index.rb +6 -8
  56. data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
  57. data/lib/rigor/analysis/edit_replay.rb +93 -6
  58. data/lib/rigor/analysis/fact_store.rb +3 -3
  59. data/lib/rigor/analysis/incremental.rb +11 -1
  60. data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +4 -3
  61. data/lib/rigor/analysis/incremental_run_slot.rb +2 -2
  62. data/lib/rigor/analysis/incremental_session.rb +306 -53
  63. data/lib/rigor/analysis/reachability/plugin_roots.rb +6 -3
  64. data/lib/rigor/analysis/rule_catalog.rb +39 -3
  65. data/lib/rigor/analysis/run_cache_key.rb +27 -3
  66. data/lib/rigor/analysis/run_disclosure_credit.rb +26 -6
  67. data/lib/rigor/analysis/run_stats.rb +2 -2
  68. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -2
  69. data/lib/rigor/analysis/runner/declaration_position.rb +8 -8
  70. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +264 -112
  71. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +4 -9
  72. data/lib/rigor/analysis/runner/pool_coordinator.rb +173 -328
  73. data/lib/rigor/analysis/runner/project_pre_passes.rb +4 -37
  74. data/lib/rigor/analysis/runner/run_level_facts.rb +304 -0
  75. data/lib/rigor/analysis/runner/run_snapshots.rb +44 -54
  76. data/lib/rigor/analysis/runner/worker_payload.rb +36 -0
  77. data/lib/rigor/analysis/runner.rb +298 -109
  78. data/lib/rigor/analysis/self_call_resolution_recorder.rb +62 -23
  79. data/lib/rigor/analysis/template_unit_positions.rb +2 -3
  80. data/lib/rigor/analysis/template_units.rb +9 -1
  81. data/lib/rigor/analysis/worker_session.rb +65 -71
  82. data/lib/rigor/bleeding_edge.rb +22 -10
  83. data/lib/rigor/builtins/imported_refinements.rb +6 -8
  84. data/lib/rigor/builtins/predefined_constant_refinements.rb +1 -1
  85. data/lib/rigor/builtins/regex_refinement.rb +4 -6
  86. data/lib/rigor/cache/descriptor.rb +6 -5
  87. data/lib/rigor/cache/file_digest.rb +39 -31
  88. data/lib/rigor/cache/incremental_snapshot.rb +95 -17
  89. data/lib/rigor/cache/rbs_cache_producer.rb +2 -2
  90. data/lib/rigor/cache/rbs_class_ancestor_table.rb +1 -1
  91. data/lib/rigor/cache/rbs_class_type_param_names.rb +2 -2
  92. data/lib/rigor/cache/rbs_constant_table.rb +1 -1
  93. data/lib/rigor/cache/rbs_descriptor.rb +90 -4
  94. data/lib/rigor/cache/rbs_environment.rb +8 -1
  95. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +2 -2
  96. data/lib/rigor/cache/rbs_known_class_names.rb +1 -1
  97. data/lib/rigor/cache/store.rb +11 -10
  98. data/lib/rigor/ci_detector.rb +1 -1
  99. data/lib/rigor/cli/annotate_command.rb +24 -19
  100. data/lib/rigor/cli/baseline_command.rb +160 -57
  101. data/lib/rigor/cli/catalogue.rb +256 -0
  102. data/lib/rigor/cli/check_command.rb +112 -104
  103. data/lib/rigor/cli/check_runner_factory.rb +2 -2
  104. data/lib/rigor/cli/command.rb +15 -1
  105. data/lib/rigor/cli/coverage_command.rb +21 -8
  106. data/lib/rigor/cli/coverage_mutation.rb +5 -7
  107. data/lib/rigor/cli/coverage_renderer.rb +2 -1
  108. data/lib/rigor/cli/coverage_scan.rb +3 -5
  109. data/lib/rigor/cli/diff_command.rb +25 -20
  110. data/lib/rigor/cli/docs_command.rb +1 -2
  111. data/lib/rigor/cli/doctor_command.rb +12 -12
  112. data/lib/rigor/cli/effects_command.rb +50 -40
  113. data/lib/rigor/cli/effects_diff_renderer.rb +18 -17
  114. data/lib/rigor/cli/effects_explain_renderer.rb +13 -12
  115. data/lib/rigor/cli/effects_renderer.rb +2 -1
  116. data/lib/rigor/cli/effects_snapshot_command.rb +26 -18
  117. data/lib/rigor/cli/explain_command.rb +18 -8
  118. data/lib/rigor/cli/fused_protection_renderer.rb +2 -1
  119. data/lib/rigor/cli/json_document.rb +41 -0
  120. data/lib/rigor/cli/lens_command.rb +29 -10
  121. data/lib/rigor/cli/lsp_command.rb +250 -23
  122. data/lib/rigor/cli/mcp_command.rb +12 -7
  123. data/lib/rigor/cli/mutation_fork_scan.rb +1 -1
  124. data/lib/rigor/cli/mutation_protection_renderer.rb +3 -2
  125. data/lib/rigor/cli/options.rb +40 -1
  126. data/lib/rigor/cli/plugin_command.rb +93 -46
  127. data/lib/rigor/cli/plugins_command.rb +17 -18
  128. data/lib/rigor/cli/plugins_renderer.rb +10 -6
  129. data/lib/rigor/cli/protection_fork_scan.rb +2 -2
  130. data/lib/rigor/cli/protection_renderer.rb +2 -1
  131. data/lib/rigor/cli/show_bleedingedge_command.rb +21 -15
  132. data/lib/rigor/cli/sig_gen_command.rb +92 -27
  133. data/lib/rigor/cli/skill_command.rb +32 -3
  134. data/lib/rigor/cli/skill_deep_probe.rb +1 -1
  135. data/lib/rigor/cli/skill_describe.rb +2 -1
  136. data/lib/rigor/cli/trace_command.rb +14 -9
  137. data/lib/rigor/cli/triage_command.rb +10 -11
  138. data/lib/rigor/cli/triage_renderer.rb +2 -1
  139. data/lib/rigor/cli/type_of_command.rb +16 -11
  140. data/lib/rigor/cli/type_of_project_scope.rb +1 -4
  141. data/lib/rigor/cli/type_of_renderer.rb +4 -1
  142. data/lib/rigor/cli/type_scan_command.rb +14 -9
  143. data/lib/rigor/cli/type_scan_renderer.rb +2 -1
  144. data/lib/rigor/cli/unused_command.rb +27 -17
  145. data/lib/rigor/cli/upgrade_command.rb +5 -0
  146. data/lib/rigor/cli.rb +214 -56
  147. data/lib/rigor/config_audit.rb +33 -19
  148. data/lib/rigor/configuration/severity_profile.rb +14 -5
  149. data/lib/rigor/configuration.rb +104 -35
  150. data/lib/rigor/effects/collector.rb +53 -21
  151. data/lib/rigor/effects/definition_context.rb +0 -2
  152. data/lib/rigor/effects/envelope.rb +1 -1
  153. data/lib/rigor/effects/framework_units.rb +1 -1
  154. data/lib/rigor/effects/mutation_classifier.rb +4 -7
  155. data/lib/rigor/effects/propagator.rb +1 -1
  156. data/lib/rigor/effects/registry.rb +2 -2
  157. data/lib/rigor/effects/snapshot.rb +1 -1
  158. data/lib/rigor/effects/taint_cause.rb +0 -2
  159. data/lib/rigor/effects/unit_scan.rb +9 -13
  160. data/lib/rigor/environment/bundle_sig_discovery.rb +64 -33
  161. data/lib/rigor/environment/class_registry.rb +3 -3
  162. data/lib/rigor/environment/constant_type_cache_holder.rb +5 -0
  163. data/lib/rigor/environment/core_root.rb +77 -0
  164. data/lib/rigor/environment/default_libraries.rb +18 -1
  165. data/lib/rigor/environment/deferred_signature_plan.rb +381 -0
  166. data/lib/rigor/environment/gated_signature_guard.rb +1 -1
  167. data/lib/rigor/environment/gated_signature_plan.rb +5 -2
  168. data/lib/rigor/environment/hkt_registry_holder.rb +6 -0
  169. data/lib/rigor/environment/lockfile_resolver.rb +28 -12
  170. data/lib/rigor/environment/member_consistency/comparator.rb +18 -1
  171. data/lib/rigor/environment/member_consistency.rb +56 -7
  172. data/lib/rigor/environment/missing_gem_constant_index.rb +3 -2
  173. data/lib/rigor/environment/project_path_gems.rb +124 -0
  174. data/lib/rigor/environment/rbs_collection_discovery.rb +123 -8
  175. data/lib/rigor/environment/rbs_coverage_report.rb +12 -11
  176. data/lib/rigor/environment/rbs_hierarchy.rb +12 -15
  177. data/lib/rigor/environment/rbs_loader.rb +772 -110
  178. data/lib/rigor/environment/reflection.rb +7 -7
  179. data/lib/rigor/environment/required_features.rb +0 -3
  180. data/lib/rigor/environment/ruby_library_roots.rb +1 -1
  181. data/lib/rigor/environment/vendored_signature_plan.rb +153 -0
  182. data/lib/rigor/environment.rb +169 -42
  183. data/lib/rigor/flow_contribution/merger.rb +4 -4
  184. data/lib/rigor/inference/acceptance.rb +136 -40
  185. data/lib/rigor/inference/alias_mutation_floor/foreign_locals.rb +117 -0
  186. data/lib/rigor/inference/alias_mutation_floor/memos.rb +85 -0
  187. data/lib/rigor/inference/alias_mutation_floor/reach.rb +119 -0
  188. data/lib/rigor/inference/alias_mutation_floor/site_collector.rb +252 -0
  189. data/lib/rigor/inference/alias_mutation_floor.rb +339 -0
  190. data/lib/rigor/inference/anonymous_meta_class.rb +18 -0
  191. data/lib/rigor/inference/block_auto_splat.rb +1 -1
  192. data/lib/rigor/inference/block_parameter_binder.rb +2 -3
  193. data/lib/rigor/inference/block_run_count.rb +185 -0
  194. data/lib/rigor/inference/budget_trace.rb +8 -2
  195. data/lib/rigor/inference/builtins/array_catalog.rb +2 -2
  196. data/lib/rigor/inference/builtins/exception_catalog.rb +2 -2
  197. data/lib/rigor/inference/builtins/numeric_catalog.rb +2 -5
  198. data/lib/rigor/inference/builtins/random_catalog.rb +2 -2
  199. data/lib/rigor/inference/callee_mutation_chain.rb +88 -77
  200. data/lib/rigor/inference/callee_write_census.rb +815 -0
  201. data/lib/rigor/inference/captured_locals.rb +2 -0
  202. data/lib/rigor/inference/census_foreign_block.rb +354 -0
  203. data/lib/rigor/inference/clause_widening.rb +1 -1
  204. data/lib/rigor/inference/closure_escape_analyzer.rb +11 -4
  205. data/lib/rigor/inference/construction_escape.rb +258 -0
  206. data/lib/rigor/inference/declaration_walk/traversal.rb +1 -1
  207. data/lib/rigor/inference/declaration_walk.rb +4 -0
  208. data/lib/rigor/inference/deep_erasure.rb +39 -7
  209. data/lib/rigor/inference/def_handle.rb +1 -1
  210. data/lib/rigor/inference/def_node_resolver.rb +33 -15
  211. data/lib/rigor/inference/def_return_typer.rb +47 -5
  212. data/lib/rigor/inference/definer_resolution.rb +19 -9
  213. data/lib/rigor/inference/definers_index.rb +162 -0
  214. data/lib/rigor/inference/direct_ivar_writes.rb +300 -0
  215. data/lib/rigor/inference/element_read_widening.rb +49 -3
  216. data/lib/rigor/inference/error_info.rb +16 -0
  217. data/lib/rigor/inference/expression_typer.rb +1559 -320
  218. data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
  219. data/lib/rigor/inference/flow_tracer.rb +31 -14
  220. data/lib/rigor/inference/fork_map.rb +3 -0
  221. data/lib/rigor/inference/fresh_frame_blocks.rb +52 -1
  222. data/lib/rigor/inference/global_write_census.rb +81 -5
  223. data/lib/rigor/inference/global_writer_reach.rb +260 -0
  224. data/lib/rigor/inference/hkt_body.rb +1 -2
  225. data/lib/rigor/inference/hkt_body_parser.rb +3 -5
  226. data/lib/rigor/inference/hkt_reducer.rb +2 -2
  227. data/lib/rigor/inference/hkt_registry.rb +3 -5
  228. data/lib/rigor/inference/in_effect_refinements.rb +7 -0
  229. data/lib/rigor/inference/indexed_narrowing.rb +61 -15
  230. data/lib/rigor/inference/intervening_ivar_reset.rb +26 -4
  231. data/lib/rigor/inference/ivar_writer_census.rb +154 -8
  232. data/lib/rigor/inference/key_presence_guard.rb +51 -24
  233. data/lib/rigor/inference/last_line/self_evidence.rb +30 -0
  234. data/lib/rigor/inference/last_line.rb +5 -2
  235. data/lib/rigor/inference/loop_value_record.rb +66 -14
  236. data/lib/rigor/inference/match_rebinding/frame.rb +57 -0
  237. data/lib/rigor/inference/match_rebinding/yielding_match.rb +101 -0
  238. data/lib/rigor/inference/match_rebinding.rb +28 -5
  239. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +1 -1
  240. data/lib/rigor/inference/method_dispatcher/argument_distribution.rb +40 -0
  241. data/lib/rigor/inference/method_dispatcher/block_folding.rb +44 -30
  242. data/lib/rigor/inference/method_dispatcher/class_keyed_block_params.rb +65 -0
  243. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +3 -3
  244. data/lib/rigor/inference/method_dispatcher/contested_overloads.rb +89 -0
  245. data/lib/rigor/inference/method_dispatcher/core_enumerable_receiver.rb +44 -0
  246. data/lib/rigor/inference/method_dispatcher/declared_constructor.rb +151 -0
  247. data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +1 -1
  248. data/lib/rigor/inference/method_dispatcher/imprecise_argument.rb +2 -2
  249. data/lib/rigor/inference/method_dispatcher/interface_shape.rb +181 -0
  250. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -1
  251. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +1 -1
  252. data/lib/rigor/inference/method_dispatcher/no_match_fallback.rb +54 -0
  253. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +51 -27
  254. data/lib/rigor/inference/method_dispatcher/random_folding.rb +1 -1
  255. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +606 -78
  256. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +1 -1
  257. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +2 -2
  258. data/lib/rigor/inference/method_dispatcher/self_substitute.rb +3 -3
  259. data/lib/rigor/inference/method_dispatcher/set_folding.rb +9 -7
  260. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +467 -84
  261. data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +0 -1
  262. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +50 -17
  263. data/lib/rigor/inference/method_dispatcher/singleton_type_params.rb +104 -0
  264. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -5
  265. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +4 -6
  266. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  267. data/lib/rigor/inference/method_dispatcher.rb +123 -25
  268. data/lib/rigor/inference/method_parameter_binder.rb +136 -56
  269. data/lib/rigor/inference/module_function_state/spellings.rb +2 -2
  270. data/lib/rigor/inference/module_function_state.rb +24 -3
  271. data/lib/rigor/inference/multi_target_binder.rb +2 -2
  272. data/lib/rigor/inference/mutation_rejoin.rb +240 -4
  273. data/lib/rigor/inference/mutation_widening.rb +199 -29
  274. data/lib/rigor/inference/narrowing.rb +238 -50
  275. data/lib/rigor/inference/operand_effects.rb +43 -2
  276. data/lib/rigor/inference/optimistic_origin.rb +134 -9
  277. data/lib/rigor/inference/origin_lookup.rb +1 -1
  278. data/lib/rigor/inference/out_buffer_mutation.rb +72 -0
  279. data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
  280. data/lib/rigor/inference/pre_eval_constants.rb +1 -1
  281. data/lib/rigor/inference/precision_scanner.rb +1 -1
  282. data/lib/rigor/inference/project_method_ownership.rb +30 -9
  283. data/lib/rigor/inference/project_patched_methods.rb +65 -13
  284. data/lib/rigor/inference/project_patched_scanner.rb +120 -25
  285. data/lib/rigor/inference/project_patched_visibility.rb +39 -3
  286. data/lib/rigor/inference/project_yielder.rb +11 -17
  287. data/lib/rigor/inference/rbs_type_translator.rb +68 -15
  288. data/lib/rigor/inference/receiver_alias.rb +20 -0
  289. data/lib/rigor/inference/receiver_writer_call.rb +67 -0
  290. data/lib/rigor/inference/recursion_rebinding.rb +157 -0
  291. data/lib/rigor/inference/refined_dispatch/constructed_class.rb +57 -17
  292. data/lib/rigor/inference/refined_dispatch/own_method.rb +51 -16
  293. data/lib/rigor/inference/refined_dispatch.rb +13 -0
  294. data/lib/rigor/inference/reflective_ivar_write.rb +19 -11
  295. data/lib/rigor/inference/remover_holder_scan.rb +287 -0
  296. data/lib/rigor/inference/remover_narrowing.rb +224 -0
  297. data/lib/rigor/inference/rewrite_mutation.rb +53 -2
  298. data/lib/rigor/inference/scope_indexer/certainty.rb +0 -1
  299. data/lib/rigor/inference/scope_indexer/class_cvars_collector.rb +13 -6
  300. data/lib/rigor/inference/scope_indexer/constant_ivar_value.rb +93 -0
  301. data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +70 -26
  302. data/lib/rigor/inference/scope_indexer/derived_param_value.rb +112 -0
  303. data/lib/rigor/inference/scope_indexer/empty_seed.rb +99 -0
  304. data/lib/rigor/inference/scope_indexer/ivar_contributions.rb +295 -0
  305. data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +136 -9
  306. data/lib/rigor/inference/scope_indexer.rb +2213 -226
  307. data/lib/rigor/inference/self_call_ivar_writes.rb +103 -12
  308. data/lib/rigor/inference/self_preserving_block.rb +87 -0
  309. data/lib/rigor/inference/set_mutation.rb +189 -0
  310. data/lib/rigor/inference/singleton_hook_decline.rb +42 -8
  311. data/lib/rigor/inference/singleton_object_constant.rb +32 -2
  312. data/lib/rigor/inference/statement_evaluator.rb +3268 -358
  313. data/lib/rigor/inference/string_mutation.rb +14 -0
  314. data/lib/rigor/inference/struct_fold_safety.rb +4 -6
  315. data/lib/rigor/inference/synthetic_method_scanner.rb +40 -17
  316. data/lib/rigor/inference/toplevel_def_census.rb +67 -0
  317. data/lib/rigor/inference/unknown_store_widening.rb +103 -4
  318. data/lib/rigor/inference/unthreaded_rebinds.rb +2 -3
  319. data/lib/rigor/inference/untyped_rejoin.rb +1 -1
  320. data/lib/rigor/inference/void_tail_summary.rb +29 -9
  321. data/lib/rigor/language_server/buffer_table.rb +3 -3
  322. data/lib/rigor/language_server/completion_provider.rb +26 -20
  323. data/lib/rigor/language_server/config_warning_announcer.rb +28 -0
  324. data/lib/rigor/language_server/diagnostic_publisher.rb +8 -3
  325. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  326. data/lib/rigor/language_server/folding_range_provider.rb +1 -2
  327. data/lib/rigor/language_server/hover_provider.rb +1 -7
  328. data/lib/rigor/language_server/hover_renderer.rb +6 -8
  329. data/lib/rigor/language_server/project_context.rb +39 -1
  330. data/lib/rigor/language_server/publish_batcher.rb +3 -4
  331. data/lib/rigor/language_server/selection_range_provider.rb +2 -4
  332. data/lib/rigor/language_server/server.rb +7 -3
  333. data/lib/rigor/language_server/signature_help_provider.rb +4 -4
  334. data/lib/rigor/lens/claims/comparison.rb +78 -0
  335. data/lib/rigor/lens/claims/request.rb +131 -0
  336. data/lib/rigor/lens/claims/type_reader.rb +216 -0
  337. data/lib/rigor/lens/claims.rb +208 -0
  338. data/lib/rigor/lens/contract_reader.rb +5 -0
  339. data/lib/rigor/lens/document.rb +1 -1
  340. data/lib/rigor/lens/file_reader.rb +21 -8
  341. data/lib/rigor/lens/plugins.rb +1 -1
  342. data/lib/rigor/lens/run.rb +8 -0
  343. data/lib/rigor/lens.rb +1 -1
  344. data/lib/rigor/library_probe.rb +68 -0
  345. data/lib/rigor/mcp/server.rb +73 -2
  346. data/lib/rigor/mcp.rb +1 -1
  347. data/lib/rigor/memo_audit.rb +282 -0
  348. data/lib/rigor/plugin/access_denied_error.rb +1 -2
  349. data/lib/rigor/plugin/base.rb +15 -2
  350. data/lib/rigor/plugin/bundled_catalog.rb +1 -12
  351. data/lib/rigor/plugin/io_boundary.rb +26 -4
  352. data/lib/rigor/plugin/loader.rb +25 -8
  353. data/lib/rigor/plugin/macro/heredoc_template.rb +7 -8
  354. data/lib/rigor/plugin/macro/trait_registry.rb +6 -9
  355. data/lib/rigor/plugin/manifest.rb +16 -16
  356. data/lib/rigor/plugin/registry.rb +16 -5
  357. data/lib/rigor/plugin/services.rb +2 -3
  358. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -0
  359. data/lib/rigor/plugin/trust_policy.rb +48 -2
  360. data/lib/rigor/plugin/type_node_resolver.rb +2 -4
  361. data/lib/rigor/plugin.rb +6 -8
  362. data/lib/rigor/plugin_gap_advisory.rb +5 -1
  363. data/lib/rigor/project_environment.rb +16 -2
  364. data/lib/rigor/protection/analysis_guard.rb +1 -1
  365. data/lib/rigor/protection/discovery_seed.rb +44 -5
  366. data/lib/rigor/protection/mutation_cache.rb +4 -1
  367. data/lib/rigor/protection/mutator.rb +1 -1
  368. data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
  369. data/lib/rigor/rbs_extended/hkt_directives.rb +6 -10
  370. data/lib/rigor/rbs_extended/reporter.rb +27 -3
  371. data/lib/rigor/rbs_extended.rb +2 -3
  372. data/lib/rigor/reflection/constant_ancestors.rb +81 -22
  373. data/lib/rigor/reflection/declaration_collectors.rb +55 -0
  374. data/lib/rigor/reflection/declarations.rb +58 -0
  375. data/lib/rigor/reflection/project_method.rb +177 -0
  376. data/lib/rigor/reflection.rb +2 -0
  377. data/lib/rigor/rooted_glob.rb +32 -1
  378. data/lib/rigor/run_scoped_slots.rb +38 -0
  379. data/lib/rigor/runtime/jit.rb +1 -1
  380. data/lib/rigor/scope/discovery_index.rb +168 -8
  381. data/lib/rigor/scope/resolution_chain/relevance.rb +14 -1
  382. data/lib/rigor/scope/resolution_chain.rb +901 -54
  383. data/lib/rigor/scope.rb +644 -37
  384. data/lib/rigor/sig_gen/bot_proposal_guard.rb +306 -0
  385. data/lib/rigor/sig_gen/classification.rb +18 -1
  386. data/lib/rigor/sig_gen/effect_annotation.rb +3 -3
  387. data/lib/rigor/sig_gen/generator.rb +588 -66
  388. data/lib/rigor/sig_gen/generic_header_scan.rb +189 -0
  389. data/lib/rigor/sig_gen/inline_declarations.rb +2 -2
  390. data/lib/rigor/sig_gen/layout_index.rb +3 -3
  391. data/lib/rigor/sig_gen/method_candidate.rb +37 -4
  392. data/lib/rigor/sig_gen/observation_collector.rb +14 -7
  393. data/lib/rigor/sig_gen/observed_call.rb +1 -1
  394. data/lib/rigor/sig_gen/path_mapper.rb +6 -0
  395. data/lib/rigor/sig_gen/platform_guarded_files.rb +184 -0
  396. data/lib/rigor/sig_gen/renderer.rb +15 -9
  397. data/lib/rigor/sig_gen/skip_reason_catalog.rb +62 -2
  398. data/lib/rigor/sig_gen/type_name_qualification.rb +69 -0
  399. data/lib/rigor/sig_gen/write_reporter.rb +63 -14
  400. data/lib/rigor/sig_gen/write_result.rb +23 -12
  401. data/lib/rigor/sig_gen/writer.rb +238 -37
  402. data/lib/rigor/sig_gen.rb +1 -0
  403. data/lib/rigor/source/forwardable_delegators.rb +122 -0
  404. data/lib/rigor/source/node_children.rb +13 -0
  405. data/lib/rigor/type/acceptance_router.rb +2 -2
  406. data/lib/rigor/type/accepts_result.rb +1 -1
  407. data/lib/rigor/type/app.rb +4 -6
  408. data/lib/rigor/type/combinator.rb +173 -21
  409. data/lib/rigor/type/constant.rb +4 -5
  410. data/lib/rigor/type/hash_shape.rb +21 -40
  411. data/lib/rigor/type/interface.rb +60 -0
  412. data/lib/rigor/type/union.rb +1 -1
  413. data/lib/rigor/type.rb +1 -0
  414. data/lib/rigor/type_node/identifier.rb +1 -2
  415. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  416. data/lib/rigor/type_node/string_literal.rb +1 -2
  417. data/lib/rigor/type_node/symbol_literal.rb +1 -2
  418. data/lib/rigor/type_node/union.rb +3 -6
  419. data/lib/rigor/type_of_memo/serving.rb +99 -0
  420. data/lib/rigor/type_of_memo/stats.rb +43 -0
  421. data/lib/rigor/type_of_memo.rb +337 -0
  422. data/lib/rigor/typing_state.rb +1094 -0
  423. data/lib/rigor/value_semantics.rb +1 -10
  424. data/lib/rigor/version.rb +1 -1
  425. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +2 -2
  426. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +2 -3
  427. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -1
  428. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +38 -36
  429. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +4 -2
  430. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +6 -4
  431. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +1 -1
  432. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -5
  433. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +0 -1
  434. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +2 -1
  435. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +3 -3
  436. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +115 -36
  437. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +3 -6
  438. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +25 -1
  439. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
  440. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +16 -6
  441. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +4 -2
  442. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +4 -3
  443. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +2 -1
  444. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +3 -2
  445. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +2 -2
  446. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +21 -31
  447. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +10 -16
  448. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +132 -33
  449. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +20 -6
  450. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +7 -4
  451. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +54 -17
  452. data/plugins/rigor-activerecord/sig/active_record/relation.rbs +19 -5
  453. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +3 -4
  454. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +1 -1
  455. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +11 -14
  456. data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +1 -1
  457. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +1 -1
  458. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/symbolize_keys.rb +102 -0
  459. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +30 -9
  460. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +55 -13
  461. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +15 -21
  462. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +1 -6
  463. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +3 -3
  464. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +1 -3
  465. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +6 -6
  466. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +5 -6
  467. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +1 -1
  468. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/params_shape.rb +2 -3
  469. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -8
  470. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +0 -1
  471. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
  472. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +10 -18
  473. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +0 -1
  474. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +0 -2
  475. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +8 -7
  476. data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +0 -2
  477. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +6 -7
  478. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +12 -2
  479. data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +2 -4
  480. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +2 -5
  481. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +8 -2
  482. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +6 -6
  483. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
  484. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +3 -8
  485. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +2 -3
  486. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -8
  487. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +200 -4
  488. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -3
  489. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +1 -1
  490. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +4 -11
  491. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +4 -3
  492. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +2 -1
  493. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +5 -5
  494. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +2 -2
  495. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +3 -4
  496. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +6 -8
  497. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +3 -2
  498. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +4 -5
  499. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +6 -7
  500. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +210 -3
  501. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +146 -7
  502. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +2 -2
  503. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +52 -34
  504. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +97 -24
  505. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +2 -3
  506. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +16 -16
  507. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +4 -8
  508. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +4 -4
  509. data/sig/rigor/analysis/baseline.rbs +0 -3
  510. data/sig/rigor/analysis/check_rules/rule_walk.rbs +6 -0
  511. data/sig/rigor/analysis/fact_store.rbs +2 -2
  512. data/sig/rigor/analysis/reachability/scan_cache.rbs +1 -1
  513. data/sig/rigor/cache/descriptor.rbs +14 -0
  514. data/sig/rigor/cli/diff_command.rbs +1 -1
  515. data/sig/rigor/cli/explain_command.rbs +1 -1
  516. data/sig/rigor/effects/registry.rbs +2 -2
  517. data/sig/rigor/environment.rbs +4 -1
  518. data/sig/rigor/inference.rbs +4 -1
  519. data/sig/rigor/plugin/additional_initializer.rbs +0 -5
  520. data/sig/rigor/plugin/base.rbs +53 -18
  521. data/sig/rigor/plugin/effect_attribution.rbs +3 -3
  522. data/sig/rigor/plugin/effect_edge.rbs +1 -1
  523. data/sig/rigor/plugin/effect_entry_points.rbs +3 -3
  524. data/sig/rigor/plugin/interfaces.rbs +51 -0
  525. data/sig/rigor/plugin/io_boundary.rbs +3 -3
  526. data/sig/rigor/plugin/manifest.rbs +9 -9
  527. data/sig/rigor/plugin/node_context.rbs +21 -0
  528. data/sig/rigor/plugin/protocol_contract.rbs +3 -7
  529. data/sig/rigor/reflection.rbs +37 -0
  530. data/sig/rigor/scope.rbs +43 -4
  531. data/sig/rigor/trinary.rbs +1 -0
  532. data/sig/rigor/type.rbs +9 -3
  533. data/sig/rigor.rbs +3 -2
  534. data/skills/rigor-ask/SKILL.md +1 -0
  535. data/skills/rigor-plugin-author/SKILL.md +4 -4
  536. data/skills/rigor-plugin-author/references/02-walker-and-types.md +5 -5
  537. data/skills/rigor-plugin-review/SKILL.md +3 -3
  538. data/skills/rigor-project-init/references/01-detect.md +1 -0
  539. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +17 -2
  540. data/skills/rigor-project-init/references/06-agent-contract.md +4 -4
  541. data/skills/rigor-rbs-contract/SKILL.md +147 -0
  542. data/skills/rigor-rbs-contract/references/01-finding-candidates.md +212 -0
  543. data/skills/rigor-rbs-contract/references/02-contract-rules.md +157 -0
  544. data/skills/rigor-rbs-contract/references/03-target-own-code.md +90 -0
  545. data/skills/rigor-rbs-contract/references/04-target-gem-rbs-collection.md +210 -0
  546. data/skills/rigor-rbs-contract/references/05-declaration-mutants.md +182 -0
  547. data/skills/rigor-rbs-setup/SKILL.md +3 -1
  548. data/skills/rigor-type-oracle/SKILL.md +26 -1
  549. data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +4 -3
  550. metadata +82 -3
@@ -24,7 +24,7 @@ Relative paths in a config file resolve against that file's own directory, with
24
24
  `rigor` runs from. They match only when you run `rigor` from the directory that holds the config file, which is
25
25
  the usual case; with `--config path/to/.rigor.yml` from elsewhere, give these three as absolute paths.
26
26
  A leading `~/` (or a bare `~`) in a path setting expands to your home directory; `~name` is an ordinary
27
- directory called `~name`, not another user's home. This covers `paths:`, `signature_paths:`, `test_paths:`,
27
+ directory called `~name`, not another user's home. This covers `paths:`, `signature_paths:`, `core_root:`, `test_paths:`,
28
28
  `pre_eval:`, `plugins_io.allowed_paths:`, `includes:`, `bundler.bundle_path:`, `bundler.lockfile:` and
29
29
  `rbs_collection.lockfile:`. It does not apply to `cache.path:`, `baseline:` or `effects.snapshot.path:`, which stay
30
30
  literal and relative to the working directory.
@@ -78,9 +78,39 @@ cache:
78
78
  | --- | --- | --- | --- |
79
79
  | `libraries` | Array | `[]` | Standard-library / gem names whose bundled RBS to load. |
80
80
  | `signature_paths` | Array | `nil` | Extra directories of `.rbs` files. Relative entries resolve against the config file's directory. |
81
+ | `core_root` | String | `nil` | A directory of core `.rbs` files loaded **instead of** the `rbs` gem's own core, such as one [rbs-shifter](https://github.com/rigortype/rbs-shifter) generates for an older CRuby. A relative path resolves against the config file's directory. `nil` loads the gem's core. See [Analysing for an older Ruby](#analysing-for-an-older-ruby). |
81
82
  | `pre_eval` | Array | `[]` | Files (or globs) walked before per-file analysis, to register project monkey-patches and publish their top-level constants project-wide. The walker does not follow `require` / `require_relative`, so list the files in load order: a method declared in two files takes the last-listed one, and a file that requires a later-listed one is still read in list order. `type-of`, `type-scan`, `trace`, `annotate` and `sig-gen` read the patches too, so they type a patched method as `rigor check` does. |
82
83
  | `plugins` | Array | `[]` | Plugins to activate — see [Using plugins](07-plugins.md). |
83
84
 
85
+ #### Analysing for an older Ruby
86
+
87
+ Rigor runs on Ruby 4.0, and its bundled core signatures describe Ruby 4.0. A project that runs on an older
88
+ Ruby can call a method that version does not have, and Rigor accepts the call. To check against the older
89
+ API, point `core_root:` at a core signature directory written for it:
90
+
91
+ ```yaml
92
+ target_ruby: "3.3"
93
+ core_root: vendor/rbs-shifter/3.3/core
94
+ ```
95
+
96
+ `core_root:` replaces the core, while `signature_paths:` only adds to it. A method that a signature directory
97
+ omits stays declared by the default core, so `signature_paths:` cannot remove one. With the core above,
98
+ `[1, 2].rfind { ... }` reports `call.undefined-method` and `" text ".strip(" ")` reports `call.wrong-arity`.
99
+ Project signatures, plugins and `libraries:` still load on top of it. `target_ruby:` sets the syntax Rigor
100
+ parses; it does not choose a core.
101
+
102
+ A `core_root:` that does not exist, is not a directory, holds no `.rbs` file, lies inside (or contains) one of
103
+ your `signature_paths:`, or does not build an RBS environment stops the run with exit code 64. A core class
104
+ whose definition fails to build because of the core's own files is reported as an
105
+ `rbs.coverage.definition-build-failed` error:
106
+
107
+ ```
108
+ rigor: core_root: "/path/to/vendor/rbs-shifter/3.3/core" does not exist
109
+ ```
110
+
111
+ The cache keys on the directory and on the contents of its files, so switching cores or regenerating one
112
+ invalidates cached results, including in a running language server.
113
+
84
114
  ### Signature generation
85
115
 
86
116
  | Key | Type | Default | Meaning |
@@ -144,7 +174,10 @@ is left alone, other than the reserved run-level rows (`rbs.coverage.…`,
144
174
  those rows are the intended keys and are never flagged, while a bare plugin
145
175
  rule, `all`, or a `plugin.`-qualified key for an unconfigured plugin is. The same findings appear in the
146
176
  `--format=json` payload under `config_warnings` (each tagged with a
147
- `kind`), so CI can assert on them.
177
+ `kind`), so CI can assert on them. The [language server](09-editor-integration.md)
178
+ shows each one as a `window/showMessage` warning, and every
179
+ [MCP tool](10-mcp-server.md#tool-reference) that takes a `config`
180
+ argument includes them in its result.
148
181
 
149
182
  ### Diagnostics
150
183
 
@@ -194,12 +227,50 @@ project Ruby's gem home without running your toolchain. If `rigor check`'s
194
227
  `--stats` shows gems whose RBS it could not find, point it at the bundle
195
228
  explicitly with `bundler.bundle_path:`, or supply signatures another way:
196
229
  `rbs collection install` (auto-discovered) or `dependencies.source_inference:`.
197
- | `rbs_collection.auto_detect` | Boolean | `true` | Auto-discover `rbs_collection.lock.yaml`. |
230
+ | `rbs_collection.auto_detect` | Boolean | `true` | Auto-discover `rbs_collection.lock.yaml`. A `source: { type: stdlib }` entry loads that stdlib library, as `libraries:` would. An entry is not loaded for a gem your `Gemfile.lock` sources from a `path:` directory that is at or under one of your `paths:` entries, or contains one, and is not left out by `exclude:` (the gem is code Rigor analyses, which your `sig/` declares). |
198
231
  | `rbs_collection.lockfile` | String | `nil` | Explicit `rbs_collection.lock.yaml` path. |
199
232
  | `dependencies.source_inference` | Array | `[]` | Per-gem source-inference modes (ADR-10). |
200
233
  | `dependencies.budget_per_gem` | Integer | `5000` | Per-gem source-walk cap, counted in **method definitions** (not time): the walker stops harvesting a gem's catalog once it reaches this many `def`s, then emits `dynamic.dependency-source.budget-exceeded` and degrades the rest to `Dynamic[top]`. Range 1250–20000. |
201
234
  | `dependencies.budget_overrun_strategy` | String | `"walker_cap"` | What happens to calls on a gem that hit `budget_per_gem` (ADR-10 § 5b). `walker_cap` (default) lets methods past the cap fall through to the engine's normal user-class resolution. `dependency_silence` instead resolves any call on a class from a budget-exceeded gem to `Dynamic[top]`, silencing `call.undefined-method` on that gem's unrecorded surface at the cost of weaker static checking there. |
202
235
 
236
+ #### Running Rigor in a gem_rbs_collection `_test/` directory
237
+
238
+ To check a signature you are writing for
239
+ [`ruby/gem_rbs_collection`](https://github.com/ruby/gem_rbs_collection), run
240
+ Rigor in the gem's `gems/<name>/<version>/_test/` directory after the
241
+ collection's own test setup:
242
+
243
+ 1. Run `bin/test gems/<name>/<version>` once from the collection root. It
244
+ writes `rbs_collection.yaml` and `rbs_collection.lock.yaml` into `_test/`
245
+ and installs the gem and its dependencies into `_test/.gem_rbs_collection/`,
246
+ with your working tree linked in as a `local` source.
247
+ 2. Put a `.rigor.yml` in `_test/`:
248
+
249
+ ```yaml
250
+ paths: ["."]
251
+ signature_paths: ["."]
252
+ ```
253
+
254
+ This is the shape of the `Steepfile` that `bin/test` writes (`check "."`,
255
+ `signature "."`). Rigor finds the lockfile and loads the gem and its
256
+ dependencies from it, and `signature_paths: ["."]` loads the `.rbs` files in
257
+ `_test/` itself. Without that line, the types those test signatures declare
258
+ are absent and calls into them are not checked.
259
+
260
+ `signature_paths: [".."]` is also safe. It loads the gem from your working
261
+ tree, and Rigor skips the lockfile's copy of the same gem and version, so the
262
+ gem is not loaded twice. Use it when the lockfile's copy is not your working
263
+ tree, for example when `rbs_collection.yaml` names a `git` source instead of
264
+ the collection's `gems/` directory.
265
+ 3. Run `rigor check` in `_test/`.
266
+
267
+ Rigor skips every `_`-prefixed directory inside a collected gem, such as another
268
+ gem's `_test/` fixtures, as `rbs` does. Rigor does not read `manifest.yaml`
269
+ or a gemspec to find dependencies. Without a lockfile it loads none of them and
270
+ reports `rbs.coverage.collection-not-installed` (`:info`), which tells you to run
271
+ the `bin/test` setup, or `rbs collection install` where an `rbs_collection.yaml`
272
+ is present.
273
+
203
274
  ### Execution
204
275
 
205
276
  | Key | Type | Default | Meaning |
@@ -237,8 +308,8 @@ config with no `effects:` key at all behave as `effects: {}` — collection,
237
308
  `effects.check`, and everything else on this page all turn on at their
238
309
  defaults. It only fills an *absence*: write `effects: false` and you stay
239
310
  opted out regardless, and any `effects:` block you do write is left exactly
240
- as written. It previews what becomes the default at **v0.4.0**
241
- ([ADR-103](../adr/103-effect-labels.md) § WD15).
311
+ as written. It previews what becomes the default at **v0.5.0**
312
+ ([ADR-103](../adr/103-effect-labels.md) § WD15, moved by § WD18).
242
313
 
243
314
  #### Entry-point presets
244
315
 
@@ -67,7 +67,7 @@ carries no `documentation_url`.
67
67
  | <a id="rule-flow-return-in-ensure"></a>`flow.return-in-ensure` | An explicit `return` inside an `ensure` clause — it overrides the method's in-flight return value and silently swallows any in-flight exception. A `return` in a nested `def`, lambda, or `define_method` block inside the `ensure` does not fire (it exits that inner frame). | high |
68
68
  | <a id="rule-flow-shadowed-rescue-clause"></a>`flow.shadowed-rescue-clause` | A `rescue` clause can never run because an earlier clause of the same chain already catches a superclass (or the same class) of every exception class it names. | high |
69
69
  | <a id="rule-def-return-type-mismatch"></a>`def.return-type-mismatch` | The method body's result violates its declared RBS return type. | medium |
70
- | <a id="rule-def-ivar-write-mismatch"></a>`def.ivar-write-mismatch` | An instance variable is written with a type disagreeing with its first write, unless its RBS declaration (`@x: T` or an `attr_*` member) admits both writes. With `rigor-actionpack` or `rigor-actionmailer` loaded, writes in two different actions of a controller or mailer are not compared, since Rails runs one action per instance. A write in a private or protected helper, `initialize` or the same action still is, and so is one in a callback target or `helper_method` that a macro in the same file names. A public method named only by a macro in another file or a concern, or reached through a lambda callback, counts as an action, and so does an action another action calls. | high |
70
+ | <a id="rule-def-ivar-write-mismatch"></a>`def.ivar-write-mismatch` | An instance variable is written with a type disagreeing with its first write, unless its RBS declaration (`@x: T` or an `attr_*` member) admits both writes. A write in a block whose `self` is provably another object (`Other.new.instance_eval { @x = 1 }`, `Class.new { … }`, `X.class_eval { … }`, a `class << obj` body) sets that object's variable and is not compared. With `rigor-actionpack` or `rigor-actionmailer` loaded, writes in two different actions of a controller or mailer are not compared, since Rails runs one action per instance. A write in a private or protected helper, `initialize` or the same action still is, and so is one in a callback target or `helper_method` that a macro in the same file names. A public method named only by a macro in another file or a concern, or reached through a lambda callback, counts as an action, and so does an action another action calls. | high |
71
71
  | <a id="rule-def-method-visibility-mismatch"></a>`def.method-visibility-mismatch` | An explicit-receiver call reaches a private method. | high |
72
72
  | <a id="rule-def-override-visibility-reduced"></a>`def.override-visibility-reduced` | An override reduces the visibility it inherits from a project-defined ancestor. | high |
73
73
  | <a id="rule-def-override-return-widened"></a>`def.override-return-widened` | An override's declared return type widens the inherited return (covariance). | high |
@@ -75,9 +75,10 @@ carries no `documentation_url`.
75
75
  | <a id="rule-global-write-type-mismatch"></a>`global.write-type-mismatch` | A special global is assigned a literal its setter rejects, so the write raises `TypeError` every time it runs. `$/`, `$-0`, `$,` and `$\` take only a String or nil (`$/ = 1` and `$/ = /x/` report). `$;` and `$-F` take a String, a Regexp, nil, or an object with `to_str`. `$~` takes a MatchData or nil. `$0` and `$PROGRAM_NAME` take a String or an object with `to_str`, so `$0 = nil` and `$0 = :name` report. `$.` takes an Integer, a Float, or an object with `to_int`, so `$. = "3"` reports and `$. = 3r` does not. `$-i` takes a String, nil, false, or an object with `to_str`. `$stdout`, `$>` and `$stderr` take anything that responds to `write`: `$stdout = 1` reports, `$stdout = StringIO.new` does not. Ruby's setter decides, not the global's RBS type. Only a value written as a literal is judged — a number, a string, a symbol, an array, a hash or a regexp literal (interpolated or not), or `nil`, `true` or `false` — so a variable, a method call or a constant never reports, whatever its type. A literal stays silent when your program defines the method the setter asks for (`write`, `to_str`, `to_int`) or a `method_missing` / `respond_to_missing?` / `respond_to?` anywhere, in any spelling and on any class — Rigor cannot always tell which objects such a definition reaches, so it does not try — or when a top-level `include` / `extend` or an ancestor's `include` names a module Rigor has no RBS for. For `$stdout`, `$>` and `$stderr` it also stays silent where a `using` is in effect for a refinement that may add `write`. `$/`, `$-0`, `$,`, `$\` and `$~` accept only their classes, so none of that silences them. A write to `$stdin` is never checked, and a special any file aliases (`alias $stdout $out`, including a `pre_eval:` file) is exempt. `warning` under `lenient`. | high |
76
76
  | <a id="rule-global-readonly-write"></a>`global.readonly-write` | A read-only special global is written — `$!`, `$$`, `$?`, `$<`, `$FILENAME`, `$*`, `$:` / `$LOAD_PATH` / `$-I`, `$"` / `$LOADED_FEATURES`, `$-W`, `$-p`, `$-l` or `$-a` — which raises `NameError` whatever the value. Covers `$g = value`, `$g += value` and a multiple-assignment target. `$LOAD_PATH ||= []` never writes and `$LOAD_PATH << dir` is a method call, so neither fires. A special any file aliases (`alias $! $err`) is exempt. An error in every profile. | high |
77
77
  | <a id="rule-static-value-use-void"></a>`static.value-use.void` | A value recovered from an author-declared `-> void` return is used in value context (an assignment right-hand side, a call receiver, or a call argument). Off by default; reaches a run only through the `use-of-void-value` bleeding-edge feature (ADR-100). A bare-statement `void` call and a legitimate `top` value both stay silent. | high |
78
+ | <a id="rule-plugin-api-surface"></a>`plugin.api-surface` | Plugin code (inside a class that inherits `Rigor::Plugin::Base`) calls a member that a `Rigor::Plugin` interface (`_Scope`, `_Environment`, `_Services`, `_FactStore`, `_Configuration`) does not declare, on a value that interface types: a hook's `scope`, `scope.environment`, and so on. The engine object answers the call today, but it is outside the plugin API Rigor promises to keep, so call a member the interface declares (`sig/rigor/plugin/interfaces.rbs`) or a public namespace instead. Off by default; reaches a run through the `plugin-api-surface` bleeding-edge feature (ADR-128), or a `severity_overrides:` key naming `plugin.api-surface` itself (a bare `plugin` key does not reach it). Calls of members every object has (`nil?`, `respond_to?`) and values that lost the interface type (a helper parameter with no signature, a join with an untyped value) stay silent; a call a `respond_to?` guard protects still reports. | high |
78
79
  | <a id="rule-effect-envelope-exceeded"></a>`effect.envelope-exceeded` | A method performs an effect its declared envelope does not admit — its proven effect labels (its own body plus everything it calls) are not covered by the `%a{pure}` or `%a{rigor:v1:effect …}` bound written on it or on its class. Opt-in twice over: it needs an `effects:` block in `.rigor.yml` and an envelope you wrote. Positioned at the Ruby `def`. Unproven ("and possibly more") effects never fire, and `mutate.local` is tolerated by every envelope. | high |
79
80
  | <a id="rule-effect-liskov-widened"></a>`effect.liskov-widened` | An override escapes the envelope written on the method it overrides. A `PgRepo` is usable wherever a `Repo` is, so a `%a{rigor:v1:effect io.db}` on `Repo#find` binds `PgRepo#find` too: an implementation may be purer than the bound it inherits, never less pure. Either what the override *does* exceeds the inherited bound, or the envelope the override *declares for itself* is wider than it. Both sides must be authored — nothing fires unless someone wrote an envelope on the ancestor — and only subclassing counts, not `include`. Positioned at the override's `def`. Needs an `effects:` block. | high |
80
- | <a id="rule-effect-unknown-label"></a>`effect.unknown-label` | An effect declaration names a label the registry does not know — a typo in an envelope (`%a{rigor:v1:effect io.bd}`), or a member of `effects.tolerated:`. The whole tag then reads as unbounded, so the declaration quietly stops doing anything; this says so. Positioned at the declaration: the `.rbs` line, the `.rb` line for an rbs-inline annotation, or `.rigor.yml` for a config value. `# rigor:disable` comments are not read out of `.rbs` or `.rigor.yml`, so use `disable:` or the baseline there. Only fires where the spelling is evidently meant to be a label (close to a known one, next to a known one, dotted, or retired) — a word nothing resembles stays silent, because you may be opening your own vocabulary. Needs an `effects:` block. | high |
81
+ | <a id="rule-effect-unknown-label"></a>`effect.unknown-label` | An effect declaration names a label the registry does not know — a typo in an envelope (`%a{rigor:v1:effect io.bd}`), or a member of `effects.tolerated:`. The whole tag then reads as unbounded, so the declaration quietly stops doing anything; this says so. Positioned at the declaration: the `.rbs` line, the `.rb` line for an rbs-inline annotation, or the loaded configuration file (`.rigor.yml` when none was loaded) for a config value. `# rigor:disable` comments are not read out of `.rbs` or `.rigor.yml`, so use `disable:` or the baseline there. Only fires where the spelling is evidently meant to be a label (close to a known one, next to a known one, dotted, or retired) — a word nothing resembles stays silent, because you may be opening your own vocabulary. Needs an `effects:` block. | high |
81
82
  | <a id="rule-effect-annotations-unchecked"></a>`effect.annotations-unchecked` | Your signatures carry `%a{pure}` / `%a{rigor:v1:effect …}` but `.rigor.yml` has no `effects:` block, so nothing checks them. One `:info` per run, positioned at the first annotation. An annotation never turns effect collection on by itself — that would make one line in one file more expensive for every run — so this is how it tells you instead. Add `effects: {}` to opt in, or `disable:` it to keep the annotations documentary. Reads both annotation lanes — `sig/*.rbs` and rbs-inline comments — on every run, a warm `--incremental` with nothing changed included. | — |
82
83
  | <a id="rule-rbs-contradicting-signature"></a>`rbs.contradicting-signature` | A method is declared both in your `sig/` and by an inline `# @rbs` / `#:` annotation, and the two provably contradict — no value or call satisfies both: in the return or a parameter every call must pass, the two types share no value (`::String` against `::Integer`). Only absolutely written Ruby core or stdlib classes count: a module such as `Comparable`, a class your project declares, a gem's class, or a relative name like plain `String` never does, or two keyword-free declarations accept positional counts that cannot meet, or one requires a keyword the other cannot take in any form. Overloads are paired by correspondence, not order. Also fires when a member-level `%a{rigor:v1:return: …}` / `%a{rigor:v1:param: …}` refinement shares no value with the type its own member declares. Positioned at the `.rbs` member; Rigor reads that declaration. A stale generated signature is the usual cause: regenerate it, or fix the annotation. Two declarations where one refines the other merge to the more precise one without a word, and a pair Rigor cannot rank drops the inline side with a `source-rbs-annotation-not-honoured` `:info` instead ([Precedence](plugins/rigor-rbs-inline.md#precedence)). An error in every profile. | high |
83
84
  | <a id="rule-suppression-unknown-rule"></a>`suppression.unknown-rule` | A `# rigor:disable[-file]` comment names a rule that does not exist (typically a typo), so the suppression silently does nothing. `plugin.`-prefixed tokens are never flagged. | high |
@@ -130,7 +131,7 @@ re-stamps it for the run. Three profiles, set with the
130
131
  | --- | --- |
131
132
  | `lenient` | Only proven diagnostics are errors; uncertain ones drop to `warning` / `info`. For incremental adoption on legacy code. |
132
133
  | `balanced` *(default)* | Most rules `error`; `dump.type` `info`; uncertain rules `warning`. |
133
- | `strict` | Nearly every rule is an `error`. The exceptions: `call.self-undefined-method` and `static.value-use.void` stay `off` (both opt-in only), `flow.unreachable-clause` stays `warning` pending its false-positive gate, and the three `suppression.*` rules stay `warning` — a stale suppression comment is worth telling you about, but it is not a reason to fail a build. CI-friendly. |
134
+ | `strict` | Nearly every rule is an `error`. The exceptions: `call.self-undefined-method`, `static.value-use.void` and `plugin.api-surface` stay `off` (all three opt-in only), `flow.unreachable-clause` stays `warning` pending its false-positive gate, and the three `suppression.*` rules stay `warning` — a stale suppression comment is worth telling you about, but it is not a reason to fail a build. CI-friendly. |
134
135
 
135
136
  Under `balanced`, the rules that do **not** emit as `error`
136
137
  are:
@@ -139,7 +140,7 @@ are:
139
140
  | --- | --- |
140
141
  | `warning` | `call.unresolved-toplevel`, `def.ivar-write-mismatch`, `def.return-type-mismatch`, `def.override-visibility-reduced`, `def.override-return-widened`, `def.override-param-narrowed`, `flow.unreachable-branch`, `flow.always-truthy-condition`, `flow.dead-assignment`, `flow.duplicate-hash-key`, `flow.return-in-ensure`, `flow.shadowed-rescue-clause`, `suppression.unknown-rule`, `suppression.empty`, `suppression.unknown-marker`, `effect.envelope-exceeded`, `effect.liskov-widened` |
141
142
  | `info` | `flow.unreachable-clause`, `dump.type`, `effect.unknown-label`, `effect.annotations-unchecked` |
142
- | `off` | `call.self-undefined-method`, `static.value-use.void` |
143
+ | `off` | `call.self-undefined-method`, `static.value-use.void`, `plugin.api-surface` |
143
144
 
144
145
  Everything else emits as `error`. For one rule under all three
145
146
  profiles, `rigor explain <rule>` prints `Authored severity:` and
@@ -77,7 +77,9 @@ a diagnostic another layer has already suppressed.
77
77
  | `rigor baseline drift` | Show how buckets have moved — `--only=over` for buckets that grew, `reducible` for ones you have already improved past, `cleared` for empty ones. |
78
78
  | `rigor baseline prune` | Drop buckets that match nothing any more. `--dry-run` previews. |
79
79
 
80
- `generate` and `regenerate` analyse with your loaded configuration unchanged except that `baseline:` is off, so options such as `bleeding_edge`, `target_ruby` or `severity_overrides` shape the recorded set exactly as they shape `rigor check`.
80
+ `generate` and `regenerate` analyse with your loaded configuration unchanged except that `baseline:` is off, so options such as `bleeding_edge`, `target_ruby` or `severity_overrides` shape the recorded set exactly as they shape `rigor check`. They analyse the configured `paths:` only: a positional path argument is rejected with a usage error (exit `64`), since a baseline over a subset of files would silently drop the entries of every other file. The other three subcommands reject a positional path the same way.
81
+
82
+ The summary counts only the diagnostics the written buckets cover. A diagnostic with no rule — a **parse error** in a file Rigor could not parse, a missing `paths:` entry, or an internal error — or no path cannot be keyed, so it never enters the baseline. The command prints a second line saying how many, why, and which files. The exit status stays 0, but the next `rigor check` with `baseline:` wired still reports them and exits non-zero until you fix those files, exclude them, or remove a bad `paths:` entry.
81
83
 
82
84
  `generate` and `regenerate` take `--match-mode=rule` (the
83
85
  default — one bucket per file × rule) or `--match-mode=message`
@@ -103,11 +105,11 @@ snapshot in the repository and compares against it explicitly:
103
105
 
104
106
  ```sh
105
107
  # Once: capture the current diagnostic surface.
106
- rigor check --format=json > rigor.baseline.json
107
- git add rigor.baseline.json
108
+ rigor check --format=json > rigor-check.json
109
+ git add rigor-check.json
108
110
 
109
111
  # Per PR: compare against the committed snapshot.
110
- rigor diff rigor.baseline.json
112
+ rigor diff rigor-check.json
111
113
  ```
112
114
 
113
115
  [`rigor diff`](02-cli-reference.md#rigor-diff) prints a `+ NEW`
@@ -80,6 +80,17 @@ is not repeated here.)
80
80
  minimal hand-RBS residual, under a double gate (the site becomes
81
81
  protected *and* `rigor check` gains no new diagnostic). See
82
82
  [Type-protection coverage](15-type-protection-coverage.md).
83
+ - **`rigor-rbs-contract`** — audits, repairs, and extends RBS
84
+ declarations as *contracts*: a gem's own `sig/`, an application's
85
+ `sig/`, or an entry contributed to `ruby/gem_rbs_collection`. It finds
86
+ where a declaration and the code disagree (`rigor check`'s
87
+ `def.return-type-mismatch`, `rigor sig-gen --diff`, use-site checks,
88
+ the library's documentation), changes a declaration only on cited
89
+ evidence, keeps a declared `void` unless the library documents a
90
+ value, and proves each change with a use-site test. On your own code
91
+ it asks you the contract questions the evidence leaves open; on a gem
92
+ you do not own, the gem's documentation decides. See
93
+ [ADR-131](../adr/131-declarations-are-contracts.md).
83
94
  - **`rigor-baseline-reduce`** — works an existing `.rigor-baseline.yml`
84
95
  down rule by rule. It prioritises with `rigor triage`, classifies each
85
96
  site as a real bug / safe stylistic finding / false positive, and
@@ -25,6 +25,7 @@ design + capability matrix lives in
25
25
  | `textDocument/documentSymbol` | Outline tree from Prism AST: `class` / `module` / `def` with nesting. |
26
26
  | `workspace/didChangeWatchedFiles` | Invalidates the per-session `Environment` + `Cache::Store` cache so saved files repropagate. |
27
27
  | `workspace/didChangeConfiguration` | Same — re-reads `.rigor.yml` / `Gemfile.lock` etc. |
28
+ | `window/showMessage` | One warning per [configuration warning](03-configuration.md) — the lines `rigor check` prints on STDERR, such as a key that has no effect or a `disable:` token that names no rule. Sent on `initialized`, and again after a save only when re-reading `.rigor.yml` changed the set of warnings. |
28
29
 
29
30
  ## Prerequisites
30
31
 
@@ -34,6 +35,14 @@ install channels in [Installing Rigor](01-installation.md) provide it;
34
35
  `mise` is the recommended one because its shims make `rigor` available
35
36
  to GUI-launched editors that do not inherit your shell environment.
36
37
 
38
+ The [VS Code extension](../../editors/vscode/README.md) also finds a
39
+ mise or asdf shim when `rigor` is not on `PATH`. When it finds no
40
+ `rigor` at all, it offers to install a **managed Rigor** into its own
41
+ storage with a Ruby 4.0 already on the machine, and uses it as its
42
+ last resort; a project that pins `rigortype` in `Gemfile.lock`,
43
+ `mise.toml` or `.tool-versions` is never served by it. See the
44
+ extension README's *Server discovery*.
45
+
37
46
  Do **not** add `rigortype` to your project's `Gemfile`. Rigor is a
38
47
  tool, not a library — installing it standalone keeps its Ruby version
39
48
  and its dependencies out of your application's resolution. See
@@ -51,8 +60,10 @@ rigor lsp [--transport=stdio] [--log=PATH] [--config=PATH]
51
60
 
52
61
  - `--transport=stdio` — default; only value accepted in v1. TCP /
53
62
  Unix-socket transports are queued.
54
- - `--log=PATH` — wire log + server debug to a file. Without it,
55
- server-side logs go to stderr.
63
+ - `--log=PATH` — append the wire log (each message in and out, one
64
+ line each) and the server's debug output from the start of the loop to
65
+ a file. Output before the loop starts still goes to stderr. Without
66
+ it, no wire log is written and server-side logs go to stderr.
56
67
  - `--config=PATH` — mirrors `rigor check --config=PATH`. Without
57
68
  it, `Configuration.discover` walks `.rigor.yml` / `.rigor.dist.yml`
58
69
  from the project root.
@@ -194,6 +205,14 @@ For a legacy bundler-based install, swap the connection list to
194
205
  there, the LSP should too. If `rigor check` fails, fix that
195
206
  first.
196
207
 
208
+ **A rule still fires although `.rigor.yml` turns it off.**
209
+
210
+ - Look for a `rigor:` warning the server showed when it started (or
211
+ after the save that changed `.rigor.yml`): a misspelt key such as
212
+ `undefined-method: "off"` at the top level, or a `disable:` token
213
+ that names no rule, has no effect, and the server says so once.
214
+ The same warnings appear on STDERR from `rigor check`.
215
+
197
216
  **Completion popup is empty.**
198
217
 
199
218
  - Completion only fires on a node with a known type. Receivers
@@ -193,6 +193,28 @@ All messages except `notifications/*` require a response.
193
193
 
194
194
  ## Tool reference
195
195
 
196
+ Every tool that takes a `config` argument reports the configuration's
197
+ warnings — the ones `rigor check` prints on STDERR, such as a key that
198
+ has no effect or a `disable:` token that names no rule. `rigor_check`
199
+ carries them in its own document; every other such tool appends a
200
+ second `text` content block holding one JSON object, after the tool's
201
+ own output, which is left unchanged:
202
+
203
+ ```json
204
+ {"config_warnings": [{"kind": "unknown_key", "message": "`undefined-method` is not a recognized configuration key; it has no effect.", "key": "undefined-method", "suggestion": null}]}
205
+ ```
206
+
207
+ Each entry has the shape of `rigor check --format json`'s
208
+ `config_warnings` entries. There is no such block when the
209
+ configuration has no warnings, or when it fails to load (the tool
210
+ reports that error itself).
211
+
212
+ `rigor_type_of` and `rigor_explain` print JSON whose top-level shape changes
213
+ in the next minor release (`{ "schema_version": 1, "results" | "rules" |
214
+ "skip_reasons": [...] }`). Until then, a successful call carries a final
215
+ `text` content block, after any `config_warnings` block, with the one-line
216
+ notice the CLI prints on STDERR. The tool's own document is left unchanged.
217
+
196
218
  ### rigor_check
197
219
 
198
220
  Analyze one or more Ruby files or directories for type errors,
@@ -222,9 +244,16 @@ undefined methods, arity mismatches, and nil-receiver risks.
222
244
  }
223
245
  ```
224
246
 
247
+ When `.rigor.yml` has [configuration warnings](03-configuration.md)
248
+ (a key or a `disable:` token that has no effect), the document carries
249
+ them under `config_warnings`, as `rigor check --format json` does.
250
+
225
251
  `isError` is `false` when the run completes normally, even if
226
- diagnostics were found. `isError: true` signals a configuration or
227
- runtime failure (bad path, unreadable config, etc.).
252
+ diagnostics were found. `isError: true` signals a usage or runtime
253
+ failure: an unreadable `config`, or a `rigor_coverage` call whose
254
+ `paths` leave nothing to measure. A missing path given to `rigor_check`
255
+ is not one of these: it is reported in the diagnostics, and the call
256
+ returns `isError: false`.
228
257
 
229
258
  **Typical agent use:** call `rigor_check` on the files an agent is
230
259
  about to edit, record the baseline, apply the edit, call again, diff
@@ -484,6 +513,13 @@ as `Constant` / `Nominal` / shaped / refined (precise) versus
484
513
 
485
514
  **Returns:** JSON — the same as `rigor coverage --format json`.
486
515
 
516
+ A `paths` entry that is missing, or is not a Ruby file, is reported as
517
+ `rigor coverage` reports it. Beside files that are measured it is skipped. The result
518
+ then carries a second text block with the CLI's stderr, unfiltered: path
519
+ warnings such as `missing.rb:1:1: warning: no such file or directory (skipped)`,
520
+ and any other warnings. When no entry holds a Ruby file, the call is
521
+ `isError: true` and the block says so.
522
+
487
523
  ```json
488
524
  {
489
525
  "summary": {
@@ -190,9 +190,10 @@ unchanged tree, so it cannot see an edit that fails to re-check a
190
190
  file it affects. `--verify-incremental-edits=N` also edits `N`
191
191
  files one at a time, in memory and not on disk, and compares each
192
192
  warm recheck with a full analysis of the edited tree. Each file is
193
- edited twice: once emptied, and once reduced to its `class` /
194
- `module` declarations with every body dropped. It replays only
195
- those two removals, never an addition or any other edit.
193
+ edited up to four times: once emptied, once reduced to its
194
+ `class` / `module` declarations with every body dropped, and up
195
+ to twice with one statement of a `class` / `module` body removed
196
+ and the rest kept. It replays only removals, never an addition.
196
197
 
197
198
  Rigor is also precise about *what* an edit changed. Re-checking a
198
199
  file that many others depend on — a base class, a widely-used
@@ -61,7 +61,7 @@ mise use gem:rigortype
61
61
  ```
62
62
 
63
63
  `mise use` records the chosen versions in a `mise.toml` in the
64
- current directory and installs them in one step. Verify:
64
+ current directory and installs them in one step. Rails projects with Steep or `rbs_rails` list `rbs` in the bundle, and the `rbs` that `gem:rigortype` puts on `PATH` can shadow it; see the [install guide's note on `rbs` shadowing](../install.md#case-a--mise-is-available-recommended) before running `bundle exec rbs`. Verify:
65
65
 
66
66
  ```sh
67
67
  rigor --version
@@ -108,10 +108,9 @@ annotations and no block gets told so, once per run:
108
108
  sig/slug.rbs:2:1: info: Effect annotations (`%a{pure}` / `%a{rigor:v1:effect …}`) are present in your project's signatures, but `.rigor.yml` carries no `effects:` block, so effect collection never runs and nothing checks them — they are documentation, not a contract. Add `effects: {}` to have Rigor prove what your methods do and check these bounds against it (ADR-103); an annotation alone never turns collection on, because that would make one line in one signature file more expensive for every run of the project.
109
109
  ```
110
110
 
111
- From **v0.4.0** collection is the default: a config with no `effects:` key at
112
- all behaves as `effects: {}`, and `effects: false` is how you opt out. On the
113
- `0.3.x` line you can preview that with
114
- `bleeding_edge: [effects-on-by-default]`.
111
+ From **v0.5.0** collection is the default: a config with no `effects:` key at
112
+ all behaves as `effects: {}`, and `effects: false` is how you opt out. Until
113
+ then you can preview that with `bleeding_edge: [effects-on-by-default]`.
115
114
 
116
115
  One exception to all of this: `rigor effects` — the bare report — runs under an
117
116
  implicit empty block whether or not you configured one, so you can look before
@@ -397,21 +397,38 @@ diff — not a policy scoped to `app/views/**/*`. See
397
397
  knows what `rigor-rails-routes` published, and `permit`
398
398
  validation only what `rigor-activerecord` published — enabling
399
399
  those producers widens what this plugin can check.
400
- - **`params[:key]` stays untyped.** Inside a controller, `params`
401
- types as `ActionController::Parameters`, and so does the result
402
- of every builder method that always returns one — `require`,
403
- `permit`, `permit!`, `expect`, `slice`, `slice!`, `except`,
404
- `without`, `extract!`, `merge`, `merge!`, `reverse_merge`,
405
- `reverse_merge!`, `with_defaults`, `with_defaults!`, `compact`,
406
- `compact_blank`, `deep_dup` — so a chain built from them keeps a
407
- concrete receiver throughout. A subscript read is deliberately
408
- left untyped: `params[:missing]` is `nil` at runtime, and a type
409
- that says otherwise would let the flow rules fold live
410
- conditions (`if params[:q]`, `url.nil?`) to a constant and report
411
- working code. Methods whose result depends on the call — `dig`,
412
- `fetch`, `compact!`, and the block-less `select` / `reject` /
413
- `transform_keys` / `transform_values` — are untyped for the same
414
- reason.
400
+ - **`params[:key]` is a gradual carrier.** Inside a controller,
401
+ `params` types as `ActionController::Parameters`, and so does the
402
+ result of every builder method that always returns one — `permit`,
403
+ `permit!`, `slice`, `slice!`, `except`, `without`, `extract!`,
404
+ `merge`, `merge!`, `reverse_merge`, `reverse_merge!`,
405
+ `with_defaults`, `with_defaults!`, `compact`, `compact_blank`,
406
+ `deep_dup` — when the receiver is `params` itself. A carrier
407
+ (`params[:tags]`) may hold an Array or a String at runtime, so a
408
+ builder call on one answers the carrier too, except `permit` and
409
+ `permit!`, which no core class defines and which answer `Parameters`.
410
+ `require`
411
+ and `expect` are different: Rails returns the value itself, which
412
+ can be a scalar (`params.require(:id).to_sym`), so they answer the
413
+ same `Dynamic[ActionController::Parameters]` carrier (#2947). A
414
+ `permit` or `permit!` after one (`params.require(:user).permit(:name)`)
415
+ still types as `Parameters`; other chain methods off a carrier stay
416
+ the carrier. A subscript read answers
417
+ `Dynamic[ActionController::Parameters | nil]` (#2430): the static
418
+ facet is the nilable `Parameters`, so `params[:user][:name]` and
419
+ `params[:user].permit(:name)` keep dispatching on that facet, and
420
+ the `Dynamic` wrapper lets the value cross a declared boundary such
421
+ as a `String` parameter, as a scalar param does at runtime. The
422
+ facet is nilable rather than the non-nil nominal on purpose:
423
+ `params[:missing]` is `nil` at runtime, and a non-nil type would
424
+ let the flow rules fold live conditions (`if params[:q]`,
425
+ `url.nil?`) to a constant and report working code. Methods on the
426
+ carrier outside the chain (`params[:q].to_s`) answer `Dynamic[top]`,
427
+ because the engine dispatches a `Dynamic` receiver without projecting
428
+ its facet: a loss of precision, not a false positive. Methods whose
429
+ result depends on the call — `dig`, `fetch`, `compact!`, and the
430
+ block-less `select` / `reject` / `transform_keys` /
431
+ `transform_values` — are untyped for the same reason.
415
432
  - **`flash[:key]` and `session[:key]` stay untyped too**, for that
416
433
  same reason and measured the same way. Both are leaf reads that
417
434
  return whatever was stored — or `nil` for a key that is not set.
@@ -32,6 +32,17 @@ path, no vendoring, no `signature_paths:` wiring.
32
32
  > surface. When it is loaded, the auto overlay stands down so the two
33
33
  > never double-declare.
34
34
 
35
+ If the project also runs `rbs collection install` for `activesupport`,
36
+ some methods are declared twice — `Object#acts_like?`,
37
+ `Object#html_safe?`, `Time#to_fs` among them. Rigor merges the two:
38
+ the plugin's signatures are tried first and the collection's still
39
+ apply, so a call either one accepts type-checks. When your own `sig/`
40
+ or inline RBS declares one of the plugin's methods, yours is used
41
+ instead and the plugin's stands down; the run reports one
42
+ `rbs.coverage.plugin-signature-stood-down` info row for the plugin's
43
+ file, naming those methods and the file whose declaration is used for
44
+ each.
45
+
35
46
  ## What it covers
36
47
 
37
48
  Roughly the top ~40 selectors plus their close neighbours, across:
@@ -42,6 +53,9 @@ Roughly the top ~40 selectors plus their close neighbours, across:
42
53
  `TrueClass` / `FalseClass`).
43
54
  - **Integer / Float** — Duration multipliers (`#days`, `#hours`,
44
55
  `#minutes`, …) and Bytes multipliers (`#megabytes`, `#gigabytes`, …).
56
+ - **Integer / Float / BigDecimal** — `#to_fs` / `#to_formatted_s`
57
+ (`NumericWithFormat`: a Symbol format, an Integer radix or a String
58
+ format, and the no-argument `to_s` form).
45
59
  - **String** — inflections (`#underscore`, `#camelize`, `#classify`,
46
60
  `#constantize`, `#pluralize`, …), filters (`#squish`, `#truncate`),
47
61
  `#html_safe`, `#starts_with?` / `#ends_with?` (also on **Symbol**),
@@ -55,7 +69,15 @@ Roughly the top ~40 selectors plus their close neighbours, across:
55
69
  - **Hash** — `#symbolize_keys` / `#stringify_keys` (+ deep / bang) and
56
70
  their `#to_options` alias, `#deep_merge`, `#reverse_merge` /
57
71
  `#with_defaults` (+ bang), `#with_indifferent_access`, `#except!`,
58
- `#extract!`.
72
+ `#extract!`. `#symbolize_keys` / `#to_options` map the key type per
73
+ member, as ActiveSupport does (`key.to_sym rescue key`): a `String` or
74
+ `Symbol` key reads as `Symbol`, and every other key (an `Integer`, a
75
+ `Float`, a project class) reads as `untyped`, because a mixin or a
76
+ project file can give any class a `to_sym`. So
77
+ `Hash[String, Integer]` gives `Hash[Symbol, Integer]` and
78
+ `Hash[Integer, Integer]` gives `Hash[untyped, Integer]`. Without the
79
+ plugin, on the activesupport overlay alone, the key reads `untyped`.
80
+ The bang forms answer the receiver's own type.
59
81
  - **Range** — `#overlaps?`, `#to_fs` / `#to_formatted_s`.
60
82
  - **Enumerable** — `#index_by`, `#index_with`, `#pluck`, `#exclude?`.
61
83
 
@@ -61,9 +61,23 @@ The plugin also types the annotation DSL's own expressions
61
61
  `T::Struct` subclass still needs the subclass's own `extend T::Sig`,
62
62
  matching the runtime) through a bundled
63
63
  `sorbet-runtime` RBS surface — so the sig DSL no longer reads as
64
- `Dynamic[top]` on a Sorbet codebase. What it does not model:
65
- `T::Struct` does not synthesise `prop`-declared readers (a
66
- `Doc#name` accessor still reads opaque), `include T::Props` does
64
+ `Dynamic[top]` on a Sorbet codebase.
65
+
66
+ On a `T::Struct`, `T::InexactStruct` or `T::ImmutableStruct`
67
+ subclass, `prop :name, Type` and `const :name, Type` at the top
68
+ level of the class body give `obj.name` the translated `Type`
69
+ (`T.nilable(String)` reads `String?`; a form outside the table
70
+ reads `Dynamic[top]`), and `prop` also answers `obj.name = v`.
71
+ A relative constant in `Type` resolves from the class body's
72
+ namespaces outward, and one that names no known class reads
73
+ `Dynamic[top]`.
74
+ A `const` has no writer, so `obj.name = v` still reports.
75
+ Subclasses inherit the accessors, a `def` of the same name in
76
+ the class body wins over the macro, and a `prop` nested under an
77
+ `if` or a block reads `Dynamic[top]`. A `prop` call in a class
78
+ outside the struct family defines nothing.
79
+
80
+ What it does not model: `include T::Props` does
67
81
  not confer the `ClassMethods` surface (only the `< T::Struct`
68
82
  subclass path does), and the deeper type-member machinery
69
83
  (`T.attached_class` variance, `type_member` bounds) is not
@@ -97,6 +97,13 @@ module Rigor
97
97
  new(buckets, project_root: project_root)
98
98
  end
99
99
 
100
+ # The diagnostics a baseline cannot address: no rule (a Prism parse error, a path or internal error) or no path.
101
+ # `#filter` passes these through and `from_diagnostics` leaves them out of every bucket; this is the one
102
+ # definition of both.
103
+ def unkeyable(diagnostics)
104
+ diagnostics.reject { |d| d.qualified_rule && d.path }
105
+ end
106
+
100
107
  private
101
108
 
102
109
  def parse_loaded(raw, path:, project_root:)
@@ -138,17 +145,12 @@ module Rigor
138
145
  raise LoadError, "#{path}: ignored[#{index}] `message:` is not a valid Regexp: #{e.message}"
139
146
  end
140
147
 
141
- # Returns Hash{[file, rule, regex_or_nil] => Array<Diagnostic>}.
142
- # In message mode, each unique message gets its own bucket;
143
- # in rule mode, every diagnostic for a (file, rule) pair
144
- # contributes to a single bucket regardless of message.
145
- # File paths are normalised to relative before keying.
148
+ # Groups diagnostics by [file, rule, regex_or_nil]. In message mode, each unique message gets its own
149
+ # bucket; in rule mode, every diagnostic for a (file, rule) pair contributes to a single bucket regardless
150
+ # of message. File paths are normalised to relative before keying.
146
151
  def group_for_baseline(diagnostics, match_mode, project_root)
147
152
  root = Pathname.new(project_root)
148
- diagnostics.each_with_object({}) do |diag, into|
149
- next if diag.qualified_rule.nil?
150
- next if diag.path.nil?
151
-
153
+ (diagnostics - unkeyable(diagnostics)).each_with_object({}) do |diag, into|
152
154
  rel = relative_path(diag.path, root)
153
155
  key = case match_mode
154
156
  when :rule
@@ -160,18 +162,18 @@ module Rigor
160
162
  end
161
163
  end
162
164
 
165
+ def relative_path(path, root_pathname)
166
+ Pathname.new(path).relative_path_from(root_pathname).to_s
167
+ rescue ArgumentError
168
+ path # different drive letter on Windows or non-child path
169
+ end
170
+
163
171
  # Generates a Regexp source string for the baseline row. The string is `Regexp.escape`d so the YAML
164
172
  # round-trip produces a regex that matches the literal message. Users hand-editing the row can replace
165
173
  # the escaped form with a pattern.
166
174
  def message_pattern_for(message)
167
175
  Regexp.new(Regexp.escape(message.to_s))
168
176
  end
169
-
170
- def relative_path(path, root_pathname)
171
- Pathname.new(path).relative_path_from(root_pathname).to_s
172
- rescue ArgumentError
173
- path # different drive letter on Windows or non-child path
174
- end
175
177
  end
176
178
 
177
179
  class LoadError < StandardError; end
@@ -181,11 +183,8 @@ module Rigor
181
183
  def initialize(buckets, project_root: Dir.pwd)
182
184
  @project_root = Pathname.new(project_root)
183
185
  @buckets = buckets.freeze
184
- # For each (file, qualified_rule) pair, two arrays:
185
- # - rule-ID rows (message_regex == nil)
186
- # - message-pattern rows (message_regex != nil)
187
- # The matcher walks message-pattern rows first (tighter match takes precedence); diagnostics that
188
- # don't match any message row fall through to the rule-ID row if one exists.
186
+ # Buckets grouped by (file, rule). `claim_bucket_for` splits each group into message-pattern rows, tried
187
+ # first as the tighter match, and the rule-ID row (message_regex == nil) as the fallback.
189
188
  @by_pair = buckets.group_by { |b| [b.file, b.rule] }.freeze
190
189
  freeze
191
190
  end
@@ -216,7 +215,7 @@ module Rigor
216
215
 
217
216
  # Diagnostics that lacked a rule or a path bypass the baseline entirely (the baseline can't address
218
217
  # them).
219
- unkeyable = diagnostics.reject { |d| d.qualified_rule && d.path }
218
+ unkeyable = self.class.unkeyable(diagnostics)
220
219
  [surfaced + unkeyable, silenced_count]
221
220
  end
222
221
 
@@ -225,7 +224,7 @@ module Rigor
225
224
  # - `:within` — `actual <= count` (silenced by the filter).
226
225
  # - `:over` — `actual > count` (over threshold; surfaced in the regular `rigor check` output).
227
226
  # - `:cleared` — `actual == 0` (the bucket can be pruned).
228
- # - `:reducible` — `0 < actual < count` (the bucket's count can be tightened; future `regenerate` slice 5
227
+ # - `:reducible` — `0 < actual < count` (the bucket's count can be tightened; `rigor baseline regenerate`
229
228
  # handles this).
230
229
  DriftRow = Struct.new(:bucket, :actual_count, :status, keyword_init: true) do
231
230
  def delta
@@ -254,8 +253,8 @@ module Rigor
254
253
  end
255
254
  end
256
255
 
257
- # Returns a new Baseline with the given buckets dropped. Used by `rigor baseline prune` (slice 2) to
258
- # remove cleared buckets (`actual == 0`) from the on-disk file.
256
+ # Returns a new Baseline with the given buckets dropped. Used by `rigor baseline prune` to remove cleared
257
+ # buckets (`actual == 0`) from the on-disk file.
259
258
  def without(buckets_to_drop)
260
259
  dropset = buckets_to_drop.to_set
261
260
  self.class.new(buckets.reject { |b| dropset.include?(b) }, project_root: @project_root)
@@ -311,9 +310,7 @@ module Rigor
311
310
  # over rule-ID rows because they're more specific. A diagnostic that matches no row goes into a
312
311
  # synthetic "no-bucket" bin keyed by (file, rule).
313
312
  bins = {}
314
- diagnostics.each do |diag|
315
- next if diag.qualified_rule.nil? || diag.path.nil?
316
-
313
+ (diagnostics - self.class.unkeyable(diagnostics)).each do |diag|
317
314
  bucket = claim_bucket_for(diag)
318
315
  key = if bucket
319
316
  [bucket.file, bucket.rule,