rigortype 0.3.8 → 0.4.0

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 (634) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/data/capability_roles/capability_roles.rbs +36 -0
  4. data/data/core_overlay/enumerable.rbs +51 -0
  5. data/data/core_overlay/enumerator.rbs +84 -0
  6. data/data/core_overlay/hash_rbs3.rbs +41 -0
  7. data/data/core_overlay/process.rbs +40 -0
  8. data/data/core_overlay/string_io.rbs +33 -0
  9. data/data/effects/core.yml +3 -3
  10. data/data/gem_overlay/activesupport/core_ext.rbs +236 -17
  11. data/docs/handbook/02-everyday-types.md +17 -14
  12. data/docs/handbook/03-narrowing.md +132 -14
  13. data/docs/handbook/04-tuples-and-shapes.md +8 -6
  14. data/docs/handbook/05-methods-and-blocks.md +1 -1
  15. data/docs/handbook/06-classes.md +2 -2
  16. data/docs/handbook/07-rbs-and-extended.md +19 -1
  17. data/docs/handbook/10-sorbet.md +9 -10
  18. data/docs/handbook/11-sig-gen.md +525 -13
  19. data/docs/handbook/README.md +1 -1
  20. data/docs/handbook/appendix-elixir.md +2 -2
  21. data/docs/handbook/appendix-go.md +2 -2
  22. data/docs/handbook/appendix-java-csharp.md +2 -2
  23. data/docs/handbook/appendix-mypy.md +2 -2
  24. data/docs/handbook/appendix-phpstan.md +1 -1
  25. data/docs/handbook/appendix-rust.md +1 -1
  26. data/docs/handbook/appendix-type-theory.md +4 -4
  27. data/docs/handbook/appendix-typescript.md +1 -1
  28. data/docs/llms.txt +2 -0
  29. data/docs/manual/02-cli-reference.md +95 -6
  30. data/docs/manual/03-configuration.md +18 -0
  31. data/docs/manual/04-diagnostics.md +15 -0
  32. data/docs/manual/07-plugins.md +15 -5
  33. data/docs/manual/08-skills.md +23 -2
  34. data/docs/manual/10-mcp-server.md +3 -2
  35. data/docs/manual/11-ci.md +9 -0
  36. data/docs/manual/15-type-protection-coverage.md +8 -0
  37. data/docs/manual/16-rbs-extended-annotations.md +46 -8
  38. data/docs/manual/18-removing-dead-code.md +10 -8
  39. data/docs/manual/19-effect-labels.md +10 -0
  40. data/docs/manual/plugins/README.md +7 -0
  41. data/docs/manual/plugins/rigor-actioncable.md +8 -1
  42. data/docs/manual/plugins/rigor-actionmailer.md +7 -0
  43. data/docs/manual/plugins/rigor-actionpack.md +261 -0
  44. data/docs/manual/plugins/rigor-active-model-serializers.md +153 -0
  45. data/docs/manual/plugins/rigor-activejob.md +7 -0
  46. data/docs/manual/plugins/rigor-activerecord.md +138 -4
  47. data/docs/manual/plugins/rigor-activestorage.md +8 -1
  48. data/docs/manual/plugins/rigor-activesupport-core-ext.md +43 -7
  49. data/docs/manual/plugins/rigor-grape.md +106 -0
  50. data/docs/manual/plugins/rigor-graphql.md +23 -2
  51. data/docs/manual/plugins/rigor-pundit.md +8 -1
  52. data/docs/manual/plugins/rigor-rails-i18n.md +10 -5
  53. data/docs/manual/plugins/rigor-rails-routes.md +7 -0
  54. data/docs/manual/plugins/rigor-rbs-inline.md +229 -1
  55. data/docs/manual/plugins/rigor-sidekiq.md +8 -1
  56. data/docs/manual/plugins/rigor-sorbet.md +21 -5
  57. data/exe/rigor +19 -4
  58. data/lib/rigor/analysis/baseline.rb +3 -3
  59. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +1 -1
  60. data/lib/rigor/analysis/check_rules/dead_version_guard_arms.rb +1 -5
  61. data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +167 -2
  62. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +10 -2
  63. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +189 -0
  64. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +6 -4
  65. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +15 -6
  66. data/lib/rigor/analysis/check_rules/rule_ids.rb +21 -6
  67. data/lib/rigor/analysis/check_rules/rule_walk.rb +41 -5
  68. data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +1 -1
  69. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +1 -1
  70. data/lib/rigor/analysis/check_rules/source_arity.rb +323 -0
  71. data/lib/rigor/analysis/check_rules/special_global_setters.rb +146 -0
  72. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +38 -4
  73. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +0 -1
  74. data/lib/rigor/analysis/check_rules.rb +718 -79
  75. data/lib/rigor/analysis/crash_signature.rb +8 -24
  76. data/lib/rigor/analysis/dependency_recorder.rb +23 -0
  77. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +1 -1
  78. data/lib/rigor/analysis/dependency_source_inference/builder.rb +0 -2
  79. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +0 -2
  80. data/lib/rigor/analysis/dependency_source_inference/index.rb +5 -5
  81. data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +1 -2
  82. data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
  83. data/lib/rigor/analysis/effects_cache_probe.rb +3 -4
  84. data/lib/rigor/analysis/erb_template_detector.rb +1 -2
  85. data/lib/rigor/analysis/fact_store.rb +15 -1
  86. data/lib/rigor/analysis/incremental.rb +42 -0
  87. data/lib/rigor/analysis/incremental_session.rb +117 -26
  88. data/lib/rigor/analysis/plugin_fact_fingerprint.rb +0 -1
  89. data/lib/rigor/analysis/project_scan.rb +11 -1
  90. data/lib/rigor/analysis/reachability/graph.rb +3 -5
  91. data/lib/rigor/analysis/reachability/plugin_roots.rb +5 -5
  92. data/lib/rigor/analysis/reachability/project_files.rb +2 -2
  93. data/lib/rigor/analysis/reachability/scan.rb +5 -5
  94. data/lib/rigor/analysis/reachability/scan_cache.rb +3 -2
  95. data/lib/rigor/analysis/reachability/signature_scan.rb +2 -2
  96. data/lib/rigor/analysis/result.rb +1 -3
  97. data/lib/rigor/analysis/rule_catalog.rb +131 -1
  98. data/lib/rigor/analysis/run_cache_key.rb +21 -12
  99. data/lib/rigor/analysis/run_cache_probe.rb +3 -5
  100. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -8
  101. data/lib/rigor/analysis/runner/declaration_position.rb +1 -2
  102. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +340 -33
  103. data/lib/rigor/analysis/runner/effect_annotation_residual_pass.rb +2 -3
  104. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +18 -14
  105. data/lib/rigor/analysis/runner/pool_coordinator.rb +357 -97
  106. data/lib/rigor/analysis/runner/project_pre_passes.rb +38 -13
  107. data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
  108. data/lib/rigor/analysis/runner.rb +407 -51
  109. data/lib/rigor/analysis/template_unit_collector.rb +303 -0
  110. data/lib/rigor/analysis/template_unit_paths.rb +91 -0
  111. data/lib/rigor/analysis/template_unit_positions.rb +293 -0
  112. data/lib/rigor/analysis/template_units.rb +399 -0
  113. data/lib/rigor/analysis/worker_session.rb +62 -20
  114. data/lib/rigor/bleeding_edge.rb +10 -24
  115. data/lib/rigor/builtins/hkt_builtins.rb +3 -2
  116. data/lib/rigor/builtins/imported_refinements.rb +159 -21
  117. data/lib/rigor/builtins/predefined_constant_refinements.rb +5 -7
  118. data/lib/rigor/builtins/regex_refinement.rb +21 -13
  119. data/lib/rigor/builtins/static_return_refinements.rb +7 -6
  120. data/lib/rigor/cache/annotation_location.rb +2 -4
  121. data/lib/rigor/cache/descriptor.rb +63 -20
  122. data/lib/rigor/cache/engine_source.rb +33 -5
  123. data/lib/rigor/cache/file_digest.rb +9 -1
  124. data/lib/rigor/cache/incremental_snapshot.rb +72 -6
  125. data/lib/rigor/cache/rbs_cache_producer.rb +4 -0
  126. data/lib/rigor/cache/rbs_class_ancestor_table.rb +0 -3
  127. data/lib/rigor/cache/rbs_class_type_param_names.rb +0 -3
  128. data/lib/rigor/cache/rbs_constant_table.rb +0 -3
  129. data/lib/rigor/cache/rbs_descriptor.rb +112 -14
  130. data/lib/rigor/cache/rbs_environment.rb +8 -4
  131. data/lib/rigor/cache/rbs_known_class_names.rb +0 -3
  132. data/lib/rigor/cache/store.rb +76 -32
  133. data/lib/rigor/ci_detector.rb +1 -0
  134. data/lib/rigor/cli/annotate_command.rb +5 -6
  135. data/lib/rigor/cli/check_command.rb +57 -10
  136. data/lib/rigor/cli/check_invocation.rb +6 -11
  137. data/lib/rigor/cli/check_runner_factory.rb +1 -5
  138. data/lib/rigor/cli/coverage_command.rb +1 -1
  139. data/lib/rigor/cli/coverage_mutation.rb +12 -3
  140. data/lib/rigor/cli/coverage_scan.rb +1 -5
  141. data/lib/rigor/cli/diff_command.rb +1 -1
  142. data/lib/rigor/cli/doc_links.rb +4 -4
  143. data/lib/rigor/cli/docs_command.rb +6 -6
  144. data/lib/rigor/cli/doctor_command.rb +85 -38
  145. data/lib/rigor/cli/effects_command.rb +2 -2
  146. data/lib/rigor/cli/effects_diff_renderer.rb +3 -3
  147. data/lib/rigor/cli/effects_snapshot_command.rb +1 -1
  148. data/lib/rigor/cli/explain_command.rb +34 -1
  149. data/lib/rigor/cli/fused_protection_report.rb +7 -1
  150. data/lib/rigor/cli/lsp_command.rb +1 -1
  151. data/lib/rigor/cli/mcp_command.rb +1 -1
  152. data/lib/rigor/cli/measurement_integrity_warning.rb +4 -5
  153. data/lib/rigor/cli/mutation_fork_scan.rb +6 -6
  154. data/lib/rigor/cli/mutation_protection_report.rb +7 -1
  155. data/lib/rigor/cli/plugin_command.rb +4 -4
  156. data/lib/rigor/cli/plugins_command.rb +2 -1
  157. data/lib/rigor/cli/prism_colorizer.rb +1 -2
  158. data/lib/rigor/cli/protection_fork_scan.rb +6 -6
  159. data/lib/rigor/cli/show_bleedingedge_command.rb +1 -1
  160. data/lib/rigor/cli/sig_gen_command.rb +212 -31
  161. data/lib/rigor/cli/skill_command.rb +2 -2
  162. data/lib/rigor/cli/skill_deep_probe.rb +4 -4
  163. data/lib/rigor/cli/skill_describe.rb +35 -35
  164. data/lib/rigor/cli/trace_command.rb +4 -4
  165. data/lib/rigor/cli/trace_renderer.rb +5 -6
  166. data/lib/rigor/cli/triage_command.rb +1 -1
  167. data/lib/rigor/cli/type_of_command.rb +29 -11
  168. data/lib/rigor/cli/type_of_renderer.rb +20 -9
  169. data/lib/rigor/cli/type_of_template_probe.rb +189 -0
  170. data/lib/rigor/cli/type_scan_command.rb +4 -4
  171. data/lib/rigor/cli/unused_command.rb +11 -3
  172. data/lib/rigor/cli/upgrade_command.rb +1 -1
  173. data/lib/rigor/cli.rb +89 -9
  174. data/lib/rigor/config_audit.rb +30 -5
  175. data/lib/rigor/configuration/severity_profile.rb +25 -9
  176. data/lib/rigor/configuration.rb +109 -7
  177. data/lib/rigor/effects/ancestry_recorder.rb +191 -0
  178. data/lib/rigor/effects/attribution.rb +12 -5
  179. data/lib/rigor/effects/callee_rule.rb +368 -0
  180. data/lib/rigor/effects/catalog.rb +7 -4
  181. data/lib/rigor/effects/collector.rb +11 -5
  182. data/lib/rigor/effects/config_envelopes.rb +15 -10
  183. data/lib/rigor/effects/definition_context.rb +179 -0
  184. data/lib/rigor/effects/definition_lines.rb +25 -6
  185. data/lib/rigor/effects/effect_table.rb +11 -4
  186. data/lib/rigor/effects/entry_points.rb +0 -2
  187. data/lib/rigor/effects/envelope_check.rb +9 -9
  188. data/lib/rigor/effects/envelope_index.rb +20 -8
  189. data/lib/rigor/effects/file_collection.rb +59 -5
  190. data/lib/rigor/effects/framework_units.rb +5 -7
  191. data/lib/rigor/effects/identity.rb +20 -6
  192. data/lib/rigor/effects/inline_anchor.rb +7 -7
  193. data/lib/rigor/effects/label_intent.rb +3 -4
  194. data/lib/rigor/effects/liskov_check.rb +8 -8
  195. data/lib/rigor/effects/local_ownership.rb +38 -12
  196. data/lib/rigor/effects/method_key.rb +22 -1
  197. data/lib/rigor/effects/mutation_classifier.rb +23 -12
  198. data/lib/rigor/effects/plugin_facts.rb +47 -37
  199. data/lib/rigor/effects/propagator.rb +297 -19
  200. data/lib/rigor/effects/registry.rb +1 -1
  201. data/lib/rigor/effects/scanner.rb +122 -73
  202. data/lib/rigor/effects/signature_sources.rb +4 -6
  203. data/lib/rigor/effects/snapshot.rb +12 -11
  204. data/lib/rigor/effects/snapshot_diff.rb +1 -1
  205. data/lib/rigor/effects/summary.rb +27 -4
  206. data/lib/rigor/effects/unit_scan.rb +393 -38
  207. data/lib/rigor/effects/unknown_label_check.rb +3 -8
  208. data/lib/rigor/effects/unknown_label_report.rb +3 -4
  209. data/lib/rigor/effects/visibility.rb +101 -0
  210. data/lib/rigor/environment/bundle_sig_discovery.rb +6 -6
  211. data/lib/rigor/environment/class_registry.rb +3 -1
  212. data/lib/rigor/environment/failure_slot.rb +2 -2
  213. data/lib/rigor/environment/installed_gem_set.rb +85 -0
  214. data/lib/rigor/environment/lockfile_resolver.rb +68 -4
  215. data/lib/rigor/environment/member_consistency/comparator.rb +708 -0
  216. data/lib/rigor/environment/member_consistency.rb +298 -0
  217. data/lib/rigor/environment/missing_gem_constant_index.rb +4 -4
  218. data/lib/rigor/environment/rbs_collection_discovery.rb +5 -5
  219. data/lib/rigor/environment/rbs_coverage_report.rb +5 -5
  220. data/lib/rigor/environment/rbs_hierarchy.rb +3 -1
  221. data/lib/rigor/environment/rbs_loader.rb +674 -79
  222. data/lib/rigor/environment.rb +174 -24
  223. data/lib/rigor/flow_contribution/merger.rb +0 -2
  224. data/lib/rigor/flow_contribution.rb +11 -13
  225. data/lib/rigor/hashing/xxh3.rb +264 -0
  226. data/lib/rigor/inference/acceptance.rb +292 -17
  227. data/lib/rigor/inference/block_auto_splat.rb +216 -0
  228. data/lib/rigor/inference/block_call_timing.rb +338 -0
  229. data/lib/rigor/inference/block_parameter_binder.rb +75 -30
  230. data/lib/rigor/inference/block_repetition.rb +71 -0
  231. data/lib/rigor/inference/body_fixpoint.rb +7 -6
  232. data/lib/rigor/inference/budget_trace.rb +3 -6
  233. data/lib/rigor/inference/builtins/method_catalog.rb +2 -1
  234. data/lib/rigor/inference/builtins/string_catalog.rb +1 -1
  235. data/lib/rigor/inference/captured_locals.rb +389 -18
  236. data/lib/rigor/inference/closure_escape_analyzer.rb +159 -17
  237. data/lib/rigor/inference/content_join.rb +200 -27
  238. data/lib/rigor/inference/coverage_scanner.rb +4 -6
  239. data/lib/rigor/inference/def_return_typer.rb +11 -7
  240. data/lib/rigor/inference/define_method_block_self.rb +64 -0
  241. data/lib/rigor/inference/dynamic_origin.rb +1 -1
  242. data/lib/rigor/inference/element_read_widening.rb +180 -0
  243. data/lib/rigor/inference/error_info.rb +196 -0
  244. data/lib/rigor/inference/expression_typer.rb +2124 -468
  245. data/lib/rigor/inference/external_ancestor_resolution.rb +267 -0
  246. data/lib/rigor/inference/fork_map.rb +5 -7
  247. data/lib/rigor/inference/fresh_frame_blocks.rb +127 -0
  248. data/lib/rigor/inference/global_write_census.rb +239 -0
  249. data/lib/rigor/inference/guard_rebinding.rb +447 -0
  250. data/lib/rigor/inference/hash_lookup_mutation.rb +88 -0
  251. data/lib/rigor/inference/hkt_reducer.rb +2 -3
  252. data/lib/rigor/inference/hkt_registry.rb +4 -7
  253. data/lib/rigor/inference/index_write_widening.rb +16 -7
  254. data/lib/rigor/inference/indexed_narrowing.rb +61 -9
  255. data/lib/rigor/inference/jump_targets.rb +82 -0
  256. data/lib/rigor/inference/last_line/implicit_self.rb +210 -0
  257. data/lib/rigor/inference/last_line/self_evidence.rb +329 -0
  258. data/lib/rigor/inference/last_line.rb +340 -0
  259. data/lib/rigor/inference/last_status.rb +144 -0
  260. data/lib/rigor/inference/macro_block_self_type.rb +168 -21
  261. data/lib/rigor/inference/match_rebinding/calls.rb +281 -0
  262. data/lib/rigor/inference/match_rebinding/frame.rb +148 -0
  263. data/lib/rigor/inference/match_rebinding/operands.rb +236 -0
  264. data/lib/rigor/inference/match_rebinding/self_calls.rb +83 -0
  265. data/lib/rigor/inference/match_rebinding.rb +392 -0
  266. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
  267. data/lib/rigor/inference/method_dispatcher/block_folding.rb +87 -33
  268. data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +1 -1
  269. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +305 -19
  270. data/lib/rigor/inference/method_dispatcher/data_folding.rb +1 -1
  271. data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +147 -0
  272. data/lib/rigor/inference/method_dispatcher/file_folding.rb +1 -1
  273. data/lib/rigor/inference/method_dispatcher/hash_transform_keys_folding.rb +191 -0
  274. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +6 -2
  275. data/lib/rigor/inference/method_dispatcher/json_folding.rb +1 -1
  276. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -1
  277. data/lib/rigor/inference/method_dispatcher/match_data_folding.rb +159 -0
  278. data/lib/rigor/inference/method_dispatcher/math_folding.rb +41 -3
  279. data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -5
  280. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +165 -111
  281. data/lib/rigor/inference/method_dispatcher/process_folding.rb +56 -0
  282. data/lib/rigor/inference/method_dispatcher/proven_overload.rb +72 -0
  283. data/lib/rigor/inference/method_dispatcher/random_folding.rb +82 -0
  284. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +1046 -69
  285. data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +25 -4
  286. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +2 -8
  287. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +1 -1
  288. data/lib/rigor/inference/method_dispatcher/self_substitute.rb +142 -0
  289. data/lib/rigor/inference/method_dispatcher/set_folding.rb +1 -1
  290. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +129 -54
  291. data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +1 -1
  292. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +1 -2
  293. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -6
  294. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +85 -4
  295. data/lib/rigor/inference/method_dispatcher/time_folding.rb +1 -1
  296. data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +1 -2
  297. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  298. data/lib/rigor/inference/method_dispatcher.rb +225 -25
  299. data/lib/rigor/inference/method_parameter_binder.rb +12 -9
  300. data/lib/rigor/inference/multi_target_binder.rb +341 -55
  301. data/lib/rigor/inference/mutation_rejoin.rb +171 -0
  302. data/lib/rigor/inference/mutation_widening.rb +156 -85
  303. data/lib/rigor/inference/narrowing.rb +835 -162
  304. data/lib/rigor/inference/operand_effects.rb +167 -0
  305. data/lib/rigor/inference/operand_walk.rb +88 -0
  306. data/lib/rigor/inference/optimistic_origin.rb +153 -16
  307. data/lib/rigor/inference/origin_lookup.rb +2 -3
  308. data/lib/rigor/inference/parameter_inference_collector.rb +7 -7
  309. data/lib/rigor/inference/pre_eval_constants.rb +6 -5
  310. data/lib/rigor/inference/precision_scanner.rb +3 -4
  311. data/lib/rigor/inference/project_method_ownership.rb +136 -0
  312. data/lib/rigor/inference/project_patched_methods.rb +9 -4
  313. data/lib/rigor/inference/project_patched_scanner.rb +10 -6
  314. data/lib/rigor/inference/protection_scanner.rb +1 -2
  315. data/lib/rigor/inference/range_constant.rb +57 -0
  316. data/lib/rigor/inference/rbs_type_translator.rb +49 -11
  317. data/lib/rigor/inference/receiver_alias.rb +93 -4
  318. data/lib/rigor/inference/receiver_blind_block.rb +219 -0
  319. data/lib/rigor/inference/refinement_mutation.rb +76 -0
  320. data/lib/rigor/inference/repeated_or_writes.rb +463 -0
  321. data/lib/rigor/inference/return_barrier.rb +54 -0
  322. data/lib/rigor/inference/rewrite_mutation.rb +120 -0
  323. data/lib/rigor/inference/scope_indexer.rb +5228 -627
  324. data/lib/rigor/inference/statement_evaluator.rb +3452 -595
  325. data/lib/rigor/inference/stored_block_call.rb +54 -0
  326. data/lib/rigor/inference/string_mutation.rb +97 -0
  327. data/lib/rigor/inference/struct_fold_safety.rb +6 -7
  328. data/lib/rigor/inference/synthetic_method_index.rb +1 -2
  329. data/lib/rigor/inference/synthetic_method_scanner.rb +4 -6
  330. data/lib/rigor/inference/unknown_store_widening.rb +200 -0
  331. data/lib/rigor/inference/unthreaded_rebinds.rb +282 -0
  332. data/lib/rigor/inference/version_guard.rb +28 -21
  333. data/lib/rigor/inference/void_origin.rb +3 -3
  334. data/lib/rigor/inference/void_tail_summary.rb +2 -4
  335. data/lib/rigor/language_server/buffer_table.rb +4 -4
  336. data/lib/rigor/language_server/completion_provider.rb +1 -1
  337. data/lib/rigor/language_server/debouncer.rb +0 -1
  338. data/lib/rigor/language_server/diagnostic_publisher.rb +7 -6
  339. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  340. data/lib/rigor/language_server/folding_range_provider.rb +1 -1
  341. data/lib/rigor/language_server/hover_provider.rb +1 -1
  342. data/lib/rigor/language_server/hover_renderer.rb +4 -4
  343. data/lib/rigor/language_server/incremental_sync.rb +6 -6
  344. data/lib/rigor/language_server/project_context.rb +7 -8
  345. data/lib/rigor/language_server/publish_batcher.rb +2 -2
  346. data/lib/rigor/language_server/selection_range_provider.rb +2 -2
  347. data/lib/rigor/language_server/server.rb +9 -9
  348. data/lib/rigor/language_server/signature_help_provider.rb +1 -1
  349. data/lib/rigor/language_server/uri.rb +1 -1
  350. data/lib/rigor/mcp/server.rb +2 -1
  351. data/lib/rigor/plugin/base.rb +258 -26
  352. data/lib/rigor/plugin/box_probe.rb +91 -0
  353. data/lib/rigor/plugin/bundled_catalog.rb +167 -0
  354. data/lib/rigor/plugin/effect_attribution.rb +65 -11
  355. data/lib/rigor/plugin/effect_entry_points.rb +3 -3
  356. data/lib/rigor/plugin/fact_store.rb +6 -6
  357. data/lib/rigor/plugin/io_boundary.rb +93 -8
  358. data/lib/rigor/plugin/isolation.rb +29 -6
  359. data/lib/rigor/plugin/loader.rb +33 -13
  360. data/lib/rigor/plugin/macro/block_as_method.rb +45 -6
  361. data/lib/rigor/plugin/macro/heredoc_template.rb +1 -1
  362. data/lib/rigor/plugin/macro/trait_registry.rb +1 -1
  363. data/lib/rigor/plugin/manifest.rb +94 -11
  364. data/lib/rigor/plugin/registry.rb +76 -12
  365. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +21 -4
  366. data/lib/rigor/plugin/template_unit.rb +196 -0
  367. data/lib/rigor/plugin/trust_policy.rb +2 -4
  368. data/lib/rigor/plugin/type_node_resolver.rb +3 -3
  369. data/lib/rigor/plugin.rb +1 -0
  370. data/lib/rigor/plugin_gap_advisory.rb +96 -0
  371. data/lib/rigor/project_environment.rb +138 -0
  372. data/lib/rigor/protection/analysis_guard.rb +93 -14
  373. data/lib/rigor/protection/closure_kill_oracle.rb +67 -23
  374. data/lib/rigor/protection/dependency_closure.rb +5 -8
  375. data/lib/rigor/protection/diagnostic_oracle.rb +2 -2
  376. data/lib/rigor/protection/discovery_seed.rb +14 -12
  377. data/lib/rigor/protection/kill_signature.rb +1 -4
  378. data/lib/rigor/protection/measurement_integrity.rb +1 -3
  379. data/lib/rigor/protection/mutation_cache.rb +7 -11
  380. data/lib/rigor/protection/mutation_scanner.rb +12 -15
  381. data/lib/rigor/protection/mutator.rb +2 -1
  382. data/lib/rigor/protection/test_suite_oracle.rb +5 -5
  383. data/lib/rigor/rbs_extended/conformance_checker.rb +4 -3
  384. data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
  385. data/lib/rigor/rbs_extended/reporter.rb +36 -10
  386. data/lib/rigor/rbs_extended.rb +88 -21
  387. data/lib/rigor/reflection/constant_ancestors.rb +97 -0
  388. data/lib/rigor/reflection/constant_path.rb +139 -0
  389. data/lib/rigor/reflection.rb +93 -110
  390. data/lib/rigor/runtime/jit.rb +6 -8
  391. data/lib/rigor/scope/discovery_index.rb +165 -2
  392. data/lib/rigor/scope.rb +1041 -32
  393. data/lib/rigor/sig_gen/alias_index.rb +289 -0
  394. data/lib/rigor/sig_gen/classification.rb +22 -5
  395. data/lib/rigor/sig_gen/declaration_equivalence.rb +127 -0
  396. data/lib/rigor/sig_gen/effect_annotation.rb +202 -0
  397. data/lib/rigor/sig_gen/generator.rb +673 -53
  398. data/lib/rigor/sig_gen/inline_declarations.rb +184 -0
  399. data/lib/rigor/sig_gen/layout_index.rb +3 -4
  400. data/lib/rigor/sig_gen/meta_class_shape.rb +3 -5
  401. data/lib/rigor/sig_gen/method_candidate.rb +47 -3
  402. data/lib/rigor/sig_gen/observation_collector.rb +18 -16
  403. data/lib/rigor/sig_gen/path_mapper.rb +4 -6
  404. data/lib/rigor/sig_gen/rbs_validity.rb +4 -4
  405. data/lib/rigor/sig_gen/renderer.rb +128 -14
  406. data/lib/rigor/sig_gen/skip_reason_catalog.rb +154 -0
  407. data/lib/rigor/sig_gen/superclass_spelling.rb +27 -0
  408. data/lib/rigor/sig_gen/type_elaborator.rb +1 -3
  409. data/lib/rigor/sig_gen/write_result.rb +19 -3
  410. data/lib/rigor/sig_gen/writer.rb +189 -41
  411. data/lib/rigor/sig_gen.rb +3 -0
  412. data/lib/rigor/signature_path_audit.rb +153 -6
  413. data/lib/rigor/source/literals.rb +0 -23
  414. data/lib/rigor/source/node_children.rb +0 -2
  415. data/lib/rigor/source/node_locator.rb +5 -11
  416. data/lib/rigor/source/node_walker.rb +0 -6
  417. data/lib/rigor/source/parameter_envelope.rb +72 -0
  418. data/lib/rigor/source.rb +1 -0
  419. data/lib/rigor/triage/catalogue.rb +1 -3
  420. data/lib/rigor/triage.rb +3 -5
  421. data/lib/rigor/type/accepts_result.rb +24 -6
  422. data/lib/rigor/type/combinator.rb +144 -5
  423. data/lib/rigor/type/data_class.rb +2 -2
  424. data/lib/rigor/type/data_instance.rb +4 -4
  425. data/lib/rigor/type/difference.rb +1 -0
  426. data/lib/rigor/type/float_range.rb +128 -0
  427. data/lib/rigor/type/hash_shape.rb +6 -6
  428. data/lib/rigor/type/integer_range.rb +13 -8
  429. data/lib/rigor/type/nominal.rb +8 -2
  430. data/lib/rigor/type/refined.rb +4 -3
  431. data/lib/rigor/type/struct_class.rb +3 -3
  432. data/lib/rigor/type/struct_instance.rb +4 -4
  433. data/lib/rigor/type.rb +1 -0
  434. data/lib/rigor/type_node/generic.rb +1 -1
  435. data/lib/rigor/type_node/range_literal.rb +25 -0
  436. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  437. data/lib/rigor/type_node.rb +1 -0
  438. data/lib/rigor/version.rb +1 -1
  439. data/lib/rigor.rb +1 -0
  440. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +0 -4
  441. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +0 -1
  442. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +0 -2
  443. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/effects.rb +1 -0
  444. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +10 -5
  445. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +0 -4
  446. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +3 -5
  447. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +1 -4
  448. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +14 -4
  449. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +32 -30
  450. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +0 -1
  451. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -3
  452. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +96 -0
  453. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/effects.rb +65 -8
  454. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +270 -0
  455. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/render_locals.rb +402 -0
  456. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +370 -0
  457. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_units.rb +132 -0
  458. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +264 -3
  459. data/plugins/rigor-actionpack/sig/action_controller.rbs +71 -0
  460. data/plugins/rigor-actionpack/sig/action_view.rbs +43 -0
  461. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +220 -0
  462. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_index.rb +56 -0
  463. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +263 -0
  464. data/plugins/rigor-active-model-serializers/lib/rigor-active-model-serializers.rb +7 -0
  465. data/plugins/rigor-active-model-serializers/sig/active_model_serializers.rbs +45 -0
  466. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +0 -4
  467. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +2 -1
  468. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +0 -1
  469. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +0 -2
  470. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +1 -1
  471. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +10 -5
  472. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +35 -3
  473. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +72 -12
  474. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +383 -7
  475. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +14 -6
  476. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +1 -2
  477. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +1 -2
  478. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +322 -41
  479. data/plugins/rigor-activerecord/sig/active_record/framework.rbs +123 -0
  480. data/plugins/rigor-activerecord/sig/active_record/relation.rbs +206 -31
  481. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +26 -14
  482. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +9 -2
  483. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +9 -2
  484. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +290 -19
  485. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +1 -0
  486. data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +3 -0
  487. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +9 -7
  488. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +7 -3
  489. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +1 -0
  490. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +8 -6
  491. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +11 -7
  492. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -11
  493. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +9 -4
  494. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +2 -0
  495. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +0 -5
  496. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +0 -1
  497. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +0 -1
  498. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +8 -1
  499. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/types.rb +1 -1
  500. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +36 -4
  501. data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +1 -0
  502. data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +141 -0
  503. data/plugins/rigor-grape/lib/rigor-grape.rb +6 -0
  504. data/plugins/rigor-grape/sig/grape.rbs +263 -0
  505. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +8 -6
  506. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +78 -5
  507. data/plugins/rigor-graphql/sig/graphql.rbs +485 -0
  508. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +1 -0
  509. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -1
  510. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +2 -3
  511. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +1 -0
  512. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +0 -5
  513. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -2
  514. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +0 -1
  515. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +0 -1
  516. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +12 -7
  517. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +0 -12
  518. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +1 -3
  519. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +0 -1
  520. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +36 -37
  521. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/acronyms.rb +5 -5
  522. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +0 -10
  523. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +5 -3
  524. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +3 -2
  525. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +2 -2
  526. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +2 -2
  527. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +9 -10
  528. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +2 -3
  529. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +10 -17
  530. data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +1 -0
  531. data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +13 -1
  532. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline/same_line_annotations.rb +149 -0
  533. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +324 -18
  534. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +0 -3
  535. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +0 -3
  536. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -5
  537. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +2 -3
  538. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +1 -0
  539. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +0 -3
  540. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +1 -0
  541. data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +1 -0
  542. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +1 -9
  543. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +1 -0
  544. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +0 -4
  545. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/effects.rb +1 -1
  546. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +2 -1
  547. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +0 -1
  548. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +0 -2
  549. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +10 -5
  550. data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -0
  551. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +2 -5
  552. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +1 -4
  553. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +3 -9
  554. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +4 -4
  555. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +1 -2
  556. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +3 -5
  557. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +7 -5
  558. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +51 -6
  559. data/plugins/rigor-sorbet/sig/sorbet.rbs +277 -0
  560. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +1 -0
  561. data/sig/rigor/analysis/baseline.rbs +69 -7
  562. data/sig/rigor/analysis/fact_store.rbs +1 -1
  563. data/sig/rigor/analysis/project_scan.rbs +74 -0
  564. data/sig/rigor/analysis/reachability/scan_cache.rbs +12 -0
  565. data/sig/rigor/cache.rbs +2 -3
  566. data/sig/rigor/effects/config_envelopes.rbs +100 -0
  567. data/sig/rigor/effects/effect_table.rbs +60 -0
  568. data/sig/rigor/effects/envelope.rbs +97 -0
  569. data/sig/rigor/effects/envelope_index.rbs +33 -0
  570. data/sig/rigor/effects/file_collection.rbs +81 -0
  571. data/sig/rigor/effects/label.rbs +26 -0
  572. data/sig/rigor/effects/label_set.rbs +46 -0
  573. data/sig/rigor/effects/method_key.rbs +24 -0
  574. data/sig/rigor/effects/origin.rbs +50 -0
  575. data/sig/rigor/effects/plugin_facts.rbs +141 -0
  576. data/sig/rigor/effects/registry.rbs +68 -0
  577. data/sig/rigor/effects/summary.rbs +50 -0
  578. data/sig/rigor/effects/taint_cause.rbs +12 -0
  579. data/sig/rigor/environment.rbs +17 -10
  580. data/sig/rigor/inference/optimistic_origin.rbs +10 -0
  581. data/sig/rigor/inference.rbs +9 -14
  582. data/sig/rigor/plugin/additional_initializer.rbs +36 -0
  583. data/sig/rigor/plugin/base.rbs +30 -7
  584. data/sig/rigor/plugin/effect_ancestry.rbs +27 -0
  585. data/sig/rigor/plugin/effect_attribution.rbs +66 -0
  586. data/sig/rigor/plugin/effect_edge.rbs +33 -0
  587. data/sig/rigor/plugin/effect_entry_points.rbs +25 -0
  588. data/sig/rigor/plugin/io_boundary.rbs +4 -0
  589. data/sig/rigor/plugin/loader.rbs +3 -3
  590. data/sig/rigor/plugin/manifest.rbs +51 -11
  591. data/sig/rigor/plugin/protocol_contract.rbs +68 -0
  592. data/sig/rigor/plugin/registry.rbs +62 -1
  593. data/sig/rigor/plugin.rbs +1 -1
  594. data/sig/rigor/rbs_extended.rbs +1 -1
  595. data/sig/rigor/reflection.rbs +9 -6
  596. data/sig/rigor/scope.rbs +148 -12
  597. data/sig/rigor/sig_gen/skip_reason_catalog.rbs +21 -0
  598. data/sig/rigor/source.rbs +4 -4
  599. data/sig/rigor/testing.rbs +10 -4
  600. data/sig/rigor/type.rbs +33 -1
  601. data/sig/rigor.rbs +39 -20
  602. data/skills/rigor-ask/SKILL.md +16 -11
  603. data/skills/rigor-baseline-reduce/SKILL.md +4 -6
  604. data/skills/rigor-ci-setup/SKILL.md +17 -21
  605. data/skills/rigor-doctor/SKILL.md +24 -22
  606. data/skills/rigor-doctor/references/01-checks.md +97 -33
  607. data/skills/rigor-editor-setup/SKILL.md +6 -4
  608. data/skills/rigor-mcp-setup/SKILL.md +5 -4
  609. data/skills/rigor-monkeypatch-resolve/SKILL.md +5 -3
  610. data/skills/rigor-next-steps/SKILL.md +4 -2
  611. data/skills/rigor-plugin-author/SKILL.md +19 -23
  612. data/skills/rigor-plugin-author/references/01-plan-and-scaffold.md +9 -8
  613. data/skills/rigor-plugin-author/references/02-walker-and-types.md +25 -27
  614. data/skills/rigor-plugin-author/references/03-test-and-ship.md +6 -8
  615. data/skills/rigor-plugin-review/SKILL.md +6 -4
  616. data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +2 -1
  617. data/skills/rigor-plugin-tune/SKILL.md +4 -2
  618. data/skills/rigor-project-init/SKILL.md +15 -10
  619. data/skills/rigor-project-init/references/01-detect.md +13 -9
  620. data/skills/rigor-project-init/references/02-configure.md +43 -14
  621. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +14 -14
  622. data/skills/rigor-project-init/references/04-sig-uplift.md +27 -14
  623. data/skills/rigor-project-init/references/06-agent-contract.md +67 -0
  624. data/skills/rigor-protection-uplift/SKILL.md +4 -6
  625. data/skills/rigor-rbs-setup/SKILL.md +4 -2
  626. data/skills/rigor-type-oracle/SKILL.md +226 -0
  627. data/skills/rigor-type-oracle/references/01-oracle-commands.md +265 -0
  628. data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +52 -0
  629. data/skills/rigor-type-oracle/references/03-gap-protocol.md +125 -0
  630. data/skills/rigor-unused-adjudicate/SKILL.md +12 -7
  631. data/skills/rigor-upgrade/SKILL.md +13 -8
  632. metadata +133 -3
  633. data/lib/rigor/cli/probe_environment.rb +0 -85
  634. data/sig/rigor/inference/builtins/numeric_catalog.rbs +0 -3
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rigor
4
+ module Plugin
5
+ class ActiveModelSerializers < Rigor::Plugin::Base
6
+ # The serializer classes {SerializerDiscoverer} found under the configured search paths, keyed by
7
+ # the class's fully-qualified name as the source spells it (`"REST::AccountSerializer"`).
8
+ #
9
+ # Each entry carries the declared superclass (so the discoverer can close the ancestry chain) and
10
+ # the three name sets that let a candidate model be CHECKED rather than guessed:
11
+ #
12
+ # - `declared_names` — the names the serializer will read off the resource at render time: the
13
+ # symbols of `attributes` / `attribute` / `has_many` / `has_one` / `belongs_to`, minus the ones
14
+ # the serializer defines itself (AMS calls the serializer's own method when it has one, and only
15
+ # falls through to `object.<name>` when it does not).
16
+ # - `object_reads` — every `object.<name>` the body writes, minus the methods every Object has.
17
+ # - `own_method_names` — the serializer's own instance `def`s, which is both what subtracts from
18
+ # `declared_names` and how an explicit `def object` is detected.
19
+ class SerializerIndex
20
+ Entry = Data.define(:class_name, :superclass_name, :file_path, :declared_names, :object_reads,
21
+ :own_method_names) do
22
+ # The declarations AMS will render by calling the name on the resource — unless an ancestor of
23
+ # this serializer defines it, which only the engine's ancestor walk can say, so that half is
24
+ # left to the caller.
25
+ def unhandled_declarations = declared_names - own_method_names
26
+
27
+ # Reads of the resource itself. Unlike a declaration, one of these is a read of the resource
28
+ # whatever the serializer or its ancestors define.
29
+ def resource_reads = object_reads
30
+
31
+ def defines_object? = own_method_names.include?("object")
32
+ end
33
+
34
+ attr_reader :entries
35
+
36
+ def initialize(entries)
37
+ @entries = entries.freeze
38
+ @by_name = entries.to_h { |entry| [entry.class_name, entry] }.freeze
39
+ freeze
40
+ end
41
+
42
+ def find(class_name) = @by_name[derooted(class_name)]
43
+ def known?(class_name) = @by_name.key?(derooted(class_name))
44
+ def empty? = @entries.empty?
45
+ def size = @entries.size
46
+ def names = @by_name.keys
47
+
48
+ private
49
+
50
+ # A query may arrive rooted (`::REST::AccountSerializer`) while entries are keyed by the
51
+ # de-rooted spelling, the same normalisation `rigor-activerecord`'s ModelIndex settled on in #583.
52
+ def derooted(class_name) = class_name.to_s.delete_prefix("::")
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,263 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rigor/plugin"
4
+
5
+ require_relative "active_model_serializers/serializer_index"
6
+ require_relative "active_model_serializers/serializer_discoverer"
7
+
8
+ module Rigor
9
+ module Plugin
10
+ # rigor-active-model-serializers — types the implicit-self `object` reader inside a serializer as the
11
+ # model the serializer serializes.
12
+ #
13
+ # It emits no diagnostic. What it contributes is one return type and the gem's own framework
14
+ # constants, and both exist for the same measurement: the 2026-09-01 corpus opacity sweep (#534
15
+ # item 6) found `object` to be Mastodon's single largest unresolved implicit-self send — 751 sites in
16
+ # `app/serializers` alone — with no plugin owning ActiveModelSerializers at all. Every
17
+ # `object.account.username` chain below one of those sites was dispatching on `Dynamic[top]`.
18
+ #
19
+ # plugins:
20
+ # - gem: rigor-active-model-serializers
21
+ # config:
22
+ # serializer_search_paths: ["app/serializers", "app/lib"] # default; optional
23
+ # serializer_base_classes: ["ActiveModel::Serializer"] # default; optional
24
+ # model_overrides: {} # default; optional
25
+ #
26
+ # ## What `object` types to, and when it declines
27
+ #
28
+ # AMS's `object` is `ActiveModel::Serializer#object`, the resource the serializer was constructed
29
+ # with. Nothing in a serializer's source STATES that resource's class — AMS binds it at `new` time —
30
+ # so the type is only ever derived, and two independent things have to agree before it is:
31
+ #
32
+ # 1. The `<Model>Serializer` naming convention resolves to exactly one model `rigor-activerecord`
33
+ # discovered (`REST::AccountSerializer` → `Account`), and
34
+ # 2. that model ANSWERS the serializer: every name the serializer will read off its resource — the
35
+ # `attributes` / `has_many` / `has_one` declarations it does not define itself, plus every
36
+ # `object.<name>` in its body — is a column, association, enum, alias or scope of that model, or a
37
+ # method the project defines on it.
38
+ #
39
+ # A `model_overrides` entry short-circuits both: the project asserted the answer. Anything else
40
+ # DECLINES, and `object` keeps whatever the engine would have given it (`Dynamic`).
41
+ #
42
+ # ## Why the name alone is not enough
43
+ #
44
+ # The name is a guess and the model index only proves the guessed class EXISTS, never that this
45
+ # serializer serializes it. Mastodon has three serializers where the guess is wrong and the class is
46
+ # real: `REST::ConversationSerializer` serializes an `AccountConversation` (its `unread`,
47
+ # `participant_accounts` and `last_status` are all absent from `Conversation`), and both
48
+ # `REST::InstanceSerializer` and `REST::V1::InstanceSerializer` serialize an `InstancePresenter`
49
+ # (`object.contact`, `object.thumbnail`). A corpus diff cannot see the damage, because an Active
50
+ # Record model's surface is open and a wrong-but-real model absorbs every read in silence — so the
51
+ # check has to be a positive one, made before the answer is contributed.
52
+ #
53
+ # Requiring EVERY name to be answered, rather than most of them, is the same choice: a serializer
54
+ # whose resource is a decorator around the model shares most of the model's surface, and it is
55
+ # exactly the one or two extra names that say so.
56
+ #
57
+ # ## Scope
58
+ #
59
+ # - **SimpleForm inputs are NOT handled.** The sweep's 875-site `object` count mixes AMS serializers
60
+ # with `SimpleForm::Inputs::Base#object`, a different gem with a different resource story
61
+ # (`object` there is the form's record, named by the `simple_form_for` call site, not by the input
62
+ # class). That belongs in a `rigor-simple-form` plugin and is deliberately left out.
63
+ # - **`serializer:` / `each_serializer:` options are not read.** `has_many :emojis, serializer:
64
+ # REST::CustomEmojiSerializer` states which serializer renders an association, never which model a
65
+ # serializer serializes, so it cannot ground `object`.
66
+ # - **A serializer with no declarations and no `object` reads gets no answer.** There is nothing to
67
+ # check the name against, and the name alone is what this refuses to trust.
68
+ class ActiveModelSerializers < Rigor::Plugin::Base
69
+ manifest(
70
+ id: "active-model-serializers",
71
+ target_gems: ["active_model_serializers"],
72
+ version: "0.1.0",
73
+ description: "Types the implicit-self `object` reader inside ActiveModel::Serializer subclasses " \
74
+ "as the serializer's model, and declares the gem's framework constants.",
75
+ config_schema: {
76
+ # `app/lib` is in the default set because a project base serializer routinely lives outside
77
+ # `app/serializers` — Mastodon's `ActivityPub::Serializer`, the parent of 60 of its 153
78
+ # serializers, is at `app/lib/activitypub/serializer.rb`. Without it the ancestry closure stops
79
+ # at the base class and two fifths of the project's serializers are simply not serializers as
80
+ # far as this plugin is concerned. A directory the project does not have costs one probe.
81
+ "serializer_search_paths" => { kind: :array, default: ["app/serializers", "app/lib"] },
82
+ "serializer_base_classes" => { kind: :array, default: ["ActiveModel::Serializer"] },
83
+ # Serializer class name => model class name, for the resources the derivation cannot reach: a
84
+ # serializer whose resource is a presenter or a decorator, or one named for a JSON shape rather
85
+ # than a model. An override is the project's own assertion and is NOT re-checked — a project
86
+ # that names a class Rigor has no RBS for gets a lenient nominal.
87
+ "model_overrides" => { kind: :hash, default: {} }
88
+ },
89
+ # Optional on purpose: without `rigor-activerecord` there is no model set to check a name
90
+ # against, and every non-overridden serializer declines. That is the designed degradation — the
91
+ # plugin contributes less, never something wrong.
92
+ consumes: [{ plugin_id: "activerecord", name: :model_index, optional: true }],
93
+ # ADR-25 (#534 item 7, same admission rule as `rigor-activerecord`'s `sig/active_record/
94
+ # framework.rbs`) — the gem's own constants, declared so they resolve and asserting nothing else.
95
+ signature_paths: ["sig"],
96
+ # ADR-26 — every class and module the bundled signature names is open, so that declaring it buys
97
+ # constant resolution and asserts nothing about a member. `ActiveModel::Serializer` is the row
98
+ # that matters most (a project's serializers inherit from it, and its whole instance surface —
99
+ # `object`, `scope`, `read_attribute_for_serialization`, the `_attributes` class state — is
100
+ # unenumerated), and `ActiveModelSerializers` / `::Adapter` are the ones the canonical
101
+ # initializer dispatches on: `ActiveModelSerializers.config.adapter = :json_api` and
102
+ # `ActiveModelSerializers::Adapter.register(...)` are what an AMS `config/initializers` file says.
103
+ open_receivers: [
104
+ "ActiveModel::Serializer",
105
+ "ActiveModel::Serializer::CollectionSerializer",
106
+ "ActiveModelSerializers",
107
+ "ActiveModelSerializers::Adapter",
108
+ "ActiveModelSerializers::Model",
109
+ "ActiveModelSerializers::SerializableResource"
110
+ ]
111
+ )
112
+
113
+ producer :serializer_index, watch: -> { [[@serializer_search_paths, "**/*.rb"]] } do |_params|
114
+ SerializerDiscoverer.new(
115
+ io_boundary: io_boundary,
116
+ search_paths: @serializer_search_paths,
117
+ base_classes: @serializer_base_classes
118
+ ).discover
119
+ end
120
+
121
+ def init(_services)
122
+ @serializer_search_paths = Array(config.fetch("serializer_search_paths")).map(&:to_s)
123
+ @serializer_base_classes = Array(config.fetch("serializer_base_classes")).map(&:to_s)
124
+ @model_overrides = config.fetch("model_overrides").to_h { |k, v| [k.to_s.delete_prefix("::"), v.to_s] }
125
+ @derivations = {}
126
+ end
127
+
128
+ # The `methods:` gate keeps this off every dispatch whose name is not `object`; the receiver and
129
+ # argument checks keep it off `foo.object` and `object(x)`, neither of which is the AMS reader.
130
+ dynamic_return methods: [:object] do |call_node, scope|
131
+ next nil unless call_node.is_a?(Prism::CallNode)
132
+ next nil unless call_node.receiver.nil?
133
+ next nil unless call_node.arguments.nil?
134
+
135
+ entry = serializer_entry(scope)
136
+ next nil if entry.nil?
137
+ # An explicit `def object`, here or on an ancestor, is the definition that runs. Answering over
138
+ # it would replace a type the engine derived from real source with a guess, and silence whatever
139
+ # that source proves.
140
+ next nil if entry.defines_object? || project_defines_object?(entry.class_name, scope)
141
+
142
+ model_name = model_class_name_for(entry, scope)
143
+ next nil if model_name.nil?
144
+
145
+ Rigor::Type::Combinator.nominal_of(model_name)
146
+ end
147
+
148
+ private
149
+
150
+ # The discovered serializer whose body `self` is in, or nil. Membership of the index is the only
151
+ # gate: a `*Serializer` name is not evidence of anything, since `object` is an ordinary method name
152
+ # that a `Json::ConversationSerializer` or an `Oj::AccountSerializer` may define for itself.
153
+ def serializer_entry(scope)
154
+ self_type = scope&.self_type
155
+ return nil unless self_type.respond_to?(:class_name)
156
+
157
+ name = self_type.class_name
158
+ return nil if name.nil? || name.empty?
159
+
160
+ producer_value(:serializer_index)&.find(name)
161
+ end
162
+
163
+ def project_defines_object?(serializer_name, scope)
164
+ return false unless scope.respond_to?(:user_def_through_ancestors)
165
+
166
+ !scope.user_def_through_ancestors(serializer_name, :object).first.nil?
167
+ end
168
+
169
+ # The model a serializer serializes, or nil where nothing corroborates it. Memoised per serializer
170
+ # class INCLUDING the nil answer: the ancestor walks behind `project_defines?` are run once per
171
+ # name per serializer, and `object` is read hundreds of times across a real `app/serializers`.
172
+ def model_class_name_for(entry, scope)
173
+ return @derivations[entry.class_name] if @derivations.key?(entry.class_name)
174
+
175
+ @derivations[entry.class_name] = derive_model_class_name(entry, scope)
176
+ end
177
+
178
+ def derive_model_class_name(entry, scope)
179
+ override = @model_overrides[entry.class_name]
180
+ return override unless override.nil? || override.empty?
181
+
182
+ # The published `:model_index` fact is a Hash keyed by the model's DE-ROOTED constant path
183
+ # (#583). `rigor-activerecord` withholds it entirely in reduced mode (no `db/schema.rb` /
184
+ # `db/structure.sql`), so a schema-less project declines here rather than typing `object` off a
185
+ # name alone.
186
+ index = read_fact(plugin_id: "activerecord", name: :model_index)
187
+ return nil if index.nil? || index.empty?
188
+
189
+ resolved = convention_candidates(entry.class_name).select { |candidate| index.key?(candidate) }
190
+ # Two readings that both name a real model (`Admin::AccountSerializer` where `Admin::Account` and
191
+ # `Account` both exist) is an ambiguity, not a first-hit: nothing here ranks one above the other.
192
+ return nil unless resolved.size == 1
193
+
194
+ model = resolved.first
195
+ answers?(entry, index.fetch(model), model, scope) ? model : nil
196
+ end
197
+
198
+ # Whether the model ANSWERS the serializer — see the class comment for why this is required and why
199
+ # it is required of every name rather than most of them. A serializer that declares nothing and
200
+ # reads nothing states nothing to check, and is declined for that reason rather than admitted for
201
+ # it.
202
+ def answers?(entry, row, model_name, scope)
203
+ declarations = entry.unhandled_declarations
204
+ reads = entry.resource_reads
205
+ return false if declarations.empty? && reads.empty?
206
+
207
+ members = model_members(row)
208
+ # A declaration the serializer itself renders is not a read of the resource at all. The entry
209
+ # subtracts the serializer's OWN methods; a base serializer's `def formatted` is only visible to
210
+ # the engine's ancestor walk, and a declaration it handles says nothing about the model.
211
+ declarations.all? do |name|
212
+ project_defines?(entry.class_name, name, scope) ||
213
+ members.include?(name) || project_defines?(model_name, name, scope)
214
+ end && reads.all? { |name| members.include?(name) || project_defines?(model_name, name, scope) }
215
+ end
216
+
217
+ # Every name the model answers that its `:model_index` row states. The `?` forms are Active
218
+ # Record's own per-column predicates, which a serializer reads as readily as the column.
219
+ #
220
+ # `macro_methods` (#1049) is the row's name-only set: `delegate`'s methods, the Paperclip / Active
221
+ # Storage attachment readers, and an `enum`'s per-value predicates, from the model body and from every
222
+ # concern the model includes. It is what lets `REST::AccountSerializer`'s `followers_count` / `user` /
223
+ # `avatar` reads resolve; an older producer that does not publish the key reads as the empty set here,
224
+ # which declines exactly as before rather than erroring.
225
+ def model_members(row)
226
+ columns = Array(row[:columns]).map(&:to_s)
227
+ associations = Array(row[:associations]).map { |a| a[:name].to_s }
228
+ enums = row[:enums].is_a?(Hash) ? row[:enums].keys.map(&:to_s) : []
229
+ aliases = row[:aliases].is_a?(Hash) ? row[:aliases].keys.map(&:to_s) : []
230
+ scopes = Array(row[:scopes]).map(&:to_s)
231
+ macros = Array(row[:macro_methods]).map(&:to_s)
232
+ (columns + columns.map { |c| "#{c}?" } + associations + enums + aliases + scopes + macros).to_set
233
+ end
234
+
235
+ # A method the project writes in Ruby on `class_name` or an ancestor of it. On the model side that
236
+ # is `Account#local?` or a concern's reader — the model index cannot see these, and without them
237
+ # the check would decline nearly every real serializer. On the serializer side it is a base
238
+ # serializer's own rendering method, which is not a read of the resource at all.
239
+ def project_defines?(class_name, method_name, scope)
240
+ return false unless scope.respond_to?(:user_def_through_ancestors)
241
+
242
+ !scope.user_def_through_ancestors(class_name, method_name.to_sym).first.nil?
243
+ end
244
+
245
+ # `REST::AccountSerializer` offers two readings — the namespaced `REST::Account` and the
246
+ # demodulized `Account` — and Rails apps use both (`Admin::AccountSerializer` for `Admin::Account`
247
+ # is as real as Mastodon's `REST::AccountSerializer` for `Account`). Both are offered to the model
248
+ # index, and exactly one of them has to land.
249
+ def convention_candidates(serializer_name)
250
+ return [] unless serializer_name.end_with?("Serializer")
251
+ # A class named exactly `Serializer` is a namespace's base serializer, never a model's.
252
+ return [] if serializer_name.split("::").last == "Serializer"
253
+
254
+ stripped = serializer_name.delete_suffix("Serializer").delete_suffix("::")
255
+ return [] if stripped.empty?
256
+
257
+ [stripped, stripped.split("::").last].uniq.reject(&:empty?)
258
+ end
259
+ end
260
+
261
+ Rigor::Plugin.register(ActiveModelSerializers)
262
+ end
263
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Gem entry point. Required by Rigor's plugin loader when `.rigor.yml` lists
4
+ # `rigor-active-model-serializers` under `plugins:`. The loader expects this `require` to side-effect a
5
+ # call to `Rigor::Plugin.register`, which the body of
6
+ # `lib/rigor/plugin/active_model_serializers.rb` performs at load time.
7
+ require_relative "rigor/plugin/active_model_serializers"
@@ -0,0 +1,45 @@
1
+ # The ActiveModelSerializers gem's own constants — issue #534 item 6.
2
+ #
3
+ # The 2026-09-01 corpus opacity sweep counted `ActiveModel::Serializer` 101 times on Mastodon,
4
+ # `ActiveModelSerializers::Model` 29 and `ActiveModelSerializers::SerializableResource` 25, every
5
+ # one of them resolving to `untyped` because nothing in the bundle named the constant. This file
6
+ # names them, and nothing more.
7
+ #
8
+ # ## The admission rule
9
+ #
10
+ # The same rule `plugins/rigor-activerecord/sig/active_record/framework.rbs` states: declaring a
11
+ # constant makes it RBS-KNOWN, and a known class with an incomplete signature turns every member the
12
+ # signature omits into a false `call.undefined-method`. Every class here is therefore ALSO listed in
13
+ # the manifest's `open_receivers:` (ADR-26), which is what keeps the members this file does not
14
+ # enumerate lenient.
15
+ #
16
+ # That pairing is load-bearing rather than defensive here, because — unlike the Active Record
17
+ # exception hierarchy, which is rescued and not dispatched on — `ActiveModel::Serializer` and
18
+ # `ActiveModelSerializers::Model` are exactly the "class a project INHERITS from" shape the Active
19
+ # Record file declines to declare. `class REST::AccountSerializer < ActiveModel::Serializer` is what
20
+ # an AMS application looks like, and `object`, `scope`, `read_attribute_for_serialization`,
21
+ # `attributes`, `has_many` and the rest of the AMS surface are not enumerated below. The Mastodon
22
+ # before/after diff recorded in `docs/notes/20260917-ams-object-recognizer.md` is the evidence that
23
+ # the pairing holds on a real application.
24
+ #
25
+ # `ActiveModel` is declared as a module by `rigor-activerecord` as well; RBS reopens a module across
26
+ # files, so the two declarations compose rather than collide, and neither plugin depends on the
27
+ # other being enabled.
28
+
29
+ module ActiveModel
30
+ class Serializer
31
+ class CollectionSerializer
32
+ end
33
+ end
34
+ end
35
+
36
+ module ActiveModelSerializers
37
+ class Model
38
+ end
39
+
40
+ class SerializableResource
41
+ end
42
+
43
+ module Adapter
44
+ end
45
+ 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
 
@@ -49,6 +49,7 @@ module Rigor
49
49
  "shoryuken" => ["io.net"], "backburner" => ["io.net"], "sucker_punch" => [],
50
50
  "async" => [], "inline" => [], "test" => []
51
51
  }.freeze
52
+ Ractor.make_shareable(TRANSPORTS)
52
53
 
53
54
  # The meaning half, which is adapter-independent and is what a policy actually names.
54
55
  MEANING = ["rails.activejob.enqueue", "job.enqueue"].freeze
@@ -61,7 +62,7 @@ module Rigor
61
62
 
62
63
  module_function
63
64
 
64
- # @param adapter [String, nil] the adapter the project declares, or nil when it declares none
65
+ # @param adapter — the adapter the project declares, or nil when it declares none
65
66
  # (or more than one, across environments)
66
67
  def attributions(adapter)
67
68
  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
  #
@@ -128,12 +129,12 @@ module Rigor
128
129
  services.fact_store.publish(plugin_id: manifest.id, name: :reachability_roots, value: roots)
129
130
  end
130
131
 
131
- # File-level only: the load-error emission. Per-call arity validation runs over the engine-owned
132
+ # File-level only: the load-error disclosure. Per-call arity validation runs over the engine-owned
132
133
  # walk via the node_rule below (ADR-37). The job index is lazily loaded + memoised by
133
134
  # `producer_value`, shared by both surfaces.
134
135
  def diagnostics_for_file(path:, scope:, root:) # rubocop:disable Lint/UnusedMethodArgument
135
136
  index = producer_value(:job_index)
136
- return [load_error_diagnostic(path)] if index.nil? && producer_error(:job_index)
137
+ disclose_load_error if index.nil? && producer_error(:job_index)
137
138
 
138
139
  []
139
140
  end
@@ -147,10 +148,14 @@ module Rigor
147
148
 
148
149
  private
149
150
 
150
- def load_error_diagnostic(path)
151
+ # Issue #1056 — "the job index did not load" is a fact about the run's INPUTS, not about the file being
152
+ # analysed, so it is registered rather than returned: see {Plugin::Base#disclose_once} for the
153
+ # channel, the `(plugin id, key)` de-duplication and why the engine positions it at `.rigor.yml:1:1`.
154
+ # Returned from the per-file hook it carried no once-guard at all and repeated on every file.
155
+ def disclose_load_error
151
156
  error = producer_error(:job_index)
152
- Rigor::Analysis::Diagnostic.new(
153
- path: path, line: 1, column: 1,
157
+ disclose_once(
158
+ :job_index_load_failed,
154
159
  message: "rigor-activejob: failed to discover jobs: #{error.class}: #{error.message}",
155
160
  severity: :warning,
156
161
  rule: "load-error"
@@ -13,13 +13,15 @@ module Rigor
13
13
  # | Method | Recognised arg shape | Validation |
14
14
  # | --- | --- | --- |
15
15
  # | `Model.find(id)` | any positional | arity check (1+ args) |
16
+ # | `Model.find { … }` | a block, no args | none (`Enumerable#find`) |
16
17
  # | `Model.find_by(col: v, ...)` | keyword args | each key must be a column |
17
18
  # | `Model.where(col: v, ...)` | keyword args | each key must be a column |
18
19
  # | `Model.where(string)` | String literal | parser-side; not validated |
19
20
  #
20
21
  # Successful matches surface as `:info` diagnostics naming the resolved table; unknown columns
21
22
  # surface as `:error`. Calls whose receiver is not a model in the index, and calls with
22
- # non-keyword arguments to `where` / `find_by`, stay silent.
23
+ # non-keyword arguments to `where` / `find_by`, stay silent. A model's own `self.find` owns its arity,
24
+ # so the arity check and the notes for the forms the return-type tier leaves to it stay silent too.
23
25
  class Analyzer
24
26
  # Methods that take a column → value Hash and need each key validated against the receiver's
25
27
  # column set. The bang variants (`find_by!` raises instead of returning nil; `find_or_create_by!`
@@ -35,9 +37,13 @@ module Rigor
35
37
 
36
38
  attr_reader :diagnostics
37
39
 
38
- def initialize(path:, model_index:)
40
+ # `scope` is the file's entry scope, which carries the project's discovered methods; it answers
41
+ # whether a model defines its own `self.find`. An editor run seeds none, so there the check misses
42
+ # even a same-file `self.find` the typer sees (#1329).
43
+ def initialize(path:, model_index:, scope:)
39
44
  @path = path
40
45
  @model_index = model_index
46
+ @scope = scope
41
47
  @diagnostics = []
42
48
  end
43
49
 
@@ -69,16 +75,42 @@ module Rigor
69
75
  end
70
76
  end
71
77
 
78
+ # A block turns `find` into `Enumerable#find` over `all` (see `Activerecord#block_find_return_type`),
79
+ # which takes no id, so the zero-argument check applies only to the block-less id lookup.
80
+ #
81
+ # The arity check and the notes describe Rails' `find`. A model's own `self.find` has its own arity,
82
+ # and the return-type tier (`Activerecord#finder_return_type`) leaves the multi-id and block forms to
83
+ # it, so a note there would claim a type the call does not get. One id still types as the model.
72
84
  def validate_find(node, entry)
85
+ return validate_block_find(node, entry) if node.block
86
+
73
87
  arity = call_argument_count(node)
74
88
  if arity.zero?
89
+ return if own_find?(entry)
90
+
75
91
  push_error(node, "wrong-arity",
76
92
  "`#{entry.class_name}.find` expects at least 1 argument, got 0")
77
93
  return
78
94
  end
95
+ return if arity >= 2 && own_find?(entry)
79
96
 
97
+ returned = arity >= 2 ? "Array[#{entry.class_name}]" : entry.class_name
80
98
  push_info(node, "model-call",
81
- "`#{entry.class_name}.find` returns #{entry.class_name} (table: `#{entry.table_name}`)")
99
+ "`#{entry.class_name}.find` returns #{returned} (table: `#{entry.table_name}`)")
100
+ end
101
+
102
+ def validate_block_find(node, entry)
103
+ return unless call_argument_count(node).zero?
104
+ return if own_find?(entry)
105
+
106
+ push_info(node, "model-call",
107
+ "`#{entry.class_name}.find` returns #{entry.class_name} | nil (table: `#{entry.table_name}`)")
108
+ end
109
+
110
+ # The predicate `Activerecord#finder_return_type` declines on, so the note and the type agree in a
111
+ # CLI run. In an editor run this scope lacks the file's own methods (#1329).
112
+ def own_find?(entry)
113
+ @scope.discovered_method_through_ancestors?(entry.class_name, :find, :singleton)
82
114
  end
83
115
 
84
116
  def validate_column_hash_call(node, entry)