rigortype 0.4.6 → 0.4.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 (417) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/data/gem_overlay/activesupport/core_ext.rbs +16 -13
  4. data/data/vendored_gem_sigs/bundler/bundler.rbs +1 -1
  5. data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +4 -3
  6. data/docs/handbook/01-getting-started.md +5 -2
  7. data/docs/handbook/11-sig-gen.md +50 -3
  8. data/docs/handbook/appendix-phpstan.md +4 -4
  9. data/docs/manual/02-cli-reference.md +152 -26
  10. data/docs/manual/03-configuration.md +36 -3
  11. data/docs/manual/06-baseline.md +6 -4
  12. data/docs/manual/09-editor-integration.md +13 -2
  13. data/docs/manual/10-mcp-server.md +38 -2
  14. data/docs/manual/12-caching.md +4 -3
  15. data/docs/manual/plugins/rigor-actionpack.md +19 -11
  16. data/docs/manual/plugins/rigor-activesupport-core-ext.md +20 -1
  17. data/lib/rigor/analysis/baseline.rb +24 -27
  18. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +5 -6
  19. data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +2 -2
  20. data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +4 -4
  21. data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +2 -2
  22. data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +85 -13
  23. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +3 -2
  24. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +0 -1
  25. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -8
  26. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +1 -1
  27. data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +3 -3
  28. data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
  29. data/lib/rigor/analysis/check_rules/rule_ids.rb +1 -1
  30. data/lib/rigor/analysis/check_rules/rule_walk.rb +9 -7
  31. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +3 -4
  32. data/lib/rigor/analysis/check_rules/special_global_setters.rb +3 -3
  33. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
  34. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +1 -1
  35. data/lib/rigor/analysis/check_rules.rb +124 -44
  36. data/lib/rigor/analysis/crash_signature.rb +2 -2
  37. data/lib/rigor/analysis/dependency_recorder.rb +4 -4
  38. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +14 -3
  39. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +1 -1
  40. data/lib/rigor/analysis/dependency_source_inference/index.rb +6 -8
  41. data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
  42. data/lib/rigor/analysis/edit_replay.rb +93 -6
  43. data/lib/rigor/analysis/fact_store.rb +3 -3
  44. data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +2 -2
  45. data/lib/rigor/analysis/incremental_run_slot.rb +2 -2
  46. data/lib/rigor/analysis/incremental_session.rb +27 -16
  47. data/lib/rigor/analysis/rule_catalog.rb +2 -2
  48. data/lib/rigor/analysis/run_stats.rb +2 -2
  49. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +2 -2
  50. data/lib/rigor/analysis/runner/declaration_position.rb +1 -1
  51. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +193 -93
  52. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +1 -1
  53. data/lib/rigor/analysis/runner/pool_coordinator.rb +147 -323
  54. data/lib/rigor/analysis/runner/project_pre_passes.rb +3 -3
  55. data/lib/rigor/analysis/runner/run_level_facts.rb +304 -0
  56. data/lib/rigor/analysis/runner/run_snapshots.rb +44 -54
  57. data/lib/rigor/analysis/runner/worker_payload.rb +36 -0
  58. data/lib/rigor/analysis/runner.rb +77 -71
  59. data/lib/rigor/analysis/self_call_resolution_recorder.rb +1 -1
  60. data/lib/rigor/analysis/template_unit_positions.rb +2 -3
  61. data/lib/rigor/analysis/template_units.rb +9 -1
  62. data/lib/rigor/analysis/worker_session.rb +24 -31
  63. data/lib/rigor/bleeding_edge.rb +3 -6
  64. data/lib/rigor/builtins/imported_refinements.rb +6 -8
  65. data/lib/rigor/builtins/predefined_constant_refinements.rb +1 -1
  66. data/lib/rigor/builtins/regex_refinement.rb +4 -6
  67. data/lib/rigor/cache/descriptor.rb +6 -5
  68. data/lib/rigor/cache/incremental_snapshot.rb +31 -12
  69. data/lib/rigor/cache/rbs_cache_producer.rb +2 -2
  70. data/lib/rigor/cache/rbs_class_ancestor_table.rb +1 -1
  71. data/lib/rigor/cache/rbs_class_type_param_names.rb +2 -2
  72. data/lib/rigor/cache/rbs_constant_table.rb +1 -1
  73. data/lib/rigor/cache/rbs_descriptor.rb +54 -3
  74. data/lib/rigor/cache/rbs_environment.rb +4 -1
  75. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +2 -2
  76. data/lib/rigor/cache/rbs_known_class_names.rb +1 -1
  77. data/lib/rigor/cache/store.rb +11 -10
  78. data/lib/rigor/ci_detector.rb +1 -1
  79. data/lib/rigor/cli/annotate_command.rb +24 -19
  80. data/lib/rigor/cli/baseline_command.rb +145 -55
  81. data/lib/rigor/cli/catalogue.rb +256 -0
  82. data/lib/rigor/cli/check_command.rb +108 -102
  83. data/lib/rigor/cli/check_runner_factory.rb +2 -2
  84. data/lib/rigor/cli/command.rb +15 -1
  85. data/lib/rigor/cli/coverage_command.rb +21 -8
  86. data/lib/rigor/cli/coverage_mutation.rb +5 -7
  87. data/lib/rigor/cli/coverage_renderer.rb +2 -1
  88. data/lib/rigor/cli/coverage_scan.rb +3 -5
  89. data/lib/rigor/cli/diff_command.rb +25 -20
  90. data/lib/rigor/cli/docs_command.rb +1 -2
  91. data/lib/rigor/cli/doctor_command.rb +12 -12
  92. data/lib/rigor/cli/effects_command.rb +50 -40
  93. data/lib/rigor/cli/effects_diff_renderer.rb +18 -17
  94. data/lib/rigor/cli/effects_explain_renderer.rb +13 -12
  95. data/lib/rigor/cli/effects_renderer.rb +2 -1
  96. data/lib/rigor/cli/effects_snapshot_command.rb +18 -17
  97. data/lib/rigor/cli/explain_command.rb +18 -8
  98. data/lib/rigor/cli/fused_protection_renderer.rb +2 -1
  99. data/lib/rigor/cli/json_document.rb +41 -0
  100. data/lib/rigor/cli/lens_command.rb +12 -8
  101. data/lib/rigor/cli/lsp_command.rb +250 -23
  102. data/lib/rigor/cli/mcp_command.rb +12 -7
  103. data/lib/rigor/cli/mutation_fork_scan.rb +1 -1
  104. data/lib/rigor/cli/mutation_protection_renderer.rb +3 -2
  105. data/lib/rigor/cli/options.rb +40 -1
  106. data/lib/rigor/cli/plugin_command.rb +93 -46
  107. data/lib/rigor/cli/plugins_command.rb +17 -18
  108. data/lib/rigor/cli/plugins_renderer.rb +10 -6
  109. data/lib/rigor/cli/protection_fork_scan.rb +2 -2
  110. data/lib/rigor/cli/protection_renderer.rb +2 -1
  111. data/lib/rigor/cli/show_bleedingedge_command.rb +21 -15
  112. data/lib/rigor/cli/sig_gen_command.rb +84 -27
  113. data/lib/rigor/cli/skill_command.rb +32 -3
  114. data/lib/rigor/cli/skill_deep_probe.rb +1 -1
  115. data/lib/rigor/cli/skill_describe.rb +1 -1
  116. data/lib/rigor/cli/trace_command.rb +14 -9
  117. data/lib/rigor/cli/triage_command.rb +10 -11
  118. data/lib/rigor/cli/triage_renderer.rb +2 -1
  119. data/lib/rigor/cli/type_of_command.rb +16 -11
  120. data/lib/rigor/cli/type_of_project_scope.rb +1 -4
  121. data/lib/rigor/cli/type_of_renderer.rb +4 -1
  122. data/lib/rigor/cli/type_scan_command.rb +14 -9
  123. data/lib/rigor/cli/type_scan_renderer.rb +2 -1
  124. data/lib/rigor/cli/unused_command.rb +27 -17
  125. data/lib/rigor/cli/upgrade_command.rb +5 -0
  126. data/lib/rigor/cli.rb +209 -54
  127. data/lib/rigor/config_audit.rb +1 -1
  128. data/lib/rigor/configuration/severity_profile.rb +5 -5
  129. data/lib/rigor/configuration.rb +72 -30
  130. data/lib/rigor/effects/definition_context.rb +0 -2
  131. data/lib/rigor/effects/envelope.rb +1 -1
  132. data/lib/rigor/effects/framework_units.rb +1 -1
  133. data/lib/rigor/effects/mutation_classifier.rb +4 -7
  134. data/lib/rigor/effects/propagator.rb +1 -1
  135. data/lib/rigor/effects/registry.rb +2 -2
  136. data/lib/rigor/effects/snapshot.rb +1 -1
  137. data/lib/rigor/effects/taint_cause.rb +0 -2
  138. data/lib/rigor/effects/unit_scan.rb +9 -13
  139. data/lib/rigor/environment/bundle_sig_discovery.rb +5 -9
  140. data/lib/rigor/environment/class_registry.rb +3 -3
  141. data/lib/rigor/environment/core_root.rb +77 -0
  142. data/lib/rigor/environment/default_libraries.rb +1 -1
  143. data/lib/rigor/environment/deferred_signature_plan.rb +283 -0
  144. data/lib/rigor/environment/gated_signature_guard.rb +1 -1
  145. data/lib/rigor/environment/gated_signature_plan.rb +5 -2
  146. data/lib/rigor/environment/lockfile_resolver.rb +28 -12
  147. data/lib/rigor/environment/member_consistency/comparator.rb +18 -1
  148. data/lib/rigor/environment/member_consistency.rb +56 -7
  149. data/lib/rigor/environment/missing_gem_constant_index.rb +3 -2
  150. data/lib/rigor/environment/project_path_gems.rb +124 -0
  151. data/lib/rigor/environment/rbs_hierarchy.rb +6 -15
  152. data/lib/rigor/environment/rbs_loader.rb +480 -67
  153. data/lib/rigor/environment/reflection.rb +7 -7
  154. data/lib/rigor/environment/required_features.rb +0 -3
  155. data/lib/rigor/environment/ruby_library_roots.rb +1 -1
  156. data/lib/rigor/environment/vendored_signature_plan.rb +153 -0
  157. data/lib/rigor/environment.rb +41 -10
  158. data/lib/rigor/flow_contribution/merger.rb +4 -4
  159. data/lib/rigor/inference/acceptance.rb +93 -32
  160. data/lib/rigor/inference/block_parameter_binder.rb +2 -3
  161. data/lib/rigor/inference/block_run_count.rb +178 -0
  162. data/lib/rigor/inference/budget_trace.rb +8 -2
  163. data/lib/rigor/inference/builtins/exception_catalog.rb +2 -2
  164. data/lib/rigor/inference/builtins/numeric_catalog.rb +2 -5
  165. data/lib/rigor/inference/builtins/random_catalog.rb +2 -2
  166. data/lib/rigor/inference/callee_mutation_chain.rb +88 -77
  167. data/lib/rigor/inference/callee_write_census.rb +815 -0
  168. data/lib/rigor/inference/construction_escape.rb +217 -0
  169. data/lib/rigor/inference/declaration_walk/traversal.rb +1 -1
  170. data/lib/rigor/inference/deep_erasure.rb +39 -7
  171. data/lib/rigor/inference/def_handle.rb +1 -1
  172. data/lib/rigor/inference/def_node_resolver.rb +1 -1
  173. data/lib/rigor/inference/def_return_typer.rb +46 -4
  174. data/lib/rigor/inference/definer_resolution.rb +4 -2
  175. data/lib/rigor/inference/definers_index.rb +106 -0
  176. data/lib/rigor/inference/direct_ivar_writes.rb +300 -0
  177. data/lib/rigor/inference/element_read_widening.rb +49 -3
  178. data/lib/rigor/inference/expression_typer.rb +116 -49
  179. data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
  180. data/lib/rigor/inference/global_write_census.rb +76 -2
  181. data/lib/rigor/inference/global_writer_reach.rb +260 -0
  182. data/lib/rigor/inference/hkt_body.rb +1 -2
  183. data/lib/rigor/inference/hkt_body_parser.rb +3 -5
  184. data/lib/rigor/inference/hkt_reducer.rb +2 -2
  185. data/lib/rigor/inference/hkt_registry.rb +3 -5
  186. data/lib/rigor/inference/indexed_narrowing.rb +4 -7
  187. data/lib/rigor/inference/intervening_ivar_reset.rb +26 -4
  188. data/lib/rigor/inference/ivar_writer_census.rb +51 -0
  189. data/lib/rigor/inference/key_presence_guard.rb +2 -1
  190. data/lib/rigor/inference/loop_value_record.rb +8 -4
  191. data/lib/rigor/inference/match_rebinding/frame.rb +46 -0
  192. data/lib/rigor/inference/match_rebinding/yielding_match.rb +45 -0
  193. data/lib/rigor/inference/match_rebinding.rb +11 -1
  194. data/lib/rigor/inference/memo_audit.rb +55 -0
  195. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +1 -1
  196. data/lib/rigor/inference/method_dispatcher/argument_distribution.rb +40 -0
  197. data/lib/rigor/inference/method_dispatcher/block_folding.rb +1 -1
  198. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +3 -3
  199. data/lib/rigor/inference/method_dispatcher/contested_overloads.rb +89 -0
  200. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -1
  201. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +1 -1
  202. data/lib/rigor/inference/method_dispatcher/no_match_fallback.rb +54 -0
  203. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +39 -27
  204. data/lib/rigor/inference/method_dispatcher/random_folding.rb +1 -1
  205. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +203 -17
  206. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +1 -1
  207. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +2 -2
  208. data/lib/rigor/inference/method_dispatcher/self_substitute.rb +2 -2
  209. data/lib/rigor/inference/method_dispatcher/set_folding.rb +9 -7
  210. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +83 -20
  211. data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +0 -1
  212. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +50 -17
  213. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -5
  214. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +4 -6
  215. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  216. data/lib/rigor/inference/method_dispatcher.rb +64 -14
  217. data/lib/rigor/inference/module_function_state.rb +11 -1
  218. data/lib/rigor/inference/multi_target_binder.rb +1 -1
  219. data/lib/rigor/inference/mutation_rejoin.rb +240 -4
  220. data/lib/rigor/inference/mutation_widening.rb +157 -27
  221. data/lib/rigor/inference/narrowing.rb +47 -33
  222. data/lib/rigor/inference/optimistic_origin.rb +1 -1
  223. data/lib/rigor/inference/out_buffer_mutation.rb +72 -0
  224. data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
  225. data/lib/rigor/inference/pre_eval_constants.rb +1 -1
  226. data/lib/rigor/inference/project_method_ownership.rb +6 -3
  227. data/lib/rigor/inference/project_yielder.rb +11 -17
  228. data/lib/rigor/inference/receiver_alias.rb +20 -0
  229. data/lib/rigor/inference/refined_dispatch/constructed_class.rb +3 -1
  230. data/lib/rigor/inference/refined_dispatch/own_method.rb +2 -1
  231. data/lib/rigor/inference/remover_holder_scan.rb +233 -0
  232. data/lib/rigor/inference/remover_narrowing.rb +224 -0
  233. data/lib/rigor/inference/rewrite_mutation.rb +53 -2
  234. data/lib/rigor/inference/scope_indexer/certainty.rb +0 -1
  235. data/lib/rigor/inference/scope_indexer/constant_ivar_value.rb +93 -0
  236. data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +58 -15
  237. data/lib/rigor/inference/scope_indexer/derived_param_value.rb +112 -0
  238. data/lib/rigor/inference/scope_indexer/empty_seed.rb +99 -0
  239. data/lib/rigor/inference/scope_indexer/ivar_contributions.rb +135 -0
  240. data/lib/rigor/inference/scope_indexer.rb +137 -46
  241. data/lib/rigor/inference/self_call_ivar_writes.rb +84 -12
  242. data/lib/rigor/inference/set_mutation.rb +189 -0
  243. data/lib/rigor/inference/singleton_hook_decline.rb +1 -2
  244. data/lib/rigor/inference/statement_evaluator.rb +2328 -138
  245. data/lib/rigor/inference/struct_fold_safety.rb +4 -6
  246. data/lib/rigor/inference/synthetic_method_scanner.rb +5 -6
  247. data/lib/rigor/inference/toplevel_def_census.rb +59 -0
  248. data/lib/rigor/inference/unknown_store_widening.rb +103 -4
  249. data/lib/rigor/inference/unthreaded_rebinds.rb +2 -3
  250. data/lib/rigor/inference/void_tail_summary.rb +2 -1
  251. data/lib/rigor/language_server/buffer_table.rb +3 -3
  252. data/lib/rigor/language_server/completion_provider.rb +26 -20
  253. data/lib/rigor/language_server/config_warning_announcer.rb +28 -0
  254. data/lib/rigor/language_server/diagnostic_publisher.rb +8 -3
  255. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  256. data/lib/rigor/language_server/folding_range_provider.rb +1 -2
  257. data/lib/rigor/language_server/hover_provider.rb +1 -7
  258. data/lib/rigor/language_server/hover_renderer.rb +6 -8
  259. data/lib/rigor/language_server/project_context.rb +39 -1
  260. data/lib/rigor/language_server/publish_batcher.rb +3 -4
  261. data/lib/rigor/language_server/selection_range_provider.rb +2 -4
  262. data/lib/rigor/language_server/server.rb +7 -3
  263. data/lib/rigor/language_server/signature_help_provider.rb +4 -4
  264. data/lib/rigor/lens/plugins.rb +1 -1
  265. data/lib/rigor/mcp/server.rb +73 -2
  266. data/lib/rigor/mcp.rb +1 -1
  267. data/lib/rigor/plugin/access_denied_error.rb +1 -2
  268. data/lib/rigor/plugin/bundled_catalog.rb +1 -12
  269. data/lib/rigor/plugin/io_boundary.rb +1 -1
  270. data/lib/rigor/plugin/loader.rb +25 -8
  271. data/lib/rigor/plugin/macro/heredoc_template.rb +7 -8
  272. data/lib/rigor/plugin/macro/trait_registry.rb +6 -9
  273. data/lib/rigor/plugin/manifest.rb +9 -13
  274. data/lib/rigor/plugin/registry.rb +3 -3
  275. data/lib/rigor/plugin/services.rb +2 -3
  276. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -0
  277. data/lib/rigor/plugin/trust_policy.rb +2 -2
  278. data/lib/rigor/plugin/type_node_resolver.rb +2 -4
  279. data/lib/rigor/plugin.rb +6 -8
  280. data/lib/rigor/plugin_gap_advisory.rb +5 -1
  281. data/lib/rigor/project_environment.rb +16 -2
  282. data/lib/rigor/protection/analysis_guard.rb +1 -1
  283. data/lib/rigor/protection/discovery_seed.rb +44 -5
  284. data/lib/rigor/protection/mutator.rb +1 -1
  285. data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
  286. data/lib/rigor/rbs_extended/hkt_directives.rb +6 -10
  287. data/lib/rigor/rbs_extended/reporter.rb +27 -3
  288. data/lib/rigor/rbs_extended.rb +2 -3
  289. data/lib/rigor/reflection/constant_ancestors.rb +5 -3
  290. data/lib/rigor/rooted_glob.rb +15 -1
  291. data/lib/rigor/run_scoped_slots.rb +38 -0
  292. data/lib/rigor/runtime/jit.rb +1 -1
  293. data/lib/rigor/scope/discovery_index.rb +19 -6
  294. data/lib/rigor/scope/resolution_chain.rb +3 -1
  295. data/lib/rigor/scope.rb +365 -31
  296. data/lib/rigor/sig_gen/classification.rb +4 -0
  297. data/lib/rigor/sig_gen/effect_annotation.rb +3 -3
  298. data/lib/rigor/sig_gen/generator.rb +371 -58
  299. data/lib/rigor/sig_gen/generic_header_scan.rb +189 -0
  300. data/lib/rigor/sig_gen/inline_declarations.rb +2 -2
  301. data/lib/rigor/sig_gen/layout_index.rb +3 -3
  302. data/lib/rigor/sig_gen/method_candidate.rb +23 -4
  303. data/lib/rigor/sig_gen/observation_collector.rb +3 -5
  304. data/lib/rigor/sig_gen/observed_call.rb +1 -1
  305. data/lib/rigor/sig_gen/path_mapper.rb +6 -0
  306. data/lib/rigor/sig_gen/renderer.rb +15 -9
  307. data/lib/rigor/sig_gen/skip_reason_catalog.rb +15 -2
  308. data/lib/rigor/sig_gen/type_name_qualification.rb +69 -0
  309. data/lib/rigor/sig_gen/write_reporter.rb +63 -14
  310. data/lib/rigor/sig_gen/write_result.rb +23 -12
  311. data/lib/rigor/sig_gen/writer.rb +217 -37
  312. data/lib/rigor/type/acceptance_router.rb +2 -2
  313. data/lib/rigor/type/accepts_result.rb +1 -1
  314. data/lib/rigor/type/app.rb +4 -6
  315. data/lib/rigor/type/combinator.rb +9 -13
  316. data/lib/rigor/type/constant.rb +4 -5
  317. data/lib/rigor/type/hash_shape.rb +21 -40
  318. data/lib/rigor/type/union.rb +1 -1
  319. data/lib/rigor/type_node/identifier.rb +1 -2
  320. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  321. data/lib/rigor/type_node/string_literal.rb +1 -2
  322. data/lib/rigor/type_node/symbol_literal.rb +1 -2
  323. data/lib/rigor/type_node/union.rb +3 -6
  324. data/lib/rigor/value_semantics.rb +1 -10
  325. data/lib/rigor/version.rb +1 -1
  326. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +2 -2
  327. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +2 -3
  328. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -1
  329. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +38 -36
  330. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +4 -2
  331. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +6 -4
  332. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +1 -1
  333. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -5
  334. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +0 -1
  335. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +2 -1
  336. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +1 -1
  337. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +66 -19
  338. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
  339. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +4 -3
  340. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +3 -2
  341. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +2 -2
  342. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +21 -31
  343. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +10 -16
  344. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +2 -3
  345. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +0 -1
  346. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +5 -4
  347. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +3 -4
  348. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +1 -1
  349. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +7 -12
  350. data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +1 -1
  351. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +1 -1
  352. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/symbolize_keys.rb +102 -0
  353. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +19 -6
  354. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +26 -13
  355. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +3 -3
  356. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +1 -3
  357. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +6 -6
  358. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +5 -6
  359. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +1 -1
  360. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/params_shape.rb +2 -3
  361. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -8
  362. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +0 -1
  363. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
  364. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +10 -18
  365. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +0 -1
  366. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +0 -2
  367. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +4 -5
  368. data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +0 -2
  369. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +6 -7
  370. data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +2 -4
  371. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +2 -5
  372. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -2
  373. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +6 -6
  374. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
  375. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +3 -8
  376. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +2 -3
  377. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -8
  378. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -3
  379. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -3
  380. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +1 -1
  381. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +4 -11
  382. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -1
  383. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +2 -1
  384. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +1 -1
  385. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +2 -2
  386. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +3 -4
  387. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +6 -8
  388. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +3 -2
  389. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +4 -5
  390. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +6 -7
  391. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +1 -1
  392. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +2 -2
  393. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +17 -21
  394. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +2 -3
  395. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +3 -2
  396. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +4 -4
  397. data/sig/rigor/analysis/baseline.rbs +0 -3
  398. data/sig/rigor/analysis/fact_store.rbs +1 -0
  399. data/sig/rigor/analysis/reachability/scan_cache.rbs +1 -1
  400. data/sig/rigor/cli/diff_command.rbs +1 -1
  401. data/sig/rigor/cli/explain_command.rbs +1 -1
  402. data/sig/rigor/effects/registry.rbs +2 -2
  403. data/sig/rigor/inference.rbs +4 -1
  404. data/sig/rigor/plugin/additional_initializer.rbs +0 -5
  405. data/sig/rigor/plugin/base.rbs +1 -0
  406. data/sig/rigor/plugin/effect_attribution.rbs +3 -3
  407. data/sig/rigor/plugin/effect_edge.rbs +1 -1
  408. data/sig/rigor/plugin/effect_entry_points.rbs +3 -3
  409. data/sig/rigor/plugin/io_boundary.rbs +2 -1
  410. data/sig/rigor/plugin/manifest.rbs +9 -9
  411. data/sig/rigor/plugin/protocol_contract.rbs +3 -7
  412. data/sig/rigor/scope.rbs +23 -3
  413. data/skills/rigor-plugin-author/SKILL.md +4 -4
  414. data/skills/rigor-plugin-author/references/02-walker-and-types.md +5 -5
  415. data/skills/rigor-plugin-review/SKILL.md +3 -3
  416. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +12 -1
  417. metadata +36 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 608dc6b03dfc9267330c24c6493f3f4ee2d025ac5ce01a044202828fc282e258
4
- data.tar.gz: 395692680ea16ec6dc42d4187fc57c10ce00731eea4cd1d03176fcc2daa07c14
3
+ metadata.gz: 983cf341f34d4407d6d06ab2b0bf50260f5b52a4f6e29c61ce0042123ab833cc
4
+ data.tar.gz: 17ea6b8b13033e507d0e7570913ab2609c830c9a5cfa706cfe055b6857d315f5
5
5
  SHA512:
6
- metadata.gz: c7145aafeaf9fd22fe3759970c9b4eaafd8720796048641a387ec7a02d4e579689fde37540db686da9904b7d8419a6338b9db7caa38997d7184960c346253f5a
7
- data.tar.gz: bff814da943de361c7c1e5bee99fd0e4847d16bb8a9bd3e36609e69246e8eb0e141c6c6e87b425720d709e2277195d9a14100a3018fef75670361fe2acd2160b
6
+ metadata.gz: 0cd85174733cb61860d33f5636e81ac5d19972f9e6bd66fc6282d1aa7330735f8a57a067e1cfa81071632a45a0ca123f607be5b5bda7b3b32153fb8069328dfc
7
+ data.tar.gz: 64ecfde3bfe3eaef83ff44fa6ee1ca72bf6f353648c55a354726e60a87203012f907ce5406dc162289ff2a99056cf8b2962a79f636d5e7dd93cf16ec2b00e8d2
data/README.md CHANGED
@@ -131,7 +131,7 @@ and [CI guide](https://rigor.typedduck.fail/manual/11-ci/).
131
131
 
132
132
  ```sh
133
133
  rigor check lib # find bugs (caches under .rigor/ — gitignore it)
134
- rigor init # drop a starter .rigor.yml
134
+ rigor init # drop a starter .rigor.yml (suggests plugins from Gemfile.lock)
135
135
  rigor annotate lib/foo.rb # reprint a file with inferred types in the margin
136
136
  rigor triage lib # summarise diagnostics: distribution, hotspots
137
137
  rigor sig-gen --diff lib/foo.rb # emit RBS from inference (--write to save)
@@ -232,7 +232,7 @@ rigor docs --list # list every bundled page
232
232
 
233
233
  ## Status
234
234
 
235
- Current release: **`v0.4.6`** (2026-10-23) — on the
235
+ Current release: **`v0.4.7`** (2026-11-03) — on the
236
236
  `0.4.x` line, continuing the evaluation era opened by `v0.2.0`, the first
237
237
  publicly-announced (general / evaluation) release. The line publishes
238
238
  an enumerated [compatibility surface](docs/compatibility.md) as a
@@ -898,31 +898,34 @@ end
898
898
 
899
899
  class Hash[unchecked out K, unchecked out V]
900
900
  # `core_ext/hash/keys`
901
- def symbolize_keys: () -> Hash[Symbol, V]
901
+ # `key.to_sym rescue key` keeps a key without `to_sym`, so no one key type is right: `Hash[untyped, V]` here,
902
+ # and with the plugin `dynamic_return` hook (#2433) maps each key member where it can.
903
+ def symbolize_keys: () -> Hash[untyped, V]
902
904
  def symbolize_keys!: () -> self
903
905
  def deep_symbolize_keys: () -> Hash[untyped, untyped]
904
- def deep_symbolize_keys!: () -> self
906
+ def deep_symbolize_keys!: () -> Hash[untyped, untyped]
905
907
  def stringify_keys: () -> Hash[String, V]
906
- def stringify_keys!: () -> self
908
+ def stringify_keys!: () -> Hash[String, V]
907
909
  def deep_stringify_keys: () -> Hash[untyped, untyped]
908
- def deep_stringify_keys!: () -> self
910
+ def deep_stringify_keys!: () -> Hash[untyped, untyped]
909
911
  def assert_valid_keys: (*K | Array[K]) -> self
910
- def deep_transform_keys: () { (K) -> K } -> Hash[K, untyped]
911
- def deep_transform_keys!: () { (K) -> K } -> self
912
+ def deep_transform_keys: [X] () { (K) -> X } -> Hash[X, untyped]
913
+ def deep_transform_keys!: [X] () { (K) -> X } -> Hash[X, untyped]
912
914
  def deep_transform_values: () { (V) -> untyped } -> Hash[K, untyped]
913
- def deep_transform_values!: () { (V) -> untyped } -> self
915
+ def deep_transform_values!: () { (V) -> untyped } -> Hash[K, untyped]
914
916
  # `alias_method`s of `symbolize_keys` / `symbolize_keys!`.
915
- def to_options: () -> Hash[Symbol, V]
917
+ def to_options: () -> Hash[untyped, V]
916
918
  def to_options!: () -> self
917
919
 
918
920
  # `core_ext/hash/deep_dup`
919
921
  def deep_dup: () -> Hash[K, V]
920
922
 
921
- # `core_ext/hash/deep_merge`
922
- def deep_merge: (Hash[K, V]) -> Hash[K, V]
923
- | (Hash[K, V]) { (K, V, V) -> V } -> Hash[K, V]
924
- def deep_merge!: (Hash[K, V]) -> self
925
- | (Hash[K, V]) { (K, V, V) -> V } -> self
923
+ # `core_ext/hash/deep_merge` — the twin of the plugin's rows (#2424 review): the argument's keys join the
924
+ # receiver's, and a nested merge makes a value neither side declares, so values read `untyped`.
925
+ def deep_merge: [A, B] (Hash[A, B] other_hash) -> Hash[K | A, untyped]
926
+ | [A, B] (Hash[A, B] other_hash) { (K | A, untyped, untyped) -> untyped } -> Hash[K | A, untyped]
927
+ def deep_merge!: [A, B] (Hash[A, B] other_hash) -> Hash[K | A, untyped]
928
+ | [A, B] (Hash[A, B] other_hash) { (K | A, untyped, untyped) -> untyped } -> Hash[K | A, untyped]
926
929
  # `other.is_a?(Hash)` — the hook `ActiveSupport::DeepMergeable` asks before
927
930
  # recursing into a value. `:nodoc:`, but public.
928
931
  def deep_merge?: (untyped other) -> bool
@@ -150,7 +150,7 @@ module Bundler
150
150
  # `#initialize`, `#specs` — declared by `sig/shims/bundler.rbs` with the same types.
151
151
  def platforms: () -> Array[untyped]
152
152
  def sources: () -> Array[untyped]
153
- def dependencies: () -> Array[Dependency]
153
+ def dependencies: () -> Hash[String, Dependency]
154
154
  def ruby_version: () -> untyped
155
155
  def bundler_version: () -> untyped
156
156
  end
@@ -112,8 +112,9 @@ class Gem::Specification
112
112
 
113
113
  # Instance-side surface widely used by bundler / rubygems
114
114
  # internals. `attr_accessor` mirrors the writer + reader pair
115
- # so the survey's `.name=` / `.version=` / `.full_gem_path=` /
116
- # `.loaded_from=` / `.post_install_message=` writes resolve too.
115
+ # so the survey's `.name=` / `.version=` / `.loaded_from=` /
116
+ # `.post_install_message=` writes resolve too. `.full_gem_path` is declared on `Gem::BasicSpecification`,
117
+ # which this class inherits from.
117
118
  def activate: () -> untyped
118
119
  def specification_version: () -> Integer
119
120
  def files: () -> Array[String]
@@ -127,13 +128,13 @@ class Gem::Specification
127
128
  # `#version`'s READER is declared by `sig/shims/rubygems.rbs` (`attr_reader version (): Version`);
128
129
  # only the writer is ours.
129
130
  def version=: (Gem::Version) -> Gem::Version
130
- attr_accessor full_gem_path: String
131
131
  attr_accessor loaded_from: String?
132
132
  attr_accessor post_install_message: String?
133
133
  end
134
134
 
135
135
  class Gem::BasicSpecification
136
136
  def version: () -> Gem::Version
137
+ attr_accessor full_gem_path: String
137
138
  def executables: () -> Array[String]
138
139
  def name: () -> String
139
140
  def full_name: () -> String
@@ -283,8 +283,11 @@ If you used the [AI-assisted setup](#the-fast-path-let-an-ai-agent-set-it-up),
283
283
  the `rigor-project-init` skill already wrote one for you. To
284
284
  write a starter by hand, `rigor init` emits `.rigor.dist.yml`
285
285
  — the project default that gets committed, with `paths` and a
286
- `severity_profile` filled in, `target_ruby` commented out until
287
- you set it to your project's Ruby, and the rest commented out. That is all most projects need; the key
286
+ `severity_profile` filled in. When your `Gemfile.lock` declares a
287
+ gem a bundled plugin models, the template suggests that plugin
288
+ under `plugins:`, commented out, for you to enable. `target_ruby`
289
+ is commented out until you set it to your project's Ruby, and the
290
+ rest is commented out too. That is all most projects need; the key
288
291
  reference, the JSON-schema editor integration, and `includes:`
289
292
  composition are in
290
293
  [Configuration](../manual/03-configuration.md).
@@ -155,6 +155,11 @@ The `sig.skipped.*` reasons are:
155
155
  generic by an inline declaration and `sig/` does not declare
156
156
  it yet, or names its type parameters otherwise. See
157
157
  [Classes made generic inline](#classes-made-generic-inline).
158
+ - `sig.skipped.generic-reopening` — the method's class, or one
159
+ it is nested in, is declared generic by loaded RBS with a
160
+ type parameter that has a bound or a default, and `sig/`
161
+ does not declare it. See
162
+ [Reopening a generic class](#reopening-a-generic-class).
158
163
  - `sig.skipped.inline-differs` — the method is declared
159
164
  inline and in `sig/`, and the two disagree. A refusal:
160
165
  `--write` and `--check` exit `1`. See
@@ -330,10 +335,34 @@ so a return inferred for the new line would describe
330
335
  parameters that are about to change. Delete the `sig/` member
331
336
  and re-run; sig-gen then writes the method afresh.
332
337
 
338
+ ### Reopening a generic class
339
+
340
+ When your code reopens a class that loaded RBS declares
341
+ generic — `class Array`, `class Hash`, a gem's class, or one
342
+ your `sig/` declares — sig-gen writes the header with that
343
+ declaration's type parameters: `class Array[unchecked out E]`.
344
+ rbs rejects a reopening whose parameters differ, so a bare
345
+ `class Array` would fail the class, and every class whose
346
+ signature mentions it, with `GenericParameterMismatchError`.
347
+ The members still say `untyped` for the element type, and a
348
+ class whose name matches a copied parameter is written
349
+ anchored (`::E`), since a bare `E` would name the parameter.
350
+
351
+ The parameters are copied only when every one is plain: a
352
+ name, a variance and `unchecked`. A bound or a default
353
+ (`class Box[T < Comparable]`) is a type written for the
354
+ declaring file's namespace and could mean another class
355
+ where sig-gen writes, so that class's methods, and those of
356
+ classes nested in it, are skipped as
357
+ `sig.skipped.generic-reopening`. Declare the class in `sig/`
358
+ with its parameters and sig-gen writes the members into that
359
+ declaration. A file that already declares the class keeps its
360
+ own header.
361
+
333
362
  ### Classes made generic inline
334
363
 
335
- sig-gen does not write a class's type parameters yet. A class
336
- declared generic inline (`# @rbs generic T`) is therefore not
364
+ sig-gen does not copy type parameters from an inline
365
+ declaration. A class declared generic inline (`# @rbs generic T`) is therefore not
337
366
  opened in `sig/`: a header without its parameters would make
338
367
  rbs reject the class, and every class whose signature mentions
339
368
  it, with `GenericParameterMismatchError`. Its methods, and
@@ -626,6 +655,24 @@ list is the current state):
626
655
  are recognised as user-authored and never produce a
627
656
  duplicate `def` insertion.
628
657
 
658
+ Two rules shape the class declarations and type names around
659
+ those methods:
660
+
661
+ - **A subclass of a core or stdlib class** is declared even
662
+ when it has no method of its own to emit, so a method
663
+ returning one resolves: `class MyHash < Hash; end` is
664
+ written `class MyHash < ::Hash[untyped, untyped]`. The
665
+ declaration goes into the file written for its source file,
666
+ so it appears only when that source file has at least one
667
+ method to emit. A class some signature already declares is
668
+ left to that declaration.
669
+ - **A type name a namespace shadows** is written with a
670
+ leading `::`. Inside a `module Foo` that declares a
671
+ `Foo::Set`, a method returning a core `Set` is written
672
+ `-> ::Set[untyped]`, because RBS would read a bare `Set`
673
+ there as `Foo::Set`. Where nothing shadows the name, it is
674
+ written bare as before.
675
+
629
676
  Method shapes the generator does **not** cover yet:
630
677
 
631
678
  - `define_method(:name) { ... }`.
@@ -643,7 +690,7 @@ two are wired today, one is reserved.
643
690
  | Policy | Behaviour |
644
691
  | --- | --- |
645
692
  | `untyped` (default) | Every parameter is spelled `untyped`. No inference-derived parameter contract is imposed on future callers. The user retains complete authorship over parameter typing. |
646
- | `observed` | Collect argument types from every call site under `--observe=PATH...` (defaults to the configured `test_paths:`, or whichever of `spec/` and `test/` exist), union per parameter position, erase to RBS, emit the union. With no test root to observe, or a declared root that does not exist or is not read, sig-gen says so on stderr, and `--format=json` carries the same fact in a top-level `observation` key (below). |
693
+ | `observed` | Collect argument types from every call site under `--observe=PATH...` (defaults to the configured `test_paths:`, or whichever of `spec/` and `test/` exist), union per parameter position, erase to RBS, emit the union. A literal argument is widened to its class first, so `"hello"` proposes `String` (#2254). With no test root to observe, or a declared root that does not exist or is not read, sig-gen says so on stderr, and `--format=json` carries the same fact in a top-level `observation` key (below). |
647
694
  | `observed-strict` | Reserved. Will additionally widen to capability roles (`_ToStr`, `_ToS`, …) once the role catalog ships. Currently rejected with a usage error. |
648
695
 
649
696
  Under `--params=observed`, the `--print` / `--diff` JSON payload gains
@@ -20,7 +20,7 @@ unchanged. This appendix maps the vocabulary.
20
20
  | Default behaviour | Inference, fall back silent | Inference, fall back silent |
21
21
  | "Levels" | 0 – 10 (numeric) | `lenient` / `balanced` / `strict` (named) |
22
22
  | Per-rule control | `ignoreErrors:` regex, level demotion | `disable:`, `severity_overrides:` |
23
- | Baseline | `phpstan-baseline.neon` | `.rigor-baseline.yml` (managed) / `rigor.baseline.json` (ad-hoc) |
23
+ | Baseline | `phpstan-baseline.neon` | `.rigor-baseline.yml` (managed) / `rigor-check.json` (ad-hoc) |
24
24
  | Stub format | PHP stub files | RBS files |
25
25
  | Custom narrowing | Type-Specifying Extensions | Plugins (Chapter 9) |
26
26
  | Custom return shape | Dynamic Return Type Extensions | Plugin `dynamic_return` |
@@ -140,10 +140,10 @@ severity controls, and includes.
140
140
  | `ignoreErrors:` (regex / pattern) | `disable:` (rule identifier or wildcard) |
141
141
  | `parameters: ignoreErrors:` per-path | `# rigor:disable-file <rule>` at the file head |
142
142
  | `includes:` | `includes:` |
143
- | `phpstan-baseline.neon` | `.rigor-baseline.yml` (managed) — or `rigor.baseline.json` (ad-hoc) |
144
- | `phpstan analyse --generate-baseline` | `rigor baseline generate` (managed) — or `rigor check --format=json > rigor.baseline.json` (ad-hoc) |
143
+ | `phpstan-baseline.neon` | `.rigor-baseline.yml` (managed) — or `rigor-check.json` (ad-hoc) |
144
+ | `phpstan analyse --generate-baseline` | `rigor baseline generate` (managed) — or `rigor check --format=json > rigor-check.json` (ad-hoc) |
145
145
  | `phpstan analyse` | `rigor check` |
146
- | `phpstan analyse --baseline` | `rigor check` with `baseline: .rigor-baseline.yml` configured (managed) — or `rigor diff rigor.baseline.json` (ad-hoc) |
146
+ | `phpstan analyse --baseline` | `rigor check` with `baseline: .rigor-baseline.yml` configured (managed) — or `rigor diff rigor-check.json` (ad-hoc) |
147
147
  | Path resolution: relative to declaring file | Path resolution: relative to declaring file (same rule). |
148
148
 
149
149
  Rigor has two baseline mechanisms: a **managed** baseline
@@ -10,6 +10,8 @@ rigor <command> [options] [arguments]
10
10
  `rigor help` prints the command list; `rigor version` prints
11
11
  the installed version. An unknown command, or a malformed
12
12
  option, exits `64` — the conventional "usage error" code.
13
+ See [`rigor help`](#rigor-help) for a single command's help and
14
+ the machine-readable list of commands and flags.
13
15
 
14
16
  Every command that loads your project configuration also
15
17
  accepts `--config=PATH` to point at a specific config file
@@ -46,7 +48,7 @@ check their arity, since another file may redefine them.
46
48
  | `--no-cache` | Skip the persistent cache for this run. With `--incremental` it is a cold full analysis: the incremental snapshot and the run-result cache are neither read nor written, so the next plain `--incremental` run still uses the snapshot it had before. |
47
49
  | `--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. A run with nothing changed is answered from the cache without starting the analyzer. See [Caching](12-caching.md). |
48
50
  | `--verify-incremental` | Acceptance gate: run the incremental analyzer against a full `--no-cache` run and assert the diagnostics agree as a set and in the same order, then exit (0 on match, 1 on mismatch: the differing diagnostics for a set mismatch, or the first index where the order diverges for a same-set, different-order mismatch). It re-analyses half of an unchanged tree and serves the other half from cache, so it checks the cache-merge path but not that an edit re-checks every file it affects; `--verify-incremental-edits` adds that. |
49
- | `--verify-incremental-edits=N` | With `--verify-incremental` (and implying it): after the check above, replay two edits on each of `N` files spread over the analysed list, through an editor buffer, never on disk: the file emptied, then the file reduced to its `class` / `module` declarations with every body dropped (skipped when that is the file itself, or holds no declaration). After each edit, the warm recheck of the baseline must equal a full analysis of the edited tree, or the run fails naming the edit and the file. Each replay costs a recheck and a full analysis. A replay the recheck declines (it would have been a full analysis) is counted in the OK line, and the run fails if every replay declined. Only these two removals are replayed, never an addition. Default 0. |
51
+ | `--verify-incremental-edits=N` | With `--verify-incremental` (and implying it): after the check above, replay edits on each of `N` files spread over the analysed list, through an editor buffer, never on disk: the file emptied; the file reduced to its `class` / `module` declarations with every body dropped (skipped when that is the file itself, or holds no declaration); and up to two copies of the file each missing one statement of a `class` / `module` body, with everything else kept, so a finer dependency edge is the only thing that re-checks a reader (visibility calls such as `private` and `module_function` are dropped first, then other statements spread over the file). After each edit, the warm recheck of the baseline must equal a full analysis of the edited tree, or the run fails naming the edit and the file. Each replay costs a recheck and a full analysis. A replay the recheck declines (it would have been a full analysis) is counted in the OK line, and the run fails if every replay declined. Only removals are replayed, never an addition. Default 0. |
50
52
  | `--clear-cache` | Delete the cache directory before running. |
51
53
  | `--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. |
52
54
  | `--[no-]stats` | Print a run summary (files, classes, memory, wall time) to stderr. Default on. |
@@ -60,7 +62,7 @@ check their arity, since another file may redefine them.
60
62
  | `--bleeding-edge[=ids]` | Adopt the bleeding-edge overlay for this run, overriding the configured [`bleeding_edge:`](03-configuration.md) selection (ADR-50 § WD2). Bare adopts every queued feature; `--bleeding-edge=a,b` adopts only the named feature ids. Inspect it with [`rigor show-bleedingedge`](#rigor-show-bleedingedge). |
61
63
  | `--no-bleeding-edge` | Ignore any configured `bleeding_edge:` selection for this run (adopt none). |
62
64
  | `--no-tolerated-effects` | Check effect envelopes as if [`effects.tolerated:`](03-configuration.md) were empty — the audit switch for your discharge policy (ADR-103). Judgment only: the run, what it collects and its cache entry are identical either way, so this never costs a re-analysis. |
63
- | `--tmp-file=PATH --instead-of=PATH` | Editor mode: analyse `PATH` using the buffer in `--tmp-file`. Both required together. Alone, only the buffer's own file produces diagnostics; add `--incremental` for whole-project scope (see below). |
65
+ | `--tmp-file=PATH --instead-of=PATH` | Editor mode: analyse `PATH` using the buffer in `--tmp-file`. Both required together, once per run (a repeat is a usage error). Alone, only the buffer's own file produces diagnostics; add `--incremental` for whole-project scope (see below). |
64
66
 
65
67
  Exit `0` when no error-severity diagnostics remain, `1` when
66
68
  any are reported, `64` on a usage error. `--fail-on` raises that
@@ -70,14 +72,16 @@ that want the stricter reading.
70
72
  ### Editor mode scope
71
73
 
72
74
  `--tmp-file` / `--instead-of` on their own analyse **only** the
73
- buffer's file. It still reads the rest of the project's declarations
74
- (the classes and methods other files define), as `rigor check FILE`
75
+ buffer's file. The `--instead-of` logical path is analysed even when it
76
+ lies outside `paths:`, as it always has been. It still reads the rest of the
77
+ project's declarations (the classes and methods other files define), as `rigor check FILE`
75
78
  does, but it reports nothing about what the unsaved edit does to the
76
79
  rest of the project.
77
80
 
78
81
  Adding `--incremental` analyses the **whole project with the buffer
79
82
  substituted** — the edited file and the files that depend on it are
80
83
  re-analysed, everything else is served from the incremental snapshot.
84
+ A buffer for a claimed template is analysed as its template unit, once.
81
85
  An unsaved change to a method's return type therefore surfaces in its
82
86
  callers, not just in the file being edited.
83
87
 
@@ -91,6 +95,11 @@ Two things to know:
91
95
  exist only in your editor, and recording them would make the next
92
96
  ordinary run believe it had already analysed a file in a state it
93
97
  was never in.
98
+ - The buffer's file need not exist on disk yet: a new file is analysed
99
+ as an added file, with or without path arguments.
100
+ - Editor mode binds one buffer per run. Repeating `--tmp-file` or
101
+ `--instead-of` is a usage error (exit `64`), not a silent replacement
102
+ of the earlier pair.
94
103
 
95
104
  `--verify-incremental` refuses a buffer: it compares against a full
96
105
  analysis of the files on disk, which a buffer contradicts by
@@ -101,12 +110,41 @@ construction.
101
110
  Write a starter configuration file.
102
111
 
103
112
  ```sh
104
- rigor init [--path=PATH] [--force]
113
+ rigor init [--path=PATH] [--force] [--no-plugins]
105
114
  ```
106
115
 
107
116
  Writes `.rigor.dist.yml` by default. `--path` chooses a
108
117
  different target; `--force` overwrites an existing file.
109
- Exit `1` if the file exists and `--force` was not given.
118
+ Exit `1` if the file exists and `--force` was not given. A `--path` whose
119
+ directory does not exist, or that cannot be written, is a usage error: one
120
+ `rigor init: cannot write PATH: REASON` line on stderr and exit `64`. The
121
+ directory is not created.
122
+
123
+ When the project has a `Gemfile.lock` in the working directory,
124
+ `rigor init` suggests the bundled plugins whose gems the lockfile
125
+ declares (the same read as [`rigor doctor`](#rigor-doctor)'s
126
+ plugin-gap check). The suggestions are commented out under
127
+ `plugins: []`, so nothing loads until you enable one: delete the
128
+ `[]` on the `plugins:` line, uncomment the entries you want, then
129
+ run `rigor plugins --strict`. A Rails project's `rails` also brings
130
+ in the Rails family, so the list can be long. Without a lockfile,
131
+ or when no locked gem has a bundled plugin, the template has no
132
+ suggestions.
133
+
134
+ Suggesting rather than enabling follows ADR-96 Criterion 2: a
135
+ lockfile's presence does not by itself opt a project into a
136
+ plugin's code.
137
+
138
+ In a Rails application (a `config/application.rb` in the working
139
+ directory), `rigor init` also suggests the source directories that
140
+ exist, `app` and `lib`. The active `paths:` stays `lib`; the
141
+ suggestion is a commented `paths:` line under it, which names what
142
+ was detected. A bare `rigor check` analyses only `lib` until you
143
+ delete the `paths:` list above and uncomment the line below. Other
144
+ layouts get no directory suggestion.
145
+
146
+ `--no-plugins` writes the template with no plugin suggestions, even
147
+ when a lockfile is present.
110
148
 
111
149
  ## `rigor annotate`
112
150
 
@@ -123,10 +161,16 @@ honours `NO_COLOR`; `--color` / `--no-color` override. When
123
161
  colour is on and [`bat`](https://github.com/sharkdp/bat) is on
124
162
  `PATH`, highlighting goes through bat (`--no-bat` opts out;
125
163
  `--bat` warns if bat is missing and falls back to the built-in
126
- colorizer). `--format=json` emits a `{ line => type }` map
127
- instead of the annotated source. Exit `1` on a parse error or a
164
+ colorizer). `--format=json` emits an `{ "schema_version": 1, "annotations": { line => type } }`
165
+ document instead of the annotated source. Exit `1` on a parse error or a
128
166
  missing file.
129
167
 
168
+ ## JSON documents
169
+
170
+ Every `--format=json` document Rigor defines whose top level is an object, except the three named below, starts with an integer `schema_version` (currently `1`). Where a flag selects which document is printed, `mode` follows it: `rigor coverage` prints `precision`, `protection`, `mutation` or `protection-fused`, and `rigor plugins` prints `status` or `capabilities`. The key set is contract from v1.0.0: a minor release may add a key, and removing or renaming one needs a major.
171
+
172
+ `rigor lens` (its own `schema` / `revision`), `rigor baseline dump` (`version`) and `rigor check`'s CI formats (`sarif`, `gitlab`, ...) keep their own markers. `rigor type-of`, `rigor explain` and `rigor trace` are the exceptions and still print their current top-level shape (an array; for `type-of` a flat object or `{ results }`; for `explain <skip-reason>` a flat object); the next minor release switches them to `{ "schema_version": 1, "results" | "rules" | "skip_reasons" | "events": [...] }`. Until then each prints a note on stderr, and the matching MCP tools append the same note as an extra text block.
173
+
130
174
  ## `rigor type-of`
131
175
 
132
176
  Print inferred types at one or more source positions.
@@ -148,7 +192,8 @@ wraps several results in a `results` array. Line queries add a
148
192
  `line_enumerations` array whose `shown` and `total` counts make
149
193
  truncation explicit. `--trace` records fail-soft fallbacks,
150
194
  after the rows of a line table in text output. The editor-mode
151
- `--tmp-file` / `--instead-of` pair is accepted as on `check`.
195
+ `--tmp-file` / `--instead-of` pair is accepted as on `check`, and a repeated flag is a usage error
196
+ (exit `64`) there too.
152
197
 
153
198
  A class or constant that another project file declares reads as
154
199
  `rigor check` types it (`singleton(User)`) when a bare
@@ -159,9 +204,13 @@ and never writes it. With no reusable snapshot (none yet, or one a
159
204
  change made unusable, for example a changed configuration, lockfile, `sig/`, `pre_eval:`
160
205
  file or required feature, an engine upgrade, or a snapshot written
161
206
  for other roots) the probe types the one file on its own, so such a name reads
162
- `Dynamic[top]`, and it prints nothing about it. Only those
163
- declarations are shared: the types `check` infers for an unannotated
164
- parameter are not.
207
+ `Dynamic[top]`, and it prints nothing about it. With
208
+ `parameter_inference: true`, the types `check` infers for an
209
+ unannotated parameter come from the same snapshot (its recorded
210
+ call-site table), so `@v = v` reads as `check` types it, but only
211
+ while no project file has changed since the snapshot (an editor
212
+ buffer whose bytes differ from the snapshot counts as a change); once any file differs, or with the setting off
213
+ or no snapshot, those types are not shared.
165
214
 
166
215
  A template a plugin compiles into Ruby (an ERB view under
167
216
  `rigor-actionpack`) is probed the way `rigor check` analyses
@@ -521,11 +570,11 @@ a severity or a suppression line.
521
570
 
522
571
  ## `rigor diff`
523
572
 
524
- Compare the current diagnostics against a saved baseline JSON
525
- and report only what is new.
573
+ Compare the current diagnostics against a saved
574
+ `rigor check --format=json` result and report only what is new.
526
575
 
527
576
  ```sh
528
- rigor diff [options] <baseline.json> [paths...]
577
+ rigor diff [options] <rigor-check.json> [paths...]
529
578
  ```
530
579
 
531
580
  `--current=PATH` compares against a saved diagnostics JSON
@@ -571,6 +620,18 @@ refuse a file, so a tighter return `--write` declines without
571
620
  `--overwrite` does not fail it; `--check --overwrite` counts one.
572
621
  See [handbook chapter 11](../handbook/11-sig-gen.md#keeping-sig-current-in-ci).
573
622
 
623
+ `--write` also repairs a header in any file under the signature directory it writes to (the directory
624
+ under the project root named like the last path component of the first `signature_paths:` entry, `sig/` by
625
+ default, vendored or generated `.rbs` under it included) that opens a generic class such as
626
+ `class Array` or `class Hash` without its type parameters (an earlier release wrote them), reporting
627
+ `repaired N generic header(s)`, and `--check` fails until it is written. In `--format=json` the row of a file
628
+ no method of the run maps to has `"source": null`. A class is repaired in all of its bare headers or in none:
629
+ when the body of any of them names a type spelt like one of the parameters (`def old: () -> E`), which the
630
+ parameter would capture, or one of them is in another `signature_paths:` entry, which `--write` never
631
+ rewrites, none is repaired. A `generic header left bare` line (`left_bare` in `--format=json`) names the files
632
+ and the reason, and `--check` does not fail on it. Write the name as `::E`, or fix the other file, to let the
633
+ repair through.
634
+
574
635
  `--write` names each proposal it skipped (a member `sig/` already declares, an `alias` included, or one that
575
636
  routes to a signature path it does not write to, because it is declared there or its class lives there) on its own `skipped Class#method (reason)` line; the exit status is
576
637
  unchanged. See [handbook chapter 11](../handbook/11-sig-gen.md#safety-what---write-will-and-will-not-do).
@@ -614,7 +675,13 @@ would take every other type in the file down with it. Under
614
675
  **refused** (the existing file is left untouched) and the command
615
676
  exits `1`: you asked for a write and did not get one. Such a skip
616
677
  is a bug in Rigor's RBS rendering, not in your code — please
617
- report it.
678
+ report it. An existing target that does not parse as RBS in the
679
+ first place, and that sig-gen would write into, is refused the same
680
+ way, named on stdout as `REFUSED` with the parse location (the JSON
681
+ action is `skipped_unparseable`); `--check` fails on it too, so such a
682
+ target is never reported as up to date. An unparseable `sig/` file that
683
+ no emittable candidate maps to is not refused and still passes
684
+ `--check`. Fix its syntax error and re-run.
618
685
 
619
686
  A method the generator declined for any other reason (a body
620
687
  that proves only `untyped`, a user-authored declaration it will
@@ -634,10 +701,20 @@ Run the Language Server over stdio. See
634
701
  rigor lsp [--transport=stdio] [--log=PATH] [--config=PATH]
635
702
  ```
636
703
 
637
- `stdio` is the only transport in v1. `--log=PATH` is accepted
638
- but not yet wired in this release — it is reserved for routing
639
- the server's wire log to a file; until then logging stays on
640
- stderr.
704
+ `stdio` is the only transport in v1. `--log=PATH` appends the LSP
705
+ wire log to PATH: each JSON-RPC message the client sends (`<-`) and
706
+ each one the server writes (`->`), one line per message with a
707
+ timestamp, plus the server's debug output from the start of the
708
+ server loop to its end. The file is opened for appending before the
709
+ server starts, so an unwritable PATH exits with a usage error. An
710
+ exception that escapes the loop is written to the file with its
711
+ backtrace. Output written before the loop starts (configuration
712
+ loading and server setup) and any crash outside the loop still go to
713
+ stderr, as does the server's output when the file becomes unwritable
714
+ mid-session. A message the JSON encoder rejects (raw invalid UTF-8) is
715
+ logged in a scrubbed form. Without `--log`, no wire log is written and
716
+ the server's debug output goes to stderr; stdout carries only the
717
+ protocol.
641
718
 
642
719
  ## `rigor baseline`
643
720
 
@@ -661,6 +738,12 @@ rigor baseline <generate|regenerate|dump|drift|prune> [options]
661
738
  `--baseline=PATH` to read a non-default baseline file. `dump`
662
739
  also accepts `--format=text|json` for machine-readable output.
663
740
 
741
+ No subcommand takes a positional path: `generate` and `regenerate`
742
+ analyse the configured `paths:`, `drift` and `prune` audit them, and
743
+ `dump` reads the baseline file and filters with `--rule`/`--file`. A path argument is a usage error
744
+ (exit `64`). Narrow the scope with `paths:` in the configuration, not on
745
+ the command line.
746
+
664
747
  ## `rigor triage`
665
748
 
666
749
  Summarise a diagnostic stream — rule distribution, **class/method
@@ -1011,15 +1094,22 @@ toolchain, so you can read a real, working plugin as a worked
1011
1094
  example when authoring your own.
1012
1095
 
1013
1096
  ```sh
1014
- rigor plugin <list|path|print|root> [name]
1097
+ rigor plugin [<name>] [--path <name>] [--list] [--root]
1015
1098
  ```
1016
1099
 
1017
- | Subcommand | Purpose |
1100
+ The positional slot is a plugin *name*; alternative outputs are
1101
+ flags, the same grammar as `rigor skill` and `rigor docs`.
1102
+
1103
+ | Form | Purpose |
1018
1104
  | --- | --- |
1019
- | `list` | Table of every bundled plugin and example, name + absolute directory path (default when no subcommand given). |
1020
- | `path <name>` | One-line absolute path to the plugin's directory. |
1021
- | `print <name>` | A header (dir / lib / sig / README paths) followed by the plugin's main source body inlined. |
1022
- | `root` | The `rigortype` gem root and its key subdirectories. |
1105
+ | `rigor plugin`, `--list` | Table of every bundled plugin and example, name + absolute directory path (the default). |
1106
+ | `<name>` | A header (dir / lib / sig / README paths) followed by the plugin's main source body inlined. |
1107
+ | `--path <name>` | One-line absolute path to the plugin's directory. |
1108
+ | `--root` | The `rigortype` gem root and its key subdirectories. |
1109
+
1110
+ The earlier verbs `list`, `path <name>`, `print <name>` and `root`
1111
+ still work for one minor release and print a deprecation notice on
1112
+ stderr naming the replacement; the output on stdout is the same.
1023
1113
 
1024
1114
  Paths resolve at runtime from the gem location (a documented
1025
1115
  caveat for container / cross-filesystem setups).
@@ -1275,6 +1365,42 @@ rigor upgrade
1275
1365
  Until then it prints the current version and notes that upgrade is
1276
1366
  queued. Exits `0`.
1277
1367
 
1368
+ ## `rigor help`
1369
+
1370
+ ```sh
1371
+ rigor help # the command list
1372
+ rigor help <command> # the same as `rigor <command> --help`
1373
+ rigor help --format=json # every command, its flags and formats
1374
+ ```
1375
+
1376
+ `rigor help --format=json` prints the whole CLI surface as one JSON
1377
+ document, for tools and agents that would otherwise parse the help
1378
+ text:
1379
+
1380
+ ```json
1381
+ { "schema_version": 1, "version": "…", "commands": [ … ] }
1382
+ ```
1383
+
1384
+ Each entry of `commands` has:
1385
+
1386
+ | Key | Value |
1387
+ | --- | --- |
1388
+ | `name` | The command name. |
1389
+ | `summary` | The one-line description `rigor help` prints. |
1390
+ | `usage` | The usage line (`Usage: rigor …`). |
1391
+ | `flags` | The command's own switches, each `{ "name", "arg", "optional", "desc" }`. `arg` is the argument's name, `null` for a switch that takes none, and `optional` is `true` when the argument may be left out (`--bleeding-edge[=LIST]`). `--[no-]x` is listed as the two flags `--x` and `--no-x`. |
1392
+ | `formats` | The values the command's own `--format` accepts, empty when it has none. A command that only has subcommands with the flag (`baseline`) lists none here. |
1393
+ | `subcommands` | One `{ "name", "usage", "flags", "formats" }` object per subcommand (`baseline dump`, `effects check`, `skill describe`), with the same meaning as above and empty arrays where the subcommand has nothing to list. |
1394
+ | `mcp_tool` | The [MCP](10-mcp-server.md) tool that mirrors the command, or `null`. |
1395
+
1396
+ The list covers every command above plus `help` and `version`. The
1397
+ text help is rendered from the same list.
1398
+
1399
+ `rigor help <command>` takes one command: a second word
1400
+ (`rigor help baseline drift`) is refused with exit `64`, and an
1401
+ unknown command exits `64` too. Flags after the command name are
1402
+ ignored rather than parsed.
1403
+
1278
1404
  ## Environment variables
1279
1405
 
1280
1406
  Most behaviour is driven by flags and `.rigor.yml`; a few
@@ -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,7 +227,7 @@ 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`. 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. |