rigortype 0.3.8 → 0.3.9

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 (447) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/data/capability_roles/capability_roles.rbs +36 -0
  4. data/data/gem_overlay/activesupport/core_ext.rbs +109 -13
  5. data/docs/handbook/02-everyday-types.md +17 -14
  6. data/docs/handbook/03-narrowing.md +37 -4
  7. data/docs/handbook/05-methods-and-blocks.md +1 -1
  8. data/docs/handbook/06-classes.md +2 -2
  9. data/docs/handbook/07-rbs-and-extended.md +14 -1
  10. data/docs/handbook/11-sig-gen.md +72 -1
  11. data/docs/handbook/README.md +1 -1
  12. data/docs/handbook/appendix-elixir.md +2 -2
  13. data/docs/handbook/appendix-go.md +2 -2
  14. data/docs/handbook/appendix-java-csharp.md +2 -2
  15. data/docs/handbook/appendix-mypy.md +2 -2
  16. data/docs/handbook/appendix-phpstan.md +1 -1
  17. data/docs/handbook/appendix-rust.md +1 -1
  18. data/docs/handbook/appendix-type-theory.md +4 -4
  19. data/docs/handbook/appendix-typescript.md +1 -1
  20. data/docs/llms.txt +2 -0
  21. data/docs/manual/02-cli-reference.md +32 -4
  22. data/docs/manual/03-configuration.md +11 -0
  23. data/docs/manual/04-diagnostics.md +11 -0
  24. data/docs/manual/07-plugins.md +15 -5
  25. data/docs/manual/08-skills.md +23 -2
  26. data/docs/manual/11-ci.md +9 -0
  27. data/docs/manual/15-type-protection-coverage.md +8 -0
  28. data/docs/manual/16-rbs-extended-annotations.md +6 -1
  29. data/docs/manual/18-removing-dead-code.md +10 -8
  30. data/docs/manual/plugins/rigor-actionpack.md +18 -0
  31. data/docs/manual/plugins/rigor-activerecord.md +28 -0
  32. data/docs/manual/plugins/rigor-activesupport-core-ext.md +32 -2
  33. data/docs/manual/plugins/rigor-rbs-inline.md +42 -1
  34. data/lib/rigor/analysis/baseline.rb +2 -2
  35. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +1 -1
  36. data/lib/rigor/analysis/check_rules/dead_version_guard_arms.rb +1 -5
  37. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +10 -2
  38. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +1 -1
  39. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +15 -6
  40. data/lib/rigor/analysis/check_rules/rule_ids.rb +1 -1
  41. data/lib/rigor/analysis/check_rules/rule_walk.rb +39 -4
  42. data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +1 -1
  43. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +1 -1
  44. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
  45. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +0 -1
  46. data/lib/rigor/analysis/check_rules.rb +200 -32
  47. data/lib/rigor/analysis/crash_signature.rb +8 -24
  48. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +1 -1
  49. data/lib/rigor/analysis/dependency_source_inference/builder.rb +0 -2
  50. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +0 -2
  51. data/lib/rigor/analysis/dependency_source_inference/index.rb +5 -5
  52. data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +1 -2
  53. data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
  54. data/lib/rigor/analysis/effects_cache_probe.rb +3 -4
  55. data/lib/rigor/analysis/erb_template_detector.rb +1 -2
  56. data/lib/rigor/analysis/fact_store.rb +6 -1
  57. data/lib/rigor/analysis/incremental.rb +16 -0
  58. data/lib/rigor/analysis/incremental_session.rb +81 -22
  59. data/lib/rigor/analysis/plugin_fact_fingerprint.rb +0 -1
  60. data/lib/rigor/analysis/reachability/graph.rb +3 -5
  61. data/lib/rigor/analysis/reachability/plugin_roots.rb +6 -5
  62. data/lib/rigor/analysis/reachability/project_files.rb +2 -2
  63. data/lib/rigor/analysis/reachability/scan.rb +5 -5
  64. data/lib/rigor/analysis/reachability/scan_cache.rb +2 -2
  65. data/lib/rigor/analysis/reachability/signature_scan.rb +2 -2
  66. data/lib/rigor/analysis/result.rb +1 -3
  67. data/lib/rigor/analysis/rule_catalog.rb +4 -1
  68. data/lib/rigor/analysis/run_cache_key.rb +1 -1
  69. data/lib/rigor/analysis/run_cache_probe.rb +3 -5
  70. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -8
  71. data/lib/rigor/analysis/runner/declaration_position.rb +1 -2
  72. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +177 -26
  73. data/lib/rigor/analysis/runner/effect_annotation_residual_pass.rb +2 -3
  74. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +9 -10
  75. data/lib/rigor/analysis/runner/pool_coordinator.rb +256 -88
  76. data/lib/rigor/analysis/runner/project_pre_passes.rb +12 -6
  77. data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
  78. data/lib/rigor/analysis/runner.rb +133 -35
  79. data/lib/rigor/analysis/worker_session.rb +9 -10
  80. data/lib/rigor/bleeding_edge.rb +12 -24
  81. data/lib/rigor/builtins/hkt_builtins.rb +2 -2
  82. data/lib/rigor/builtins/imported_refinements.rb +155 -21
  83. data/lib/rigor/builtins/predefined_constant_refinements.rb +5 -7
  84. data/lib/rigor/builtins/regex_refinement.rb +4 -4
  85. data/lib/rigor/builtins/static_return_refinements.rb +5 -6
  86. data/lib/rigor/cache/annotation_location.rb +2 -4
  87. data/lib/rigor/cache/descriptor.rb +58 -20
  88. data/lib/rigor/cache/engine_source.rb +4 -4
  89. data/lib/rigor/cache/file_digest.rb +9 -1
  90. data/lib/rigor/cache/incremental_snapshot.rb +40 -6
  91. data/lib/rigor/cache/rbs_class_ancestor_table.rb +0 -3
  92. data/lib/rigor/cache/rbs_class_type_param_names.rb +0 -3
  93. data/lib/rigor/cache/rbs_constant_table.rb +0 -3
  94. data/lib/rigor/cache/rbs_descriptor.rb +79 -11
  95. data/lib/rigor/cache/rbs_environment.rb +8 -4
  96. data/lib/rigor/cache/rbs_known_class_names.rb +0 -3
  97. data/lib/rigor/cache/store.rb +77 -32
  98. data/lib/rigor/cli/annotate_command.rb +5 -6
  99. data/lib/rigor/cli/check_command.rb +57 -10
  100. data/lib/rigor/cli/check_invocation.rb +6 -11
  101. data/lib/rigor/cli/check_runner_factory.rb +1 -5
  102. data/lib/rigor/cli/coverage_command.rb +1 -1
  103. data/lib/rigor/cli/coverage_mutation.rb +12 -3
  104. data/lib/rigor/cli/coverage_scan.rb +1 -5
  105. data/lib/rigor/cli/diff_command.rb +1 -1
  106. data/lib/rigor/cli/doc_links.rb +3 -3
  107. data/lib/rigor/cli/docs_command.rb +2 -2
  108. data/lib/rigor/cli/doctor_command.rb +85 -38
  109. data/lib/rigor/cli/effects_command.rb +2 -2
  110. data/lib/rigor/cli/effects_diff_renderer.rb +3 -3
  111. data/lib/rigor/cli/effects_snapshot_command.rb +1 -1
  112. data/lib/rigor/cli/explain_command.rb +34 -1
  113. data/lib/rigor/cli/fused_protection_report.rb +7 -1
  114. data/lib/rigor/cli/lsp_command.rb +1 -1
  115. data/lib/rigor/cli/mcp_command.rb +1 -1
  116. data/lib/rigor/cli/measurement_integrity_warning.rb +4 -5
  117. data/lib/rigor/cli/mutation_fork_scan.rb +6 -6
  118. data/lib/rigor/cli/mutation_protection_report.rb +7 -1
  119. data/lib/rigor/cli/plugin_command.rb +1 -1
  120. data/lib/rigor/cli/plugins_command.rb +2 -1
  121. data/lib/rigor/cli/prism_colorizer.rb +2 -2
  122. data/lib/rigor/cli/protection_fork_scan.rb +6 -6
  123. data/lib/rigor/cli/show_bleedingedge_command.rb +1 -1
  124. data/lib/rigor/cli/sig_gen_command.rb +2 -2
  125. data/lib/rigor/cli/skill_command.rb +1 -1
  126. data/lib/rigor/cli/skill_deep_probe.rb +4 -4
  127. data/lib/rigor/cli/skill_describe.rb +36 -35
  128. data/lib/rigor/cli/trace_command.rb +4 -4
  129. data/lib/rigor/cli/trace_renderer.rb +5 -6
  130. data/lib/rigor/cli/triage_command.rb +1 -1
  131. data/lib/rigor/cli/type_of_command.rb +10 -6
  132. data/lib/rigor/cli/type_scan_command.rb +4 -4
  133. data/lib/rigor/cli/unused_command.rb +11 -3
  134. data/lib/rigor/cli/upgrade_command.rb +1 -1
  135. data/lib/rigor/cli.rb +68 -6
  136. data/lib/rigor/config_audit.rb +30 -5
  137. data/lib/rigor/configuration/severity_profile.rb +6 -6
  138. data/lib/rigor/configuration.rb +43 -4
  139. data/lib/rigor/effects/attribution.rb +1 -3
  140. data/lib/rigor/effects/config_envelopes.rb +6 -8
  141. data/lib/rigor/effects/definition_lines.rb +25 -6
  142. data/lib/rigor/effects/effect_table.rb +0 -1
  143. data/lib/rigor/effects/entry_points.rb +0 -2
  144. data/lib/rigor/effects/envelope_check.rb +8 -8
  145. data/lib/rigor/effects/envelope_index.rb +5 -8
  146. data/lib/rigor/effects/framework_units.rb +4 -6
  147. data/lib/rigor/effects/identity.rb +4 -6
  148. data/lib/rigor/effects/inline_anchor.rb +7 -7
  149. data/lib/rigor/effects/label_intent.rb +3 -4
  150. data/lib/rigor/effects/liskov_check.rb +8 -8
  151. data/lib/rigor/effects/method_key.rb +1 -1
  152. data/lib/rigor/effects/plugin_facts.rb +4 -6
  153. data/lib/rigor/effects/propagator.rb +2 -3
  154. data/lib/rigor/effects/scanner.rb +1 -1
  155. data/lib/rigor/effects/signature_sources.rb +3 -5
  156. data/lib/rigor/effects/snapshot.rb +10 -10
  157. data/lib/rigor/effects/snapshot_diff.rb +1 -1
  158. data/lib/rigor/effects/unit_scan.rb +10 -10
  159. data/lib/rigor/effects/unknown_label_check.rb +3 -8
  160. data/lib/rigor/effects/unknown_label_report.rb +3 -4
  161. data/lib/rigor/environment/bundle_sig_discovery.rb +6 -6
  162. data/lib/rigor/environment/class_registry.rb +3 -1
  163. data/lib/rigor/environment/failure_slot.rb +2 -2
  164. data/lib/rigor/environment/installed_gem_set.rb +85 -0
  165. data/lib/rigor/environment/lockfile_resolver.rb +51 -4
  166. data/lib/rigor/environment/missing_gem_constant_index.rb +4 -4
  167. data/lib/rigor/environment/rbs_collection_discovery.rb +5 -5
  168. data/lib/rigor/environment/rbs_coverage_report.rb +5 -5
  169. data/lib/rigor/environment/rbs_hierarchy.rb +3 -1
  170. data/lib/rigor/environment/rbs_loader.rb +388 -59
  171. data/lib/rigor/environment.rb +105 -20
  172. data/lib/rigor/flow_contribution/merger.rb +0 -2
  173. data/lib/rigor/flow_contribution.rb +11 -13
  174. data/lib/rigor/inference/acceptance.rb +153 -8
  175. data/lib/rigor/inference/block_parameter_binder.rb +2 -3
  176. data/lib/rigor/inference/body_fixpoint.rb +5 -5
  177. data/lib/rigor/inference/budget_trace.rb +1 -5
  178. data/lib/rigor/inference/captured_locals.rb +2 -3
  179. data/lib/rigor/inference/closure_escape_analyzer.rb +2 -4
  180. data/lib/rigor/inference/coverage_scanner.rb +4 -6
  181. data/lib/rigor/inference/dynamic_origin.rb +1 -1
  182. data/lib/rigor/inference/element_read_widening.rb +168 -0
  183. data/lib/rigor/inference/expression_typer.rb +763 -143
  184. data/lib/rigor/inference/fork_map.rb +5 -7
  185. data/lib/rigor/inference/hkt_reducer.rb +2 -3
  186. data/lib/rigor/inference/hkt_registry.rb +4 -7
  187. data/lib/rigor/inference/index_write_widening.rb +1 -5
  188. data/lib/rigor/inference/macro_block_self_type.rb +1 -4
  189. data/lib/rigor/inference/method_dispatcher/block_folding.rb +2 -9
  190. data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +1 -1
  191. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +305 -19
  192. data/lib/rigor/inference/method_dispatcher/data_folding.rb +1 -1
  193. data/lib/rigor/inference/method_dispatcher/file_folding.rb +1 -1
  194. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +2 -2
  195. data/lib/rigor/inference/method_dispatcher/json_folding.rb +1 -1
  196. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -1
  197. data/lib/rigor/inference/method_dispatcher/math_folding.rb +41 -3
  198. data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -5
  199. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +61 -47
  200. data/lib/rigor/inference/method_dispatcher/random_folding.rb +82 -0
  201. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +165 -31
  202. data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +25 -4
  203. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +2 -8
  204. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +1 -1
  205. data/lib/rigor/inference/method_dispatcher/set_folding.rb +1 -1
  206. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +1 -1
  207. data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +1 -1
  208. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +1 -2
  209. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +4 -5
  210. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +85 -4
  211. data/lib/rigor/inference/method_dispatcher/time_folding.rb +1 -1
  212. data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +1 -2
  213. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  214. data/lib/rigor/inference/method_dispatcher.rb +12 -13
  215. data/lib/rigor/inference/method_parameter_binder.rb +4 -6
  216. data/lib/rigor/inference/multi_target_binder.rb +1 -3
  217. data/lib/rigor/inference/mutation_rejoin.rb +168 -0
  218. data/lib/rigor/inference/mutation_widening.rb +100 -51
  219. data/lib/rigor/inference/narrowing.rb +299 -46
  220. data/lib/rigor/inference/optimistic_origin.rb +1 -7
  221. data/lib/rigor/inference/origin_lookup.rb +2 -3
  222. data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
  223. data/lib/rigor/inference/pre_eval_constants.rb +6 -5
  224. data/lib/rigor/inference/precision_scanner.rb +3 -4
  225. data/lib/rigor/inference/project_patched_methods.rb +2 -2
  226. data/lib/rigor/inference/project_patched_scanner.rb +3 -3
  227. data/lib/rigor/inference/protection_scanner.rb +1 -2
  228. data/lib/rigor/inference/range_constant.rb +57 -0
  229. data/lib/rigor/inference/rbs_type_translator.rb +49 -11
  230. data/lib/rigor/inference/receiver_alias.rb +3 -3
  231. data/lib/rigor/inference/refinement_mutation.rb +72 -0
  232. data/lib/rigor/inference/scope_indexer.rb +688 -151
  233. data/lib/rigor/inference/statement_evaluator.rb +221 -50
  234. data/lib/rigor/inference/string_mutation.rb +60 -0
  235. data/lib/rigor/inference/struct_fold_safety.rb +6 -7
  236. data/lib/rigor/inference/synthetic_method_index.rb +1 -2
  237. data/lib/rigor/inference/synthetic_method_scanner.rb +4 -6
  238. data/lib/rigor/inference/version_guard.rb +28 -21
  239. data/lib/rigor/inference/void_origin.rb +3 -3
  240. data/lib/rigor/inference/void_tail_summary.rb +2 -4
  241. data/lib/rigor/language_server/buffer_table.rb +4 -4
  242. data/lib/rigor/language_server/completion_provider.rb +1 -1
  243. data/lib/rigor/language_server/debouncer.rb +1 -1
  244. data/lib/rigor/language_server/diagnostic_publisher.rb +4 -4
  245. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  246. data/lib/rigor/language_server/folding_range_provider.rb +1 -1
  247. data/lib/rigor/language_server/hover_provider.rb +1 -1
  248. data/lib/rigor/language_server/hover_renderer.rb +2 -2
  249. data/lib/rigor/language_server/incremental_sync.rb +6 -6
  250. data/lib/rigor/language_server/project_context.rb +2 -5
  251. data/lib/rigor/language_server/publish_batcher.rb +2 -2
  252. data/lib/rigor/language_server/selection_range_provider.rb +2 -2
  253. data/lib/rigor/language_server/server.rb +9 -9
  254. data/lib/rigor/language_server/signature_help_provider.rb +1 -1
  255. data/lib/rigor/language_server/uri.rb +1 -1
  256. data/lib/rigor/plugin/base.rb +90 -21
  257. data/lib/rigor/plugin/bundled_catalog.rb +167 -0
  258. data/lib/rigor/plugin/effect_attribution.rb +7 -7
  259. data/lib/rigor/plugin/effect_entry_points.rb +3 -3
  260. data/lib/rigor/plugin/fact_store.rb +6 -6
  261. data/lib/rigor/plugin/io_boundary.rb +93 -8
  262. data/lib/rigor/plugin/isolation.rb +29 -6
  263. data/lib/rigor/plugin/loader.rb +31 -12
  264. data/lib/rigor/plugin/macro/heredoc_template.rb +1 -1
  265. data/lib/rigor/plugin/macro/trait_registry.rb +1 -1
  266. data/lib/rigor/plugin/manifest.rb +23 -1
  267. data/lib/rigor/plugin/registry.rb +42 -12
  268. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +12 -1
  269. data/lib/rigor/plugin/trust_policy.rb +2 -4
  270. data/lib/rigor/plugin/type_node_resolver.rb +3 -3
  271. data/lib/rigor/plugin_gap_advisory.rb +96 -0
  272. data/lib/rigor/project_environment.rb +138 -0
  273. data/lib/rigor/protection/analysis_guard.rb +93 -14
  274. data/lib/rigor/protection/closure_kill_oracle.rb +67 -23
  275. data/lib/rigor/protection/dependency_closure.rb +5 -8
  276. data/lib/rigor/protection/diagnostic_oracle.rb +2 -2
  277. data/lib/rigor/protection/discovery_seed.rb +11 -11
  278. data/lib/rigor/protection/kill_signature.rb +2 -4
  279. data/lib/rigor/protection/measurement_integrity.rb +1 -3
  280. data/lib/rigor/protection/mutation_cache.rb +6 -9
  281. data/lib/rigor/protection/mutation_scanner.rb +12 -15
  282. data/lib/rigor/protection/mutator.rb +2 -1
  283. data/lib/rigor/protection/test_suite_oracle.rb +5 -5
  284. data/lib/rigor/rbs_extended/conformance_checker.rb +4 -3
  285. data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
  286. data/lib/rigor/rbs_extended/reporter.rb +36 -10
  287. data/lib/rigor/rbs_extended.rb +61 -21
  288. data/lib/rigor/reflection/constant_path.rb +126 -0
  289. data/lib/rigor/reflection.rb +26 -10
  290. data/lib/rigor/runtime/jit.rb +6 -8
  291. data/lib/rigor/scope/discovery_index.rb +30 -0
  292. data/lib/rigor/scope.rb +203 -4
  293. data/lib/rigor/sig_gen/generator.rb +115 -22
  294. data/lib/rigor/sig_gen/layout_index.rb +3 -4
  295. data/lib/rigor/sig_gen/meta_class_shape.rb +3 -5
  296. data/lib/rigor/sig_gen/observation_collector.rb +17 -15
  297. data/lib/rigor/sig_gen/path_mapper.rb +4 -6
  298. data/lib/rigor/sig_gen/rbs_validity.rb +4 -4
  299. data/lib/rigor/sig_gen/renderer.rb +4 -5
  300. data/lib/rigor/sig_gen/skip_reason_catalog.rb +111 -0
  301. data/lib/rigor/sig_gen/superclass_spelling.rb +27 -0
  302. data/lib/rigor/sig_gen/type_elaborator.rb +1 -3
  303. data/lib/rigor/sig_gen/writer.rb +23 -6
  304. data/lib/rigor/signature_path_audit.rb +153 -6
  305. data/lib/rigor/source/literals.rb +0 -23
  306. data/lib/rigor/source/node_children.rb +0 -2
  307. data/lib/rigor/source/node_locator.rb +5 -11
  308. data/lib/rigor/source/node_walker.rb +2 -5
  309. data/lib/rigor/triage/catalogue.rb +1 -3
  310. data/lib/rigor/triage.rb +3 -5
  311. data/lib/rigor/type/accepts_result.rb +24 -6
  312. data/lib/rigor/type/combinator.rb +68 -5
  313. data/lib/rigor/type/data_class.rb +2 -2
  314. data/lib/rigor/type/data_instance.rb +4 -4
  315. data/lib/rigor/type/float_range.rb +128 -0
  316. data/lib/rigor/type/hash_shape.rb +5 -5
  317. data/lib/rigor/type/integer_range.rb +13 -8
  318. data/lib/rigor/type/nominal.rb +8 -2
  319. data/lib/rigor/type/refined.rb +3 -3
  320. data/lib/rigor/type/struct_class.rb +3 -3
  321. data/lib/rigor/type/struct_instance.rb +4 -4
  322. data/lib/rigor/type.rb +1 -0
  323. data/lib/rigor/type_node/generic.rb +1 -1
  324. data/lib/rigor/type_node/range_literal.rb +25 -0
  325. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  326. data/lib/rigor/type_node.rb +1 -0
  327. data/lib/rigor/version.rb +1 -1
  328. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +0 -4
  329. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +0 -1
  330. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +0 -2
  331. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -0
  332. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +0 -4
  333. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +3 -5
  334. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +1 -4
  335. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +1 -0
  336. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +33 -30
  337. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +0 -1
  338. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -3
  339. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +28 -2
  340. data/plugins/rigor-actionpack/sig/action_controller.rbs +71 -0
  341. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +0 -4
  342. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
  343. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +0 -1
  344. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +0 -2
  345. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +1 -1
  346. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +1 -0
  347. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +3 -4
  348. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +1 -2
  349. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +1 -2
  350. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +42 -7
  351. data/plugins/rigor-activerecord/sig/active_record/framework.rbs +123 -0
  352. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +1 -0
  353. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +9 -2
  354. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +119 -14
  355. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +1 -0
  356. data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +1 -0
  357. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +9 -7
  358. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +7 -3
  359. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +1 -0
  360. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +8 -6
  361. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +11 -7
  362. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -11
  363. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +9 -4
  364. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +1 -0
  365. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +0 -5
  366. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +0 -1
  367. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +0 -1
  368. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +8 -1
  369. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +36 -4
  370. data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +1 -0
  371. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +8 -6
  372. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +6 -3
  373. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +1 -0
  374. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -1
  375. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +0 -3
  376. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +1 -0
  377. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +0 -5
  378. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -2
  379. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +0 -1
  380. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +0 -1
  381. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +1 -0
  382. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +0 -12
  383. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +1 -3
  384. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +0 -1
  385. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +1 -0
  386. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/acronyms.rb +5 -5
  387. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -10
  388. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +3 -3
  389. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +2 -2
  390. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +2 -2
  391. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +2 -2
  392. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +9 -10
  393. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +2 -3
  394. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -0
  395. data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +1 -0
  396. data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +13 -1
  397. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +128 -3
  398. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +0 -3
  399. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +0 -3
  400. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -5
  401. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +2 -3
  402. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +1 -0
  403. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +0 -3
  404. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +1 -0
  405. data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +1 -0
  406. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +1 -9
  407. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +1 -0
  408. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +0 -4
  409. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/effects.rb +1 -1
  410. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +1 -1
  411. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +0 -1
  412. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +0 -2
  413. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +1 -0
  414. data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -0
  415. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +2 -5
  416. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +1 -4
  417. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +3 -9
  418. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +4 -4
  419. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +1 -2
  420. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +4 -5
  421. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +7 -5
  422. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +5 -5
  423. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +1 -0
  424. data/sig/rigor/analysis/reachability/scan_cache.rbs +12 -0
  425. data/sig/rigor/cache.rbs +2 -3
  426. data/sig/rigor/environment.rbs +5 -0
  427. data/sig/rigor/inference.rbs +3 -10
  428. data/sig/rigor/plugin/io_boundary.rbs +4 -0
  429. data/sig/rigor/plugin/manifest.rbs +2 -1
  430. data/sig/rigor/reflection.rbs +3 -1
  431. data/sig/rigor/scope.rbs +24 -3
  432. data/sig/rigor/sig_gen/skip_reason_catalog.rbs +14 -0
  433. data/sig/rigor/type.rbs +27 -1
  434. data/sig/rigor.rbs +1 -0
  435. data/skills/rigor-ask/SKILL.md +8 -6
  436. data/skills/rigor-plugin-author/references/02-walker-and-types.md +13 -2
  437. data/skills/rigor-project-init/SKILL.md +6 -3
  438. data/skills/rigor-project-init/references/02-configure.md +10 -6
  439. data/skills/rigor-project-init/references/06-agent-contract.md +67 -0
  440. data/skills/rigor-type-oracle/SKILL.md +228 -0
  441. data/skills/rigor-type-oracle/references/01-oracle-commands.md +261 -0
  442. data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +52 -0
  443. data/skills/rigor-type-oracle/references/03-gap-protocol.md +128 -0
  444. data/skills/rigor-unused-adjudicate/SKILL.md +8 -5
  445. metadata +26 -3
  446. data/lib/rigor/cli/probe_environment.rb +0 -85
  447. data/sig/rigor/inference/builtins/numeric_catalog.rbs +0 -3
@@ -11,6 +11,7 @@ end
11
11
 
12
12
  require_relative "type_node/identifier"
13
13
  require_relative "type_node/integer_literal"
14
+ require_relative "type_node/range_literal"
14
15
  require_relative "type_node/symbol_literal"
15
16
  require_relative "type_node/string_literal"
16
17
  require_relative "type_node/generic"
data/lib/rigor/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rigor
4
- VERSION = "0.3.8"
4
+ VERSION = "0.3.9"
5
5
  end
@@ -34,10 +34,6 @@ module Rigor
34
34
  # The broadcast violations for a single call node, or `[]` when the node is not a
35
35
  # `broadcast_to` / `ActionCable.server.broadcast` call this plugin recognises. ADR-37: the
36
36
  # engine owns the walk.
37
- #
38
- # @param call_node [Prism::Node]
39
- # @param channel_index [ChannelIndex]
40
- # @return [Array<Violation>]
41
37
  def violations_for(call_node:, channel_index:)
42
38
  return [] unless call_node.is_a?(Prism::CallNode)
43
39
 
@@ -43,7 +43,6 @@ module Rigor
43
43
  @base_classes = base_classes.to_set { |name| strip_root(name.to_s) }
44
44
  end
45
45
 
46
- # @return [ChannelIndex]
47
46
  def discover
48
47
  entries = []
49
48
  ruby_files_under(@search_paths).each do |path|
@@ -39,8 +39,6 @@ module Rigor
39
39
  # `"::ChatChannel"`; see {ChannelDiscoverer}), while a QUERY may legitimately arrive rooted:
40
40
  # `::ChatChannel.broadcast_to(...)` renders its receiver as `"::ChatChannel"`. The root marker is
41
41
  # dropped here, once, so no caller needs a `find(name) || find("::#{name}")` retry (#621).
42
- #
43
- # @return [Entry, nil]
44
42
  def find(class_name)
45
43
  @by_name[strip_leading_namespace(class_name.to_s)]
46
44
  end
@@ -65,6 +65,7 @@ module Rigor
65
65
  class Actioncable < Rigor::Plugin::Base
66
66
  manifest(
67
67
  id: "actioncable",
68
+ target_gems: ["actioncable"],
68
69
  # Bumped 2026-09-02 (#621) — channel keys and base-class names are de-rooted at the producer and a
69
70
  # reopened class's actions and streams are UNIONed rather than clobbered, so a cached 0.1.0 index
70
71
  # can be missing a whole channel (`< ::ApplicationCable::Channel`) or half its stream names.
@@ -42,10 +42,6 @@ module Rigor
42
42
 
43
43
  # The mailer-call violations for a single call node (0..2), or `[]` when it is not a
44
44
  # `<Mailer>.action(...)` call on a known mailer. ADR-37: the engine owns the walk.
45
- #
46
- # @param call_node [Prism::Node]
47
- # @param mailer_index [MailerIndex]
48
- # @return [Array<Violation>]
49
45
  def violations_for(call_node:, mailer_index:)
50
46
  return [] unless call_node.is_a?(Prism::CallNode) && action_call_candidate?(call_node)
51
47
 
@@ -38,10 +38,9 @@ module Rigor
38
38
  VIEW_FORMATS = %w[html text].freeze
39
39
  VIEW_EXTENSIONS = %w[erb haml slim].freeze
40
40
 
41
- # @param io_boundary [Rigor::Plugin::IoBoundary]
42
- # @param search_paths [Array<String>] absolute or project-relative paths to scan for mailers.
43
- # @param base_classes [Array<String>] direct superclasses that mark a class as a mailer.
44
- # @param views_root [String] absolute or project-relative path to the views directory (typically
41
+ # @param search_paths — absolute or project-relative paths to scan for mailers.
42
+ # @param base_classes direct superclasses that mark a class as a mailer.
43
+ # @param views_root absolute or project-relative path to the views directory (typically
45
44
  # `app/views`).
46
45
  def initialize(io_boundary:, search_paths:, base_classes:, views_root: DEFAULT_VIEWS_ROOT)
47
46
  @io_boundary = io_boundary
@@ -53,7 +52,6 @@ module Rigor
53
52
  @views_root = views_root
54
53
  end
55
54
 
56
- # @return [MailerIndex]
57
55
  def discover
58
56
  # Two-pass: first collect every module's defs (for the include-following step), then build
59
57
  # per-class entries that pull in actions from include'd modules. GitLab's `Notify` mailer
@@ -58,8 +58,6 @@ module Rigor
58
58
  # `"::UserMailer"`; see {MailerDiscoverer}), while a QUERY may legitimately arrive rooted:
59
59
  # `::UserMailer.welcome(u)` renders its receiver as `"::UserMailer"`. The root marker is dropped
60
60
  # here, once, so no caller needs a `find(name) || find("::#{name}")` retry (#621).
61
- #
62
- # @return [ClassEntry, nil]
63
61
  def find(class_name)
64
62
  @by_name[strip_leading_namespace(class_name.to_s)]
65
63
  end
@@ -68,9 +66,8 @@ module Rigor
68
66
  @by_name.key?(strip_leading_namespace(class_name.to_s))
69
67
  end
70
68
 
71
- # @param file_path [String] absolute path of a mailer file (canonicalised — see plugin entry's
69
+ # @param file_path absolute path of a mailer file (canonicalised — see plugin entry's
72
70
  # `harvest`)
73
- # @return [ClassEntry, nil]
74
71
  def find_by_file(file_path)
75
72
  @entries.find { |entry| entry.file_path == file_path }
76
73
  end
@@ -43,6 +43,7 @@ module Rigor
43
43
  class Actionmailer < Rigor::Plugin::Base
44
44
  manifest(
45
45
  id: "actionmailer",
46
+ target_gems: ["actionmailer"],
46
47
  # Bumped 2026-05-28 — extended RESERVED_CLASS_METHODS to include `respond_to?` / `public_send` /
47
48
  # `send` / `__send__` / `method` and friends so dynamic-dispatch idioms
48
49
  # (`Mailer.respond_to?(action)` / `Mailer.public_send(action)`) stop firing `unknown-action`
@@ -81,10 +81,8 @@ module Rigor
81
81
  # duplicates on Mastodon and ~119 on Redmine, exactly stacked with the rails-routes diagnostics.
82
82
  # Returns `[]` for unknown / arity-mismatch shapes.
83
83
  #
84
- # @param call_node [Prism::Node]
85
- # @param helper_table [Hash{String => Hash}] each entry carries `name`, `arity`,
84
+ # @param helper_table — each entry carries `name`, `arity`,
86
85
  # `acceptable_arities`, `path`, `http_method`, `action`.
87
- # @return [Array<Violation>]
88
86
  def helper_violations_for(call_node:, helper_table:)
89
87
  return [] unless implicit_helper_call?(call_node)
90
88
 
@@ -108,10 +106,7 @@ module Rigor
108
106
  # each filter name reference is validated against it. Calls outside a known controller
109
107
  # contribute nothing.
110
108
  #
111
- # @param call_node [Prism::Node]
112
- # @param ancestors [Array<Prism::Node>] the lexical ancestor chain
113
- # @param controller_index [ControllerIndex]
114
- # @return [Array<Violation>]
109
+ # @param ancestors — the lexical ancestor chain
115
110
  def filter_violations_for(call_node:, ancestors:, controller_index:)
116
111
  return [] unless filter_call?(call_node)
117
112
 
@@ -144,10 +139,6 @@ module Rigor
144
139
  # AR model's column list (looked up via the model_index fact published by `rigor-activerecord`).
145
140
  # Calls whose `:require` argument is a non-literal Symbol are passed through; namespaced models
146
141
  # (`params.require(:admin_user)` → `Admin::User`) are deferred.
147
- #
148
- # @param call_node [Prism::Node]
149
- # @param model_index [Hash{String => Hash}]
150
- # @return [Array<Violation>]
151
142
  def permit_violations_for(call_node:, model_index:)
152
143
  return [] unless permit_chain?(call_node)
153
144
 
@@ -179,13 +170,13 @@ module Rigor
179
170
  # method that doesn't call `render`) is also skipped — Phase 3 validates explicit renders only,
180
171
  # since the implicit path would false-positive on `redirect_to` / `head` / early returns.
181
172
  #
182
- # @param call_node [Prism::Node]
183
- # @param ancestors [Array<Prism::Node>] the lexical ancestor chain
184
- # @param path [String] file being analysed
185
- # @param view_search_roots [Array<String>]
186
- # @param controller_index [ControllerIndex, nil]
187
- # @return [Array<Violation>]
188
- def render_violations_for(call_node:, ancestors:, path:, view_search_roots:, controller_index: nil)
173
+ # @param ancestors — the lexical ancestor chain
174
+ # @param path file being analysed
175
+ # @param io_boundary the plugin's {Rigor::Plugin::IoBoundary}; #629 — every filesystem
176
+ # question this phase asks goes through it, so "no such template" is a recorded dependency and
177
+ # the run-result cache invalidates once the template appears
178
+ def render_violations_for(call_node:, ancestors:, path:, view_search_roots:, io_boundary:,
179
+ controller_index: nil)
189
180
  return [] unless render_call?(call_node)
190
181
 
191
182
  class_name = enclosing_controller_name(ancestors)
@@ -217,12 +208,12 @@ module Rigor
217
208
  # `app/views/admin/settings/` holds about/, appearance/, … but no top-level templates). Skip
218
209
  # render checks for these — the diagnostic would be a false positive against intentional Rails
219
210
  # abstract-base layouts.
220
- return [] if abstract_base_controller?(class_name, controller_path, view_search_roots)
211
+ return [] if abstract_base_controller?(class_name, controller_path, view_search_roots, io_boundary)
221
212
 
222
213
  target = render_target_for(call_node, controller_path)
223
214
  return [] if target.nil?
224
215
 
225
- violation = render_violation(call_node, target, view_search_roots)
216
+ violation = render_violation(call_node, target, view_search_roots, io_boundary)
226
217
  violation ? [violation] : []
227
218
  end
228
219
 
@@ -406,15 +397,16 @@ module Rigor
406
397
  # controllers.
407
398
  # Deliberately NOT triggered by "no view directory at all" because that fires the diagnostic we
408
399
  # DO want for genuinely-missing views (the typo / forgot-to-create case).
409
- def abstract_base_controller?(class_name, controller_path, view_search_roots)
400
+ def abstract_base_controller?(class_name, controller_path, view_search_roots, io_boundary)
410
401
  return true if class_name.end_with?("BaseController")
411
402
 
412
403
  view_search_roots.any? do |root|
413
404
  dir = File.join(root, controller_path)
414
- next false unless File.directory?(dir)
415
-
416
- entries = Dir.children(dir)
417
- entries.any? && entries.all? { |e| File.directory?(File.join(dir, e)) }
405
+ # #629 the listing row this records is the same one {#locate_template} needs, and the
406
+ # boundary deduplicates it per directory, so the whole render phase costs one glob
407
+ # validation per consulted view directory on a warm run.
408
+ entries = io_boundary.list_directory(dir)
409
+ entries.any? && entries.all? { |e| File.directory?(e) }
418
410
  end
419
411
  end
420
412
 
@@ -473,9 +465,9 @@ module Rigor
473
465
  Rigor::Plugin::Inflector.underscore(stripped)
474
466
  end
475
467
 
476
- def render_violation(call_node, target, view_search_roots)
468
+ def render_violation(call_node, target, view_search_roots, io_boundary)
477
469
  kind, relative = target
478
- existing = locate_template(relative, view_search_roots)
470
+ existing = locate_template(relative, view_search_roots, io_boundary)
479
471
  if existing
480
472
  render_target_violation(call_node, kind, relative, existing)
481
473
  else
@@ -483,11 +475,22 @@ module Rigor
483
475
  end
484
476
  end
485
477
 
486
- def locate_template(relative, view_search_roots)
478
+ # #629 — resolved from the DIRECTORY LISTING, not from a probe per candidate path. The old shape
479
+ # asked `File.file?` up to `extensions × roots` times per `render` call and recorded nothing, so a
480
+ # cached run kept reporting `plugin.actionpack.missing-template` after the template was written.
481
+ # One {Rigor::Plugin::IoBoundary#list_directory} row per `app/views/<controller>` directory carries
482
+ # the dependency instead: any file added or removed there invalidates the run, and the warm-run
483
+ # validation count is the number of view directories the run consulted rather than the number of
484
+ # candidate paths it tried.
485
+ def locate_template(relative, view_search_roots, io_boundary)
486
+ basename = File.basename(relative)
487
487
  view_search_roots.each do |root|
488
+ # Names, not paths: the boundary answers in absolute paths while `root` may be the relative
489
+ # `app/views` the user configured, and the reported candidate keeps the configured spelling.
490
+ listing = io_boundary.list_directory(File.join(root, File.dirname(relative)))
491
+ names = listing.map { |entry| File.basename(entry) }
488
492
  RENDER_TEMPLATE_EXTENSIONS.each do |ext|
489
- candidate = File.join(root, "#{relative}#{ext}")
490
- return candidate if File.file?(candidate)
493
+ return File.join(root, "#{relative}#{ext}") if names.include?("#{basename}#{ext}")
491
494
  end
492
495
  end
493
496
  nil
@@ -42,7 +42,6 @@ module Rigor
42
42
  @search_paths = search_paths
43
43
  end
44
44
 
45
- # @return [ControllerIndex]
46
45
  def discover
47
46
  entries = {}
48
47
  ruby_files_under(@search_paths).each do |path|
@@ -42,8 +42,6 @@ module Rigor
42
42
  # `"Admin::AccountsController"` — never `"::AccountsController"`; see {ControllerDiscoverer}),
43
43
  # while a QUERY may legitimately arrive rooted. The root marker is dropped at every lookup entry
44
44
  # point, once, so no caller needs a `find(name) || find("::#{name}")` retry (#621).
45
- #
46
- # @return [Entry, nil]
47
45
  def find(class_name)
48
46
  @entries[strip_leading_namespace(class_name)]
49
47
  end
@@ -84,7 +82,7 @@ module Rigor
84
82
  methods.uniq.freeze
85
83
  end
86
84
 
87
- # @return [Boolean] true when the class has at least one include OR parent class we couldn't
85
+ # @return true when the class has at least one include OR parent class we couldn't
88
86
  # resolve in the index (typically a gem-shipped concern such as Devise's
89
87
  # `Devise::Controllers::Helpers`, or a gem-shipped parent controller such as
90
88
  # `Devise::ConfirmationsController` or `Doorkeeper::AuthorizedApplicationsController`). Phase
@@ -55,6 +55,7 @@ module Rigor
55
55
  class Actionpack < Rigor::Plugin::Base
56
56
  manifest(
57
57
  id: "actionpack",
58
+ target_gems: ["actionpack"],
58
59
  # ADR-37: the four phases (helper / filter / render / strong-params) run per-call over the
59
60
  # engine-owned walk; the enclosing controller is read from the node-rule `NodeContext`
60
61
  # ancestors. Nested-module qualification is preserved — `module Admin; class
@@ -68,7 +69,11 @@ module Rigor
68
69
  # Bumped 2026-09-02 (#621) — a reopened controller's `def`s are UNIONed rather than clobbered by
69
70
  # the later file in the glob, so a cached 1.0.0 index can be missing the filter targets the merge
70
71
  # restores.
71
- version: "1.1.0",
72
+ # Bumped 2026-09-10 (#534 item 7) — the plugin gained a bundled `sig/` (ADR-25) naming the
73
+ # `ActionController` namespace and the errors controllers rescue, so the constants stop resolving
74
+ # to `untyped`. `ActionController::Parameters` and the `ActionDispatch` readers stay undeclared;
75
+ # `sig/action_controller.rbs` documents why that absence is load-bearing.
76
+ version: "1.2.0",
72
77
  description: "Validates Action Pack route-helper calls and filter chains inside controllers, and types the request-context readers (`params` / `session` / `request` / `flash`) and their chains.",
73
78
  config_schema: {
74
79
  "controller_search_paths" => { kind: :array, default: ["app/controllers"] },
@@ -78,6 +83,26 @@ module Rigor
78
83
  { plugin_id: "rails-routes", name: :helper_table, optional: true },
79
84
  { plugin_id: "activerecord", name: :model_index, optional: true }
80
85
  ],
86
+ # ADR-25 (#534 item 7) — the framework-namespace signatures. See `sig/action_controller.rbs` for
87
+ # the admission rule and, more importantly, for what it refuses to declare.
88
+ signature_paths: ["sig"],
89
+ # ADR-26 — every class the bundled signature names is declared so the CONSTANT resolves; none of
90
+ # them enumerates a method surface, so `ParameterMissing#param` and its siblings must stay
91
+ # lenient rather than becoming `call.undefined-method` on a rescue body.
92
+ open_receivers: [
93
+ "ActionController::ActionControllerError",
94
+ "ActionController::BadRequest",
95
+ "ActionController::RoutingError",
96
+ "ActionController::UrlGenerationError",
97
+ "ActionController::MethodNotAllowed",
98
+ "ActionController::NotImplemented",
99
+ "ActionController::UnknownFormat",
100
+ "ActionController::InvalidAuthenticityToken",
101
+ "ActionController::InvalidCrossOriginRequest",
102
+ "ActionController::MissingExactTemplate",
103
+ "ActionController::ParameterMissing",
104
+ "ActionController::UnpermittedParameters"
105
+ ],
81
106
  # ADR-103 WD10 / WD14 (#387) — see {Effects} for what each row is and why.
82
107
  effect_root: "rails",
83
108
  effect_labels: %w[
@@ -144,7 +169,8 @@ module Rigor
144
169
  diagnostics_for(
145
170
  Analyzer.render_violations_for(
146
171
  call_node: node, ancestors: context.ancestors, path: path,
147
- view_search_roots: @view_search_paths, controller_index: producer_value(:controller_index)
172
+ view_search_roots: @view_search_paths, io_boundary: io_boundary,
173
+ controller_index: producer_value(:controller_index)
148
174
  ),
149
175
  path: path, node: node
150
176
  )
@@ -0,0 +1,71 @@
1
+ # Action Pack's framework namespaces — issue #534 item 7.
2
+ #
3
+ # The 2026-09-01 corpus sweep counted `ActionController` 37 times on mastodon as an unresolved
4
+ # constant. This file names the namespace and the errors controllers rescue, and nothing else.
5
+ #
6
+ # ## What must NOT be declared here, and why the list is so short
7
+ #
8
+ # `ActionController::Parameters` is absent on purpose, and it is the most important absence in this
9
+ # plugin. `Rigor::Plugin::Actionpack::REQUEST_CONTEXT_READER_TYPES` types `params` as a *lenient*
10
+ # nominal — a real class name Rigor ships no RBS for — precisely so that `params.require(...)`,
11
+ # `params.to_unsafe_h` and every other member of a surface no signature can complete resolve
12
+ # lenient-to-`Dynamic` instead of drawing `call.undefined-method`. Declaring the class here, even
13
+ # empty, would make it RBS-known and undo that. The same argument covers `ActionDispatch::Request`,
14
+ # `Request::Session`, `Flash::FlashHash`, `Flash::FlashNow` and `Cookies::CookieJar`.
15
+ #
16
+ # `ActionController::Base` and `ActionController::API` are absent for the stronger reason: every
17
+ # controller in a Rails app inherits from one of them, so declaring either empty would close the
18
+ # whole project's controller hierarchy and turn `render`, `before_action`, `head` and the rest into
19
+ # `call.undefined-method` on working code.
20
+ #
21
+ # What is left is exactly the shape that is safe: bare namespace modules nobody dispatches on, and
22
+ # exception classes whose inherited surface is a core Ruby one Rigor already carries. Each error
23
+ # class is listed in the manifest's `open_receivers:` (ADR-26), so the framework-specific readers
24
+ # this file does not enumerate (`ParameterMissing#param`, `UrlGenerationError#routes`, …) stay
25
+ # lenient too.
26
+ #
27
+ # Superclasses transcribed from Rails v8.0.2 (`actionpack/lib/action_controller/metal/exceptions.rb`
28
+ # and `strong_parameters.rb`); `ParameterMissing < KeyError` and `UnpermittedParameters < IndexError`
29
+ # are the two that are NOT under `ActionControllerError`, and getting that wrong would be a wrong
30
+ # `rescue` reachability answer, so both are stated from the source rather than from the pattern.
31
+
32
+ module ActionController
33
+ class ActionControllerError < StandardError
34
+ end
35
+
36
+ class BadRequest < ActionControllerError
37
+ end
38
+
39
+ class RoutingError < ActionControllerError
40
+ end
41
+
42
+ class UrlGenerationError < ActionControllerError
43
+ end
44
+
45
+ class MethodNotAllowed < ActionControllerError
46
+ end
47
+
48
+ class NotImplemented < MethodNotAllowed
49
+ end
50
+
51
+ class UnknownFormat < ActionControllerError
52
+ end
53
+
54
+ class InvalidAuthenticityToken < ActionControllerError
55
+ end
56
+
57
+ class InvalidCrossOriginRequest < ActionControllerError
58
+ end
59
+
60
+ class MissingExactTemplate < UnknownFormat
61
+ end
62
+
63
+ # `::KeyError` is generic in core RBS (`KeyError[K, R]`, the receiver and key of the failed lookup),
64
+ # so the supertype has to be applied. `untyped` on both: Rails raises it from a Parameters lookup and
65
+ # this signature has no business claiming what `#receiver` / `#key` answer.
66
+ class ParameterMissing < ::KeyError[untyped, untyped]
67
+ end
68
+
69
+ class UnpermittedParameters < ::IndexError
70
+ end
71
+ end
@@ -25,10 +25,6 @@ module Rigor
25
25
 
26
26
  # The job-call violations for a single call node (0..2), or `[]` when the node is not a
27
27
  # `<Job>.perform_*` entry call on a known job. ADR-37: the engine owns the walk.
28
- #
29
- # @param call_node [Prism::Node]
30
- # @param job_index [JobIndex]
31
- # @return [Array<Violation>]
32
28
  def violations_for(call_node:, job_index:)
33
29
  return [] unless call_node.is_a?(Prism::CallNode) && entry_call?(call_node)
34
30
 
@@ -61,7 +61,7 @@ module Rigor
61
61
 
62
62
  module_function
63
63
 
64
- # @param adapter [String, nil] the adapter the project declares, or nil when it declares none
64
+ # @param adapter the adapter the project declares, or nil when it declares none
65
65
  # (or more than one, across environments)
66
66
  def attributions(adapter)
67
67
  labels = (TRANSPORTS[adapter] || ["io"]) + MEANING
@@ -32,7 +32,6 @@ module Rigor
32
32
  @base_classes = base_classes.to_set { |name| strip_root(name.to_s) }
33
33
  end
34
34
 
35
- # @return [JobIndex]
36
35
  def discover
37
36
  candidates = []
38
37
  ruby_files_under(@search_paths).each do |path|
@@ -42,8 +42,6 @@ module Rigor
42
42
  # `"::WelcomeJob"`; see {JobDiscoverer}), while a QUERY may legitimately arrive rooted:
43
43
  # `::WelcomeJob.perform_later(1)` renders its receiver as `"::WelcomeJob"`. The root marker is
44
44
  # dropped here, once, so no caller needs a `find(name) || find("::#{name}")` retry (#621).
45
- #
46
- # @return [Entry, nil]
47
45
  def find(class_name)
48
46
  @by_name[strip_leading_namespace(class_name.to_s)]
49
47
  end
@@ -51,7 +51,7 @@ module Rigor
51
51
  @recurring_paths = recurring_paths
52
52
  end
53
53
 
54
- # @return [Array<String>] the class names named by a `class:` key in a recurring-task entry, sorted
54
+ # @return the class names named by a `class:` key in a recurring-task entry, sorted
55
55
  # and unique. NOT yet intersected with the discovered jobs — the caller does that.
56
56
  def job_names
57
57
  names = Set.new
@@ -37,6 +37,7 @@ module Rigor
37
37
  class Activejob < Rigor::Plugin::Base
38
38
  manifest(
39
39
  id: "activejob",
40
+ target_gems: ["activejob"],
40
41
  # Bumped — publishes `:reachability_roots` for `rigor unused` (ADR-102 WD3): the jobs Solid Queue's
41
42
  # recurring schedule runs by name, which no `perform_later` call site writes down.
42
43
  #
@@ -77,9 +77,8 @@ module Rigor
77
77
  # `ApplicationRecord`) setting `self.table_name_prefix` for every model under it — both already
78
78
  # guess wrong identically on unpatched code.
79
79
  class ModelDiscoverer
80
- # @param io_boundary [Rigor::Plugin::IoBoundary]
81
- # @param search_paths [Array<String>] absolute or project-relative paths.
82
- # @param base_classes [Array<String>] superclass names that identify a class as an AR model.
80
+ # @param search_paths — absolute or project-relative paths.
81
+ # @param base_classes superclass names that identify a class as an AR model.
83
82
  # Declaration macros whose column's runtime value is a rich object, not the SQL scalar. Their column
84
83
  # must NOT be narrowed to the schema type (see {ModelIndex.build}'s type-override remap).
85
84
  TYPE_OVERRIDE_METHODS = %i[serialize mount_uploader mount_uploaders].freeze
@@ -113,7 +112,7 @@ module Rigor
113
112
 
114
113
  attr_reader :type_override_columns
115
114
 
116
- # @return [Array<Hash>] rows of { class_name:, table_name_override:, sti_parent:, ... }
115
+ # @return rows of { class_name:, table_name_override:, sti_parent:, ... }
117
116
  def discover
118
117
  candidates = []
119
118
  ruby_files_under(@search_paths).each do |path|
@@ -45,8 +45,7 @@ module Rigor
45
45
  unique_constraint foreign_key primary_keys
46
46
  ].freeze
47
47
 
48
- # @param source [String] contents of `db/schema.rb`
49
- # @return [SchemaTable]
48
+ # @param source contents of `db/schema.rb`
50
49
  def self.parse(source)
51
50
  tree = Prism.parse(source).value
52
51
  new.parse(tree)
@@ -54,8 +54,7 @@ module Rigor
54
54
  # Uppercase leading keywords that mark a table-level constraint line rather than a column.
55
55
  CONSTRAINT_KEYWORDS = %w[CONSTRAINT PRIMARY UNIQUE CHECK FOREIGN EXCLUDE LIKE PARTITION].freeze
56
56
 
57
- # @param source [String] contents of `db/structure.sql`
58
- # @return [SchemaTable]
57
+ # @param source contents of `db/structure.sql`
59
58
  def self.parse(source)
60
59
  new.parse(source)
61
60
  end
@@ -63,6 +63,12 @@ module Rigor
63
63
  class Activerecord < Rigor::Plugin::Base
64
64
  manifest(
65
65
  id: "activerecord",
66
+ target_gems: ["activerecord"],
67
+ # 0.9.0, 2026-09-10 (#534 item 7) — the bundled signatures gained `sig/active_record/framework.rbs`:
68
+ # the Active Record exception hierarchy, the `ActiveModel` namespace and `Arel`. No producer payload
69
+ # changed shape, but the version is the cache key a project sees, and the RBS half of a plugin's
70
+ # contribution rides the same bundle.
71
+ #
66
72
  # 0.8.0, 2026-09-02 — a model declared `class ::User` is keyed by its de-rooted name (`"User"`) in
67
73
  # the {ModelIndex} and the published `:model_index` fact (#583). The version is part of the
68
74
  # producer cache KEY: a cached 0.7.0 index still keys such a model `"::User"`, and `ModelIndex#find`
@@ -87,7 +93,7 @@ module Rigor
87
93
  # a scope lambda body / class-method body now contributes `Relation[Model]` via `scope.self_type`
88
94
  # instead of falling through to `Kernel#select` (the IO multiplexer, `Array[String]` return). Plus
89
95
  # `:select` added to the relation-entry-point list.
90
- version: "0.8.0",
96
+ version: "0.9.0",
91
97
  description: "Types ActiveRecord finders against the project's db/schema.rb and AR models.",
92
98
  config_schema: {
93
99
  "schema_file" => { kind: :string, default: "db/schema.rb" },
@@ -104,7 +110,28 @@ module Rigor
104
110
  # ADR-26 — `ActiveRecord::Relation` is an "open" receiver: it delegates an unbounded set of
105
111
  # user-defined scopes / class methods to its model, so `call.undefined-method` must not fire for
106
112
  # it. `CheckRules` reads this manifest field and skips the rule for the class.
107
- open_receivers: ["ActiveRecord::Relation"],
113
+ #
114
+ # The `sig/active_record/framework.rbs` names (#534 item 7) are open for the narrower reason that
115
+ # file documents: each is declared so the CONSTANT resolves, and none of them enumerates a method
116
+ # surface. `RecordInvalid#record`, `StatementInvalid#sql`, `Arel.star` and every other member left
117
+ # out must stay lenient, so the declaration buys constant resolution and asserts nothing else.
118
+ open_receivers: [
119
+ "ActiveRecord::Relation",
120
+ "ActiveRecord::ActiveRecordError",
121
+ "ActiveRecord::RecordNotFound",
122
+ "ActiveRecord::RecordInvalid",
123
+ "ActiveRecord::RecordNotSaved",
124
+ "ActiveRecord::RecordNotDestroyed",
125
+ "ActiveRecord::ReadOnlyRecord",
126
+ "ActiveRecord::Rollback",
127
+ "ActiveRecord::StaleObjectError",
128
+ "ActiveRecord::ConnectionNotEstablished",
129
+ "ActiveRecord::StatementInvalid",
130
+ "ActiveRecord::WrappedDatabaseException",
131
+ "ActiveRecord::RecordNotUnique",
132
+ "ActiveModel::ValidationError",
133
+ "Arel"
134
+ ],
108
135
  # ADR-103 WD2 / WD10 (#387) — the effect layer. rigor-activerecord models ActiveRecord, which is
109
136
  # part of Rails, so it opens the framework's own `rails.*` root rather than one named after the
110
137
  # plugin; `effect_root:` is granted only because the engine bundles this plugin
@@ -512,15 +539,23 @@ module Rigor
512
539
  return bool_type if predicate
513
540
 
514
541
  inner = ruby_type_to_type(column.ruby_type)
515
- return nil if inner.nil?
542
+ return Rigor::Type::Combinator.untyped if inner.nil?
516
543
 
517
544
  column.array? ? Rigor::Type::Combinator.nominal_of("Array", type_args: [inner]) : inner
518
545
  end
519
546
 
520
- # Maps a `SchemaTable::Column#ruby_type` string to a Rigor type. `"Object"` (json / jsonb /
521
- # unrecognised column types) declines — `Nominal[Object]` would be NARROWER than the RBS-erased
522
- # envelope and could surface false `call.undefined-method` on a value whose real shape the plugin
523
- # cannot model.
547
+ # Maps a `SchemaTable::Column#ruby_type` string to a Rigor type. `"Object"` (json / jsonb / a
548
+ # `serialize`d or `mount_uploader`ed column / an unrecognised SQL type) declines — `Nominal[Object]`
549
+ # would be NARROWER than the RBS-erased envelope and could surface false `call.undefined-method` on a
550
+ # value whose real shape the plugin cannot model.
551
+ #
552
+ # ADR-82 WD4 — the caller turns that decline into an explicit `Dynamic[top]` rather than passing the
553
+ # call on. The two are the same type at the site (dispatch widens an unresolved reader to Dynamic
554
+ # anyway), but only the plugin ANSWERING makes the engine record `framework_dsl_boundary`: this is a
555
+ # macro-generated attribute whose value crosses a DSL boundary and stays dynamic, which is exactly the
556
+ # cause WD4 defined and left unattributed. Answering here is honest in the way WD4 requires — the
557
+ # plugin knows the reader exists (the schema declares the column) and knows it cannot type it — where
558
+ # guessing "framework boundary" for any unresolved receiver would not be.
524
559
  def ruby_type_to_type(ruby_type)
525
560
  case ruby_type
526
561
  when "bool" then bool_type