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
@@ -16,7 +16,7 @@ module Rigor
16
16
  #
17
17
  # Scope:
18
18
  #
19
- # - Recognises the standard six controllers (`:sessions`, `:passwords`, `:confirmations`, `:unlocks`,
19
+ # - Recognises the standard controllers (`:sessions`, `:passwords`, `:confirmations`, `:unlocks`,
20
20
  # `:registrations`, `:omniauth_callbacks`).
21
21
  # - Honours `skip: [...]` to suppress controllers the project disables.
22
22
  # - Honours `path: "..."` to remap the URL path (does NOT remap the helper-name prefix — Devise uses
@@ -162,8 +162,7 @@ module Rigor
162
162
  OMNIAUTH_SUFFIXES
163
163
  end
164
164
 
165
- # ADR-39 — delegates to the shared inflector (real `ActiveSupport::Inflector` when available),
166
- # replacing the tiny AS-replica this module used to duplicate from `RoutesParser`.
165
+ # ADR-39 — delegates to the shared inflector (real `ActiveSupport::Inflector` when available).
167
166
  def singularize(word)
168
167
  Rigor::Plugin::Inflector.singularize(word)
169
168
  end
@@ -7,7 +7,7 @@ require "prism"
7
7
  module Rigor
8
8
  module Plugin
9
9
  class RailsRoutes < Rigor::Plugin::Base
10
- # Walks `app/helpers/**/*.rb` (or any configured set of helper directories) and extracts every public
10
+ # Walks the configured `helper_paths:` directories (default `app/`) and extracts every public
11
11
  # module-level method name so the analyzer's `unknown-helper` rule does not false-fire on calls to
12
12
  # project-defined `*_path` / `*_url` helpers (the canonical Rails pattern: a `UrlHelper` module
13
13
  # exposing `full_asset_url`, `host_to_url`, `frontend_asset_url`, and so on).
@@ -409,14 +409,9 @@ module Rigor
409
409
  end
410
410
  end
411
411
 
412
- # ADR-39: inflection delegates to the shared `Rigor::Plugin::Inflector`, which calls the real
413
- # `ActiveSupport::Inflector` (the authority Rails itself uses to generate helper names) when
414
- # available, falling back to a built-in approximation otherwise. This replaces the hand-tuned
415
- # AS-replica these methods used to carry — every special case they accreted (`news` uncountable,
416
- # `media`→`medium`, `databases`→`database`, `custom_css` ss-preservation,
417
- # `async_refresh`→`async_refreshes`) was reverse-engineered `ActiveSupport::Inflector` behaviour,
418
- # so the real inflector produces the same result for those and the correct one for the words the
419
- # replica never covered.
412
+ # ADR-39: inflection delegates to the shared `Rigor::Plugin::Inflector`, which uses the real
413
+ # `ActiveSupport::Inflector` (the authority Rails uses to generate helper names) when available,
414
+ # and a built-in approximation otherwise.
420
415
  def singularize(word)
421
416
  Rigor::Plugin::Inflector.singularize(word)
422
417
  end
@@ -1286,7 +1281,7 @@ module Rigor
1286
1281
  return nil unless plural
1287
1282
 
1288
1283
  # Rails appends `_index_path` to the index helper name when the singular form of the resource
1289
- # matches the plural form AND the noun isn't in the canonical UNCOUNTABLE list. The collision
1284
+ # matches the plural form (uncountable nouns included). The collision
1290
1285
  # would otherwise put both the index (`:id`-less) and show (`:id`-bearing) helpers under the same
1291
1286
  # name, and Rails disambiguates by suffixing the index form. Mastodon's `resources
1292
1287
  # :reblogged_by, controller: :reblogged_by_accounts, only: :index` and similar rely on this —
@@ -1432,8 +1427,6 @@ module Rigor
1432
1427
  end
1433
1428
 
1434
1429
  # ADR-39 — delegates to the shared inflector (real `ActiveSupport::Inflector` when available).
1435
- # Previously a hand-tuned AS-replica kept in sync with `Context#singularize`; both now share the
1436
- # one authoritative implementation.
1437
1430
  def singularize_word(word)
1438
1431
  Rigor::Plugin::Inflector.singularize(word)
1439
1432
  end
@@ -14,7 +14,7 @@ module Rigor
14
14
  # rigor-rails-routes — validates Rails route-helper calls (`users_path`, `edit_user_path(@user)`, …)
15
15
  # against the project's `config/routes.rb`.
16
16
  #
17
- # Tier 1A of the [Rails plugins roadmap](../../../../docs/design/20260508-rails-plugins-roadmap.md).
17
+ # Tier 1A of the [Rails plugins roadmap](../../../../../docs/design/20260508-rails-plugins-roadmap.md).
18
18
  # Statically interprets the routes DSL via Prism — no `rails` runtime dependency. Recognised v0.1.0
19
19
  # surface:
20
20
  #
@@ -35,7 +35,8 @@ module Rigor
35
35
  class RbsInline < Rigor::Plugin::Base
36
36
  # Synthesizer callable invoked once per project Ruby source file by `Environment.for_project` at
37
37
  # env-build time. Returns the synthesised RBS source as a String, or `nil` when the file contributes
38
- # nothing (no magic comment in the default mode, empty annotation set, parse error per WD6).
38
+ # nothing (no annotation in the default mode, no magic comment under `require_magic_comment: true`,
39
+ # or a parse error per WD6).
39
40
  class Synthesizer
40
41
  # An RDoc directive comment: `#:` followed by a bare word (RDoc allows a hyphen, as in `:call-seq:`)
41
42
  # and a closing colon. RDoc predates rbs-inline by about two decades and `#:<name>:` is
@@ -358,7 +358,7 @@ module Rigor
358
358
  # signature, a core ancestor, or the project's source) is kept whole. `Object`, `BasicObject` and a module
359
359
  # String or Symbol includes otherwise read `String | Symbol`, as the String they most often are, so a String
360
360
  # method called after the matcher reports nothing. Any other module is UNDECIDED: a passing value may include
361
- # it without being a String. (`approval-queue.d/regexp-match-open-surface-subject.md`, shared with #2256.)
361
+ # it without being a String. (Decision shared with #2256.)
362
362
  def module_match_member(member, scope)
363
363
  return nil unless member.is_a?(Rigor::Type::Nominal) && member.type_args.empty?
364
364
 
@@ -14,7 +14,7 @@ module Rigor
14
14
  # narrows `expect(x).to MATCHER` assertions downstream, and binds `let`/`subject` locals to their
15
15
  # inferred return types inside `it` bodies.
16
16
  #
17
- # Tier 3A of the [Rails plugins roadmap](../../../../docs/design/20260508-rails-plugins-roadmap.md).
17
+ # Tier 3A of the [Rails plugins roadmap](../../../../../docs/design/20260508-rails-plugins-roadmap.md).
18
18
  # Ships four checks:
19
19
  #
20
20
  # 1. **Duplicate `let` / `subject` declarations** in the same scope (`warning`). RSpec's runtime lets
@@ -44,7 +44,7 @@ module Rigor
44
44
  # - **No shared-context resolution.** `include_context`, `shared_context`, and `it_behaves_like` are
45
45
  # recognised as scope-opening calls but their declarations are not pulled into the host scope.
46
46
  # - **Constant validation is not done here.** `RSpec.describe SomeClass do` does not validate
47
- # `SomeClass`; the engine's `inference.unresolved-constant` already catches that.
47
+ # `SomeClass`; the engine's constant-resolution diagnostics already catch that.
48
48
  class Rspec < Rigor::Plugin::Base
49
49
  manifest(
50
50
  id: "rspec",
@@ -30,7 +30,7 @@ module Rigor
30
30
  # - `route_to(...)` / `redirect_to(...)` — needs the routes table from `rigor-rails-routes`
31
31
  # (`:helper_table` cross-plugin fact). Future slice.
32
32
  # - `have_enqueued_job(JobClass)` / `have_enqueued_mail(MailerClass)` — class-existence check overlaps
33
- # with engine's `inference.unresolved-constant`; queued behind a decision on which surface owns the
33
+ # with the engine's constant-resolution diagnostics; queued behind a decision on which surface owns the
34
34
  # diagnostic.
35
35
  # - `have_received(:method)` — overlaps with engine's `call.undefined-method`; same coordination
36
36
  # question.
@@ -44,9 +44,8 @@ module Rigor
44
44
  description: "Validates rspec-rails behavioral matchers (have_http_status floor)."
45
45
  )
46
46
 
47
- # ADR-37 — the engine walks each file and hands us every CallNode; the analyzer is now pure per-node
48
- # logic. The diagnostic points at the matcher name (`message_loc`), reproducing the previous
49
- # `message_loc || location` positioning.
47
+ # ADR-37 — the engine walks each file and hands us every CallNode; the analyzer is pure per-node
48
+ # logic. The diagnostic points at the matcher name (`message_loc`).
50
49
  node_rule Prism::CallNode do |node, _scope, path|
51
50
  violation = HaveHttpStatusAnalyzer.violation_for(node)
52
51
  next [] unless violation
@@ -31,7 +31,7 @@ module Rigor
31
31
  # have_db_index(:col)
32
32
  #
33
33
  # All look up `:col` against the model's `columns:` (the published `:model_index` fact's flat
34
- # `Array<String>` per model — see `Analyzer#column_names`). Unknown columns fire `unknown-column`.
34
+ # name list per model — see `Analyzer#column_names`). Unknown columns fire `unknown-column`.
35
35
  #
36
36
  # ### Association matchers
37
37
  #
@@ -107,8 +107,7 @@ module Rigor
107
107
 
108
108
  # A direct matcher invocation is a `CallNode` whose `name` is in `MATCHER_TABLE` and whose first
109
109
  # argument is a `SymbolNode`. The chain shape (`should`, `expect(...).to`, `is_expected.to`) is
110
- # irrelevant — we always recurse to the inner matcher, so a diagnostic fires on the matcher
111
- # regardless of the surrounding chain.
110
+ # irrelevant: the engine visits the inner matcher call, so the diagnostic fires on it regardless.
112
111
  def matcher_invocation?(node)
113
112
  node.is_a?(Prism::CallNode) && MATCHER_TABLE.key?(node.name) && symbol_first_arg?(node)
114
113
  end
@@ -172,11 +171,10 @@ module Rigor
172
171
  end
173
172
 
174
173
  # `entry` is the published fact's per-model Hash: `{ table:, columns:, associations:, ... }`.
175
- # `columns:` is a flat `Array<String>` (`Activerecord#index_to_published_hash`); `associations:` is
176
- # `Array<Hash>` with String `:name`, Symbol `:kind` (`:singular` | `:collection`), `:nullable`,
177
- # `:polymorphic`, and a `:target` that is a String except on polymorphic rows, where it is nil —
178
- # the row shape `ModelDiscoverer#build_association_row` publishes. Only `:name` and `:kind` are
179
- # read here.
174
+ # `columns:` is a flat list of column names (`Activerecord#index_to_published_hash`); `associations:`
175
+ # is a list of rows with `:name`, `:kind` (`:singular` | `:collection`), `:nullable`, `:polymorphic`,
176
+ # and a `:target` that is nil on polymorphic rows — the row shape `ModelDiscoverer#build_association_row`
177
+ # publishes. Only `:name` and `:kind` are read here.
180
178
  def column_names(entry) = entry[:columns] || []
181
179
  def association_rows(entry) = entry[:associations] || []
182
180
  def find_association(entry, name) = association_rows(entry).find { |a| a[:name] == name.to_s }
@@ -98,9 +98,10 @@ module Rigor
98
98
  # Cached separately from `:worker_index` because the two invalidate on different files: editing a
99
99
  # schedule changes which workers are reached without touching `app/workers` at all. The `watch:` roots
100
100
  # the glob at the working directory so that CREATING a schedule file — not just editing one — is seen. Each
101
- # configured path is escaped to a literal name, so `sidekiq[1].yml` is watched as that file.
101
+ # configured path is escaped to a literal name, so `sidekiq[1].yml` is watched as that file, and an
102
+ # absolute path is watched under its own directory (`RootedGlob.split_file`, #2222).
102
103
  producer :scheduled_workers,
103
- watch: -> { [[".", *@schedule_paths.map { |path| Rigor::RootedGlob.literal(path) }]] } do |_params|
104
+ watch: -> { @schedule_paths.map { |path| Rigor::RootedGlob.split_file(path) } } do |_params|
104
105
  ScheduleScan.new(
105
106
  io_boundary: io_boundary,
106
107
  schedule_paths: @schedule_paths
@@ -31,11 +31,10 @@ module Rigor
31
31
  # behaviour: it always raises). This lets the engine's existing flow analysis treat code after
32
32
  # `T.absurd` as unreachable, matching what users of Sorbet expect.
33
33
  # - When the branch is REACHABLE (the discriminant's type isn't `bot`), the recogniser also records
34
- # the call node in a per-plugin set. The plugin's `diagnostics_for_file` later walks the AST for
35
- # `T.absurd` calls and emits a `plugin.sorbet.absurd-reachable` warning at every call_node whose
36
- # object identity matches the recorded set. We rely on the runner only parsing each file once per
37
- # run, so the same Prism node object is seen in both the `dynamic_return` rule and
38
- # `diagnostics_for_file`.
34
+ # the call node in a per-plugin set. The plugin's node rule later emits a
35
+ # `plugin.sorbet.absurd-reachable` warning for each recorded call node, matched by object identity.
36
+ # We rely on the runner only parsing each file once per run, so the same Prism node object is seen
37
+ # in both the `dynamic_return` rule and the node rule.
39
38
  module AbsurdRecognizer
40
39
  # @return true when `call_node` is `T.absurd(x)`.
41
40
  def self.absurd_call?(call_node)
@@ -21,7 +21,7 @@ module Rigor
21
21
  # | `T.unsafe(x)` | return type ← `Dynamic[top]` |
22
22
  # | `T.reveal_type(expr)` | return type ← `inferred(expr)` (passes through) |
23
23
  # | `T.assert_type!(expr, T)` | return type ← translated `T` + static subtype check |
24
- # | `T.bind(self, T)` | return type ← `Constant[nil]` + post_return_fact narrowing self to translated `T` |
24
+ # | `T.bind(self, T)` | return type ← `Constant[nil]` + `self` narrowing fact |
25
25
  #
26
26
  # The Sorbet runtime's `T.let` / `T.cast` actually return the inner expression unchanged at runtime;
27
27
  # their job is purely to *assert* a static type. From Rigor's static perspective the simplest
@@ -36,7 +36,7 @@ module Rigor
36
36
  # `T.reveal_type` is "diagnostic-only" in Sorbet: it passes the value through unchanged at runtime
37
37
  # AND surfaces the inferred static type as a build-time message. The recogniser contributes the
38
38
  # inferred type (so chained call sites still resolve as if the `T.reveal_type` wrapper weren't
39
- # there); the plugin's `diagnostics_for_file` hook surfaces the `plugin.sorbet.reveal-type` `:info`
39
+ # there); the plugin's node rule surfaces the `plugin.sorbet.reveal-type` `:info`
40
40
  # message for human consumption.
41
41
  #
42
42
  # `T.bind(self, T)` is recognised as block-scope self narrowing. The recogniser returns a
@@ -113,9 +113,9 @@ module Rigor
113
113
 
114
114
  # `T.assert_type!(expr, T)` shares the typed-assertion contribution shape with `T.cast` (return is
115
115
  # the asserted type), so the recogniser delegates the return-type half through
116
- # `resolve_typed_assertion`. The static subtype check that distinguishes `assert_type!` from
117
- # `cast` lives in the plugin's `diagnostics_for_file` hook (mirroring the absurd-recognizer
118
- # pattern: record the call here, emit the diagnostic from the per-file walker).
116
+ # `resolve_typed_assertion`. The static subtype check that distinguishes `assert_type!` from `cast`
117
+ # is run by the plugin's `record_assert_type_check`, which records a mismatch for the node rule to
118
+ # emit (the absurd-recognizer pattern).
119
119
  def assert_type_check(call_node, scope)
120
120
  return nil if scope.nil?
121
121
 
@@ -135,8 +135,7 @@ module Rigor
135
135
  # `T.reveal_type(expr)` returns `expr` unchanged at runtime; the Sorbet-side semantics is "make
136
136
  # the inferred static type visible to the user." The contribution mirrors `T.must` minus the
137
137
  # nil-stripping: the call's return type is the inner expression's inferred type. The companion
138
- # diagnostic is emitted by the plugin's `diagnostics_for_file` hook; this method handles the
139
- # contribution only.
138
+ # diagnostic is emitted by a node rule; this method handles the contribution only.
140
139
  def resolve_reveal_type(call_node, scope)
141
140
  inner = nth_argument(call_node, 0)
142
141
  return Rigor::Type::Combinator.untyped if inner.nil? || scope.nil?
@@ -57,7 +57,7 @@ module Rigor
57
57
  @entries[key_for(class_name, method_name, kind)]
58
58
  end
59
59
 
60
- # @return frozen mapping `{ include: [...], extend: [...] }`.
60
+ # @return frozen `:include` / `:extend` buckets of mixed-in module names.
61
61
  # Returns {EMPTY_MIXINS} when no mixins were recorded.
62
62
  def mixins_for(class_name)
63
63
  @mixins[class_name] || EMPTY_MIXINS
@@ -22,8 +22,8 @@ module Rigor
22
22
  #
23
23
  # The parser walks the chain right-to-left, gathering whatever it recognises (`params` / `returns` /
24
24
  # `void` / `abstract` / `override` / `overridable` / `final` / `type_parameters` / `checked` /
25
- # `on_failure`) into a frozen result hash stored in {MethodSignature}. Modifiers and
26
- # `type_parameters` are recorded but not yet acted on.
25
+ # `on_failure`) into a frozen result hash stored in {MethodSignature}. Modifiers are recorded but not
26
+ # yet acted on; `type_parameters` is recognised and its payload discarded.
27
27
  #
28
28
  # The parser is intentionally tolerant — unknown chain nodes degrade to "the rest of the chain is
29
29
  # opaque" rather than raising. The plugin emits a diagnostic (`plugin.sorbet.parse-error`) only when
@@ -42,8 +42,8 @@ module Rigor
42
42
  # `IoBoundary`. The catalog is frozen after the first build and consulted by the `dynamic_return` rule
43
43
  # at every gated call site. RBI files share the catalog with project-source sigs — both produce
44
44
  # `MethodSignature` entries keyed by `(class_name, method_name, kind)`. When a key collides across
45
- # files, the last-walked sig wins (ordering is platform-dependent: `Dir.glob` returns directory
46
- # entries in filesystem order). Sorbet's full shim-override semantics — `sorbet/rbi/shims/` overriding
45
+ # files, the last-walked sig wins (`Dir.glob` results are sorted, so the walk order is stable).
46
+ # Sorbet's full shim-override semantics — `sorbet/rbi/shims/` overriding
47
47
  # `sorbet/rbi/gems/` — lands in a later slice once the catalog gains per-source provenance.
48
48
  #
49
49
  # The plugin emits `plugin.sorbet.parse-error` warnings for malformed sig blocks (no block / empty
@@ -79,12 +79,10 @@ module Rigor
79
79
  "rbi_paths" => { kind: :array, default: ["sorbet/rbi"] },
80
80
  "enforce_sigil" => { kind: :boolean, default: true }
81
81
  },
82
- # #1097 — bundled sorbet-runtime DSL surface (`sig/sorbet.rbs`). `sig` itself reaches the
83
- # class object through `extend T::Sig` (or the `< T::ImmutableStruct` / `< T::Enum`
84
- # family, which carry the edge themselves), which is
85
- # why `rbs_complete_extends:` — the extend-edge allow-list — carries `T::Sig` /
86
- # `T::Helpers` / `T::Generic`, and `rbs_complete_ancestors:` carries the Struct/Enum family
87
- # for the subclass bridge.
82
+ # #1097 — bundled sorbet-runtime DSL surface (`sig/sorbet.rbs`). `sig` reaches the class object
83
+ # through `extend T::Sig` (or the `T::Struct` / `T::Enum` family, which carry the edge themselves):
84
+ # `rbs_complete_extends:` is that extend-edge allow-list, and `rbs_complete_ancestors:` carries the
85
+ # Struct/Enum family for the subclass bridge.
88
86
  signature_paths: ["sig"],
89
87
  rbs_complete_extends: %w[T::Sig T::Helpers T::Generic],
90
88
  rbs_complete_ancestors: %w[T::Struct T::InexactStruct T::ImmutableStruct T::Enum],
@@ -122,8 +120,7 @@ module Rigor
122
120
  @configured_paths = Array(config.fetch("paths", services.configuration.paths)).map(&:to_s)
123
121
  @rbi_paths = Array(config.fetch("rbi_paths")).map(&:to_s)
124
122
  # Schema default `true` — only files marked `# typed: true` / `:strict` / `:strong` contribute
125
- # their sigs. Set to `false` to record every file's sigs regardless of sigil (current behaviour
126
- # pre-this-config).
123
+ # their sigs. Set to `false` to record every file's sigs regardless of sigil.
127
124
  @enforce_sigil = config.fetch("enforce_sigil")
128
125
  # Per-call-site assertion gating: `@sigil_by_path` (built during catalog harvest) is consulted so
129
126
  # `T.let` / `T.cast` / `T.must` / `T.bind` / `T.assert_type!` only fire in files Sorbet itself
@@ -135,20 +132,20 @@ module Rigor
135
132
  @parse_errors_by_path = {}
136
133
  @catalog_built = false
137
134
  # ADR-11 slice 6 — Prism nodes for `T.absurd` calls we observed in the `dynamic_return` rule to be
138
- # *reachable* (i.e., their discriminant didn't narrow to `bot`). `diagnostics_for_file` walks the
139
- # per-file AST and surfaces these as `plugin.sorbet.absurd-reachable` warnings. Hash is keyed on
140
- # the Prism node's `object_id` because the runner only parses each file once per run, so identity
141
- # is stable across the two plugin hooks.
135
+ # *reachable* (i.e., their discriminant didn't narrow to `bot`). The node rule below surfaces these
136
+ # as `plugin.sorbet.absurd-reachable` warnings. The Hash is keyed by node identity
137
+ # (`compare_by_identity`) because the runner only parses each file once per run, so identity is
138
+ # stable across the two plugin hooks.
142
139
  @reachable_absurd_nodes = {}.compare_by_identity
143
140
  # ADR-11 light follow-up — `T.reveal_type` calls observed in the `dynamic_return` rule, paired
144
141
  # with the display string for the inferred type at the call site. Mirrors the absurd-node
145
- # compare-by-identity hash; `diagnostics_for_file` surfaces each entry as a
142
+ # compare-by-identity hash; the node rule below surfaces each entry as a
146
143
  # `plugin.sorbet.reveal-type` `:info` diagnostic.
147
144
  @reveal_type_calls = {}.compare_by_identity
148
145
  # T.bind / T.assert_type! priority slice 1 — `T.assert_type!` calls observed in the
149
146
  # `dynamic_return` rule whose static subtype check FAILED, paired with the inferred + asserted
150
- # type display strings. Same compare-by-identity discipline. `diagnostics_for_file` walks the
151
- # file AST for `T.assert_type!` calls and surfaces matching entries as
147
+ # type display strings. Same compare-by-identity discipline. The node rule below surfaces matching
148
+ # entries as
152
149
  # `plugin.sorbet.assert-type-mismatch` `:error` diagnostics.
153
150
  @assert_type_mismatches = {}.compare_by_identity
154
151
  end
@@ -245,7 +242,7 @@ module Rigor
245
242
  # analysis treats code after it as unreachable; the legacy contribution's `exceptional: :raises`
246
243
  # slot was never consumed on the dispatcher path — only `return_type` survives the merge). When
247
244
  # the discriminant *isn't* narrowed to `bot` at this scope, also records the call node so
248
- # `diagnostics_for_file` can surface a `plugin.sorbet.absurd-reachable` warning.
245
+ # the node rule can surface a `plugin.sorbet.absurd-reachable` warning.
249
246
  if AbsurdRecognizer.absurd_call?(call_node)
250
247
  @reachable_absurd_nodes[call_node] = true unless AbsurdRecognizer.exhaustive?(call_node, scope)
251
248
  return Rigor::Type::Combinator.bot
@@ -255,7 +252,7 @@ module Rigor
255
252
  # cheaper to recognise (no catalog walk required) and they win over any cataloged signature: the
256
253
  # user explicitly asserted the type at the call site. The light follow-up extends the recogniser
257
254
  # to `T.must_because` (alias of `T.must`) and `T.reveal_type` (passes the type through; the
258
- # human-facing diagnostic is recorded here for `diagnostics_for_file` to emit).
255
+ # human-facing diagnostic is recorded here for the node rule to emit).
259
256
  #
260
257
  # Per-call-site sigil gating: with `enforce_sigil: true` (default), assertions only fire in files
261
258
  # Sorbet itself would enforce. Files at `# typed: false` (or sigil-less, which Sorbet treats as
@@ -502,8 +499,7 @@ module Rigor
502
499
  # ADR-88 WD2 — deterministic fold order matters now that the catalog VALUE is digested into the
503
500
  # incremental fact-surface fingerprint: a duplicate `(class, method, kind)` sig's last-wins winner
504
501
  # must not vary by machine / run. `Dir.glob` sorts its results by default on Ruby 3.0+, so the fold
505
- # order is already stable (the slice-1 comment's "filesystem order" caveat predates that default);
506
- # this walk relies on it rather than re-sorting.
502
+ # order is already stable; this walk relies on it rather than re-sorting.
507
503
  Dir.glob("**/*#{ext}", base: absolute).each do |relative|
508
504
  harvest_file(canonicalize(File.join(absolute, relative)), catalog, sigil_by_path, parse_errors_by_path)
509
505
  end
@@ -11,9 +11,8 @@ module Rigor
11
11
  #
12
12
  # 1. **Collect pass.** Walk the file once to gather every state name declared inside a
13
13
  # `state_machine do ... end` block (`state :draft`, `state :submitted`, ...).
14
- # 2. **Validate pass.** Walk the file again, validating each `transition_to(:sym)` and `event :sym`
15
- # reference against the collected state set. Levenshtein distance ≤ 3 drives the did-you-mean
16
- # suggestions.
14
+ # 2. **Validate pass.** Walk the file again, validating each `transition_to(:sym)` reference against
15
+ # the collected state set. `DidYouMean::SpellChecker` drives the did-you-mean suggestions.
17
16
  #
18
17
  # Useful for `aasm` / `statesman` / hand-rolled DSLs and any framework where declarations and uses
19
18
  # live in the same file. The same skeleton lifts to GraphQL types, ActiveModel validations, route
@@ -10,8 +10,9 @@ require_relative "serializer_index"
10
10
  module Rigor
11
11
  module Plugin
12
12
  class Typelizer < Rigor::Plugin::Base
13
- # Reads every `.rb` file under the configured typelizer dirs and the project's `paths:` through the plugin's `IoBoundary`, runs a
14
- # {SerializerCollector} over each and merges the results into one {SerializerIndex}.
13
+ # Reads every `.rb` file under the configured typelizer dirs and the project's `paths:` through the
14
+ # plugin's `IoBoundary`, runs a {SerializerCollector} over each and merges the results into one
15
+ # {SerializerIndex}.
15
16
  class SerializerDiscoverer
16
17
  def initialize(io_boundary:, dirs:, project_paths: [])
17
18
  @io_boundary = io_boundary
@@ -3,16 +3,16 @@
3
3
  module Rigor
4
4
  module Plugin
5
5
  class Typelizer < Rigor::Plugin::Base
6
- # The project's classes (every file of the project's `paths:` and the typelizer `dirs`), as the {SerializerCollector} found them. A frozen value
7
- # object the `:serializer_index` producer caches.
6
+ # The project's classes (every file of the project's `paths:` and the typelizer `dirs`), as the
7
+ # {SerializerCollector} found them. A frozen value object the `:serializer_index` producer caches.
8
8
  #
9
9
  # {#roots} answers the one question, conservatively — a wrong "no" costs a missing root, a wrong "yes"
10
10
  # hides a dead class.
11
11
  class SerializerIndex
12
12
  # A class the project declares. `superclass` is the constant as written (`nil` when absent) and
13
13
  # `nesting` the `Module.nesting` chain (innermost first) its header was written under, which is where
14
- # Ruby resolves that constant. `dsl` is true when its body includes or extends `Typelizer::DSL`; `in_dirs` when a file under the
15
- # typelizer `dirs` declares it (only such a class is published as a root).
14
+ # Ruby resolves that constant. `dsl` is true when its body includes or extends `Typelizer::DSL`;
15
+ # `in_dirs` when a file under the typelizer `dirs` declares it (only such a class is published as a root).
16
16
  ClassEntry = Data.define(:name, :superclass, :nesting, :dsl, :in_dirs)
17
17
 
18
18
  def initialize(classes:)
@@ -66,9 +66,6 @@ module Rigor
66
66
 
67
67
  # Always an Array of Bucket — `initialize` freezes what it is given and every caller hands it
68
68
  # Bucket instances (parse_row rows, from_diagnostics groups, `without`'s reject, or `[]`).
69
- # sig-gen gap: #2321 — `initialize` assigns `buckets.freeze`, an expression derived from the
70
- # parameter rather than a bare read of it, so the reader is skipped (untyped return) and the element type
71
- # is hand-written.
72
69
  attr_reader buckets: Array[Bucket]
73
70
 
74
71
  # `project_root` is `Pathname.new`'d, so a `Pathname` is accepted as well as a `String`
@@ -3,6 +3,7 @@ module Rigor
3
3
  module CheckRules
4
4
  def self?.diagnose: (path: String, root: untyped, scope_index: Hash[untyped, Scope]) -> Array[Diagnostic]
5
5
  def self?.build_node_collectors: (String path, untyped scope_index, ?untyped root) -> Hash[Symbol, untyped]
6
+ # authored: returns Rigor::Analysis::CheckRules::RuleWalk::CollectorDriver, which has no RBS declaration yet (#2404); stays `untyped` until it is declared
6
7
  def self?.node_collector_driver: (Hash[Symbol, untyped] collectors) -> untyped
7
8
  def self?.shadow_verify_converged_collectors: (String path, untyped root, untyped scope_index, Hash[Symbol, untyped]? collectors) -> void
8
9
  def self?.filter_suppressed: (Array[Diagnostic] diagnostics, comments: untyped, disabled_rules: Array[String]) -> Array[Diagnostic]
@@ -3,7 +3,7 @@ module Rigor
3
3
  module Reachability
4
4
  class ScanCache
5
5
  def self.open: (untyped, ?target_ruby: untyped) -> Rigor::Analysis::Reachability::ScanCache
6
- def self.source_digest: () -> String
6
+ def self.source_digest: () -> ("unreadable" | String)
7
7
  def initialize: (path: untyped, header: untyped) -> void
8
8
  def save: () -> nil
9
9
  end
@@ -1,4 +1,4 @@
1
1
  class Rigor::CLI::DiffCommand
2
2
  def initialize: (argv: untyped, out: untyped, err: untyped) -> void
3
- def run: () -> (0 | 1 | Integer)
3
+ def run: () -> (0 | 1 | 64)
4
4
  end
@@ -1,4 +1,4 @@
1
1
  class Rigor::CLI::ExplainCommand
2
2
  def initialize: (argv: untyped, out: untyped, err: untyped) -> void
3
- def run: () -> (0 | Integer)
3
+ def run: () -> (0 | 64)
4
4
  end
@@ -33,7 +33,7 @@ module Rigor
33
33
  def self.load_file: (untyped path) -> Registry
34
34
 
35
35
  attr_reader vocabulary_version: Integer
36
- # sig-gen gap: #2321 — `labels:` is declared `Array[untyped]` (input `untyped`, looks deliberate; a declared
36
+ # sig-gen gap: #2325 — `labels:` is declared `Array[untyped]` (input `untyped`, looks deliberate; a declared
37
37
  # type is authoring intent tracked by #2325, and only then does this row generate; coercion-facing:
38
38
  # `map(&:to_s)`); every declared label, sorted. Implied ancestors are recognised by {#known?} but not listed
39
39
  # here.
@@ -42,7 +42,7 @@ module Rigor
42
42
  # Unmarked residue: `@roots` is computed from `@known` in `initialize`, not assigned from a
43
43
  # parameter, so no parameter credit applies.
44
44
  attr_reader roots: Array[String]
45
- # sig-gen gap: #2321 — `descriptions:` is declared `Hash[untyped, untyped]` (input `untyped`, looks deliberate;
45
+ # sig-gen gap: #2325 — `descriptions:` is declared `Hash[untyped, untyped]` (input `untyped`, looks deliberate;
46
46
  # a declared type is authoring intent tracked by #2325, and only then does this row generate; coercion-facing:
47
47
  # keys and texts `to_s`'d); one line per root, from the data file's `descriptions:`.
48
48
  attr_reader descriptions: Hash[String, String]
@@ -3,7 +3,8 @@ module Rigor
3
3
  type closure_classification = :non_escaping | :escaping | :unknown
4
4
 
5
5
  class ExpressionTyper
6
- def self.harvest_return_memo: () -> Hash[untyped, Array[untyped]]
6
+ def self.harvest_return_memo: (?Hash[untyped, Hash[untyped, untyped]]? bucket) -> Hash[untyped, Array[untyped]]
7
+ def self.current_return_memo: () -> Hash[untyped, Hash[untyped, untyped]]?
7
8
  def initialize: (scope: Scope, ?tracer: FallbackTracer?) -> void
8
9
  def type_of: (untyped node) -> Type::t
9
10
  def return_type_for: (untyped def_node, Type::t receiver, Array[Type::t] arg_types) -> Type::t?
@@ -172,6 +173,8 @@ module Rigor
172
173
  def self?.collect_class_decls: (untyped node, Array[String] qualified_prefix, Hash[String, Type::t] accumulator) -> void
173
174
  def self?.constant_writes_for_file: (untyped root) -> Hash[String, untyped]
174
175
  def self?.constant_literal_value: (untyped rvalue) -> Array[untyped]?
176
+ # authored: the `Set[String]` values are source paths by contract; a Set's element type stays `untyped` in sig-gen (#2177, #2317), so the generator
177
+ # would never propose it.
175
178
  def self?.finalize_constant_writes: (Hash[String, Hash[String, untyped]] writes) -> [Hash[String, Type::Constant], Hash[String, Set[String]]]
176
179
  def self?.propagate: (untyped node, Hash[untyped, Scope] table, Scope parent_scope) -> void
177
180
 
@@ -4,15 +4,10 @@ module Rigor
4
4
  # the read-before-write nil soundness gate does not widen their ivars. See
5
5
  # lib/rigor/plugin/additional_initializer.rb for the full contract.
6
6
  class AdditionalInitializer
7
- # sig-gen gap: #2321 — `attr_reader` rows over ivars `initialize` assigns from expressions derived from its
8
- # parameters are skipped (untyped return); the constructor validates `receiver_constraint` is a non-empty String
9
- # and normalizes both lists with `map(&:to_sym)`, so the stored types are hand-written.
10
7
  attr_reader receiver_constraint: String
11
8
 
12
- # sig-gen gap: #2321 — as above.
13
9
  attr_reader methods: Array[Symbol]
14
10
 
15
- # sig-gen gap: #2321 — as above.
16
11
  attr_reader block_methods: Array[Symbol]
17
12
 
18
13
  # The validators accept Symbol or non-empty String elements; `initialize` normalizes to
@@ -84,5 +84,6 @@ class Rigor::Plugin::Base
84
84
  def effect_ancestry: () -> Array[Rigor::Plugin::EffectAncestry]
85
85
  def io_boundary: () -> Rigor::Plugin::IoBoundary
86
86
  def cache_for: (untyped producer_id, ?params: untyped, ?descriptor: untyped) -> Proc
87
+ # authored: returns Rigor::Cache::Descriptor::PluginEntry, which has no RBS declaration yet (#2404); stays `untyped` until it is declared
87
88
  def plugin_entry: () -> untyped
88
89
  end
@@ -19,7 +19,7 @@ module Rigor
19
19
  # intent tracked by #2325, and only then does this row generate; coercion-facing: `to_s`), and `initialize`
20
20
  # stores the private `validate_receiver!` helper's result (#2312).
21
21
  attr_reader receiver: String
22
- # sig-gen gap: #2321 — `method:` is `untyped` (input `untyped`, looks deliberate; a declared type is authoring
22
+ # sig-gen gap: #2325 — `method:` is `untyped` (input `untyped`, looks deliberate; a declared type is authoring
23
23
  # intent tracked by #2325, and only then does this row generate; coercion-facing: `String | Symbol`), stored as
24
24
  # `method.to_sym`.
25
25
  attr_reader method: Symbol
@@ -28,7 +28,7 @@ module Rigor
28
28
  # intent tracked by #2325, and only then does this row generate; coercion-facing: `Array()` of anything
29
29
  # answering `to_s`), stored from the private `normalize_labels` helper (#2312).
30
30
  attr_reader labels: Array[String]
31
- # sig-gen gap: #2321 — `narrow:` is `untyped` (input `untyped`, looks deliberate; a declared type is authoring
31
+ # sig-gen gap: #2325 — `narrow:` is `untyped` (input `untyped`, looks deliberate; a declared type is authoring
32
32
  # intent tracked by #2325, and only then does this row generate; coercion-facing: `to_s`, nil kept).
33
33
  attr_reader narrow: String?
34
34
  attr_reader discharge: bool
@@ -41,7 +41,7 @@ module Rigor
41
41
  # intent tracked by #2325, and only then does this row generate; coercion-facing: `to_s`), stored from the
42
42
  # private `validate_taint!` helper (#2312).
43
43
  attr_reader taint: String?
44
- # sig-gen gap: #2321 — `callee:` is `untyped` (input `untyped`, looks deliberate; a declared type is authoring
44
+ # sig-gen gap: #2325 — `callee:` is `untyped` (input `untyped`, looks deliberate; a declared type is authoring
45
45
  # intent tracked by #2325, and only then does this row generate; coercion-facing: `to_s`, nil kept).
46
46
  attr_reader callee: String?
47
47
  # sig-gen gap: #2312 — `callee_fallbacks:` is `untyped` (input `untyped`, looks deliberate; a declared type is
@@ -11,7 +11,7 @@ module Rigor
11
11
  # intent tracked by #2325, and only then does this row generate; coercion-facing: `to_s`), and `initialize`
12
12
  # stores the result of the private `validate_receiver!` helper (#2312).
13
13
  attr_reader receiver: String
14
- # sig-gen gap: #2321 — `method:` is `untyped` (input `untyped`, looks deliberate; a declared type is authoring
14
+ # sig-gen gap: #2325 — `method:` is `untyped` (input `untyped`, looks deliberate; a declared type is authoring
15
15
  # intent tracked by #2325, and only then does this row generate; coercion-facing: `String | Symbol | nil`),
16
16
  # stored as `method&.to_sym`.
17
17
  attr_reader method: Symbol?
@@ -4,15 +4,15 @@ module Rigor
4
4
  # framework entry points (ADR-103 WD6). Frozen value object; {#globs} are deduplicated and
5
5
  # sorted in `initialize`.
6
6
  class EffectEntryPoints
7
- # sig-gen gap: #2321 — `initialize` declares `name:` `untyped` (input `untyped`, looks deliberate; a declared
7
+ # sig-gen gap: #2325 — `initialize` declares `name:` `untyped` (input `untyped`, looks deliberate; a declared
8
8
  # type is authoring intent tracked by #2325, and only then does this row generate; coercion-facing: it takes
9
9
  # anything that answers `to_s`), so no input type reaches the reader.
10
10
  attr_reader name: String
11
- # sig-gen gap: #2321 — `globs:` is `untyped` (input `untyped`, looks deliberate; a declared type is authoring
11
+ # sig-gen gap: #2325 — `globs:` is `untyped` (input `untyped`, looks deliberate; a declared type is authoring
12
12
  # intent tracked by #2325, and only then does this row generate; coercion-facing: `Array(globs)`, each `to_s`),
13
13
  # deduplicated and sorted in `initialize`.
14
14
  attr_reader globs: Array[String]
15
- # sig-gen gap: #2321 — `why:` is `untyped` (input `untyped`, looks deliberate; a declared type is authoring
15
+ # sig-gen gap: #2325 — `why:` is `untyped` (input `untyped`, looks deliberate; a declared type is authoring
16
16
  # intent tracked by #2325, and only then does this row generate; coercion-facing: `to_s`).
17
17
  attr_reader why: String
18
18
 
@@ -4,7 +4,7 @@ class Rigor::Plugin::IoBoundary
4
4
  def file?: (untyped) -> bool
5
5
  def directory?: (untyped) -> bool
6
6
  def list_directory: (untyped) -> Array[String]
7
- def list_directory_kinds: (untyped) -> Hash[String, untyped]
7
+ def list_directory_kinds: (untyped) -> Hash[String, Symbol]
8
8
  # Records a `:names` glob row (membership only); a caller needing a match's content `read_file`s it. A `..`
9
9
  # segment, a brace or a backslash in `pattern` raises `AccessDeniedError`.
10
10
  def glob: (untyped root, untyped pattern) -> Array[String]
@@ -15,6 +15,7 @@ class Rigor::Plugin::IoBoundary
15
15
  def refusal_summary: () -> { count: Integer, first_path: String?, nearest_root: String? }?
16
16
  def open_url: (untyped url) -> String
17
17
  # Returns a frozen `Rigor::Cache::Descriptor` (no RBS yet -> untyped).
18
+ # authored: returns Rigor::Cache::Descriptor, which has no RBS declaration yet (#2404); stays `untyped` until it is declared
18
19
  def cache_descriptor: () -> untyped
19
20
  def replay: (untyped descriptor) -> nil
20
21
  end