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
@@ -80,14 +80,16 @@ partition `declared` — they sum to 1497. **`reachable only from tests`
80
80
  is a subset of `reachable`**, not a fifth bucket, so do not add it in.
81
81
 
82
82
  Read `roots` first. A root is a declaration something outside your code
83
- reaches — an entry point, a route, a framework convention.
84
- Reachability is computed *from* roots, so a thin root set inflates
85
- everything below it. The pathological case is a Rails application with
86
- `0 from plugins`: nothing in Ruby source names a controller, so every
87
- controller you own appears dead. There is no published healthy ratio
88
- to compare against; what you are checking is whether the number is
89
- plausible for your framework, and `0 from plugins` on a framework app
90
- never is.
83
+ reaches — an entry point, a route, a framework convention. The
84
+ `(… from plugins, …)` parenthetical is omitted entirely when no plugin
85
+ contributed a root, so a bare `roots: N` with no such note is itself
86
+ the signal. Reachability is computed *from* roots, so a thin root set
87
+ inflates everything below it. The pathological case is a Rails
88
+ application with no `from plugins` note at all: nothing in Ruby source
89
+ names a controller, so every controller you own appears dead. There is
90
+ no published healthy ratio to compare against; what you are checking
91
+ is whether the number is plausible for your framework, and a missing
92
+ `from plugins` note on a framework app never is.
91
93
 
92
94
  ## Getting the number down
93
95
 
@@ -214,6 +214,16 @@ rigor effects --pure
214
214
  436 methods on Redmine, and they are your `%a{pure}` candidates — the on-ramp to
215
215
  the last section of this chapter.
216
216
 
217
+ You do not have to write them all out by hand. With the `effects:` block in
218
+ place, `rigor sig-gen` annotates the methods in this set whose signature it is
219
+ proposing anyway: `%a{pure}` above every method that is exhaustive, undischarged,
220
+ free of surviving `≤` labels, not already carrying a bound you wrote, and calling
221
+ nothing the analyzer had no description for; and, under `--effect-envelopes`, the labelled spelling for the methods that
222
+ do have a footprint. A method that is clean only because `effects.tolerated:`
223
+ says so is deliberately left bare — the annotation would travel to readers who
224
+ do not share your tolerated list. See
225
+ [handbook chapter 11](../handbook/11-sig-gen.md#emitting-effect-annotations).
226
+
217
227
  ### Asking it a question
218
228
 
219
229
  **By label.** The question this chapter opens with, in one command:
@@ -54,6 +54,9 @@ The full catalogue, with a one-line scope for every plugin, is
54
54
  `Result` / `Option` unwrap types and synthesises `Enum` variants.
55
55
  - [rigor-pundit](rigor-pundit.md) — policy-class existence and
56
56
  `authorize(record, :action)` predicate validation.
57
+ - [rigor-active-model-serializers](rigor-active-model-serializers.md) —
58
+ types the `object` reader inside a serializer as the serializer's
59
+ model (no diagnostics).
57
60
  - [rigor-sidekiq](rigor-sidekiq.md) — Sidekiq `Worker.perform_*`
58
61
  argument arity against the discovered `#perform`.
59
62
  - [rigor-actioncable](rigor-actioncable.md) — `broadcast_to` channel
@@ -62,6 +65,10 @@ The full catalogue, with a one-line scope for every plugin, is
62
65
  through Minitest / Test::Unit assertions and spec matchers.
63
66
  - [rigor-graphql](rigor-graphql.md) — GraphQL-Ruby type / enum / input
64
67
  / mutation table publication (cross-plugin facts, no diagnostics).
68
+ - [rigor-grape](rigor-grape.md) — types the `Grape::API` endpoint DSL
69
+ (`params`, `namespace`, verb macros, `desc`, `route_setting`) and
70
+ `Grape::Entity` `expose` declarations, including the `instance_eval`'d
71
+ block `self` bindings.
65
72
  - [rigor-rspec-rails](rigor-rspec-rails.md) — `have_http_status`
66
73
  argument validation (out-of-range codes, unknown status symbols).
67
74
  - [rigor-shoulda-matchers](rigor-shoulda-matchers.md) — shoulda matcher
@@ -36,7 +36,7 @@ ActionCable.server.broadcast("chat_room_42", body: "hi") # warning: no such s
36
36
  | `plugin.actioncable.broadcast-stream` | info | `ActionCable.server.broadcast("...", ...)` matched a registered `stream_from` literal |
37
37
  | `plugin.actioncable.unknown-channel` | error | the receiver ends in `Channel` but is not in the index (with a did-you-mean) |
38
38
  | `plugin.actioncable.unknown-stream` | warning | the literal stream name matched no `stream_from` registration (with a did-you-mean) |
39
- | `plugin.actioncable.load-error` | warning | channel discovery failed (parse/read error) — once per file |
39
+ | `plugin.actioncable.load-error` | warning | channel discovery failed (parse/read error) — once per run, on `.rigor.yml` |
40
40
 
41
41
  The `unknown-stream` check is **suppressed** when any discovered
42
42
  channel registers a dynamic stream (`stream_from interpolated_string`
@@ -100,6 +100,13 @@ default.
100
100
  - **The `#receive` contract is path-scoped, not class-scoped** (ADR-28):
101
101
  any `def receive(data)` defined anywhere under `channel_search_paths`
102
102
  is typed, even on a class that isn't an ActionCable channel.
103
+ - **The "failed to discover channels" warning is run-scoped.** It is
104
+ a fact about your configuration, not about any one source file, so
105
+ it is reported once per run on `.rigor.yml` rather than repeated on
106
+ every analysed file. It is `:warning`, so if you baselined it at its
107
+ old position that entry no longer matches and the row fails a
108
+ `--fail-on=warning` run — regenerate with `rigor baseline
109
+ regenerate`.
103
110
 
104
111
  ## Plugin internals
105
112
 
@@ -69,6 +69,13 @@ plugins:
69
69
  which the report already records. Rooting every class under
70
70
  `app/mailers` instead would mark a mailer nothing sends reachable
71
71
  forever on no evidence.
72
+ - **The "failed to discover mailers" warning is run-scoped.** It is
73
+ a fact about your configuration, not about any one source file, so
74
+ it is reported once per run on `.rigor.yml` rather than repeated on
75
+ every analysed file. It is `:warning`, so if you baselined it at its
76
+ old position that entry no longer matches and the row fails a
77
+ `--fail-on=warning` run — regenerate with `rigor baseline
78
+ regenerate`.
72
79
 
73
80
  ## Plugin internals
74
81
 
@@ -54,6 +54,7 @@ plugins:
54
54
  config:
55
55
  controller_search_paths: ["app/controllers"] # default
56
56
  view_search_paths: ["app/views"] # default
57
+ view_type_checks: false # default
57
58
  ```
58
59
 
59
60
  ## What it types
@@ -93,8 +94,268 @@ so *can* fold a condition — and `Mime::NullType`, the value `format`
93
94
  returns when there is no format, answers `nil?` with `true` while
94
95
  being a real object. Typing it needs a nil-aware answer.
95
96
 
97
+ ## Framework constants resolve
98
+
99
+ The plugin ships a small bundled signature naming the
100
+ `ActionController` namespace and the errors controllers rescue —
101
+ `ParameterMissing`, `UnpermittedParameters`, `RoutingError`,
102
+ `BadRequest`, `UnknownFormat`, `InvalidAuthenticityToken` and their
103
+ siblings. A `rescue ActionController::ParameterMissing => e` now types
104
+ `e` as that class instead of leaving it opaque.
105
+
106
+ The signature names **only** those. `ActionController::Base` and
107
+ `ActionController::API` are left undeclared on purpose: every
108
+ controller in the app inherits from one of them, and an incomplete
109
+ declaration of a superclass turns every member it omits — `render`,
110
+ `before_action`, `head` — into a report on working code. For the same
111
+ reason `ActionController::Parameters` and the `ActionDispatch` readers
112
+ above stay undeclared; their leniency is what makes the `params`
113
+ typing safe.
114
+
115
+ ## ERB templates as effect units
116
+
117
+ Every `app/views/**/*.erb` is compiled to Ruby and analysed as one
118
+ **effect unit**, keyed `view:users/show.html` — Rails' own logical
119
+ name with the handler dropped, so an ERB → Haml rewrite is not a
120
+ rename. Nothing has to be enabled: activating the plugin is what
121
+ claims the templates.
122
+
123
+ What that buys is the answer to *"what does this request actually
124
+ do"* past the `render` line. A partial that calls `@user.update`
125
+ reports `io.db.write` at `app/views/users/_card.html.erb`, a
126
+ `Time.now` in a layout fragment reports `nondet.time`, a leftover
127
+ `puts` reports `io.output.stdout` — all of it in `rigor effects` and
128
+ in the snapshot, so a template that starts writing shows up in a
129
+ diff.
130
+
131
+ ```
132
+ $ rigor effects
133
+ view:users/_card.html: [mutate.local, nondet.time] ≤ [io.db.write] …?
134
+ view:users/show.html: [mutate.local] ≤ [io.db.read] …?
135
+ ```
136
+
137
+ **The compiler** is Erubi when it resolves in your project's bundle
138
+ — Rails' own — and stdlib `ERB` otherwise. Erubi is never added to
139
+ your Gemfile and is not a Rigor dependency
140
+ ([ADR-90](../../adr/90-target-library-resolution-from-project-bundle.md)).
141
+ Either way the line map is measured rather than assumed, so a
142
+ finding names the template's own line; the column is always 1,
143
+ because a compiler rewrites the text of each line and a column of
144
+ the compiled Ruby would name nothing you wrote.
145
+
146
+ **What `self` is.** `ActionView::Base`, declared so the name
147
+ resolves and **open** so its method surface stays lenient. That is
148
+ what keeps `link_to`, `form_with`, `t`, `content_for`, your own
149
+ `ApplicationHelper` methods and every route helper from drawing a
150
+ finding per line.
151
+
152
+ **What is in scope.** `@ivars` are seeded from the controller
153
+ actions that render the template — the implicit render
154
+ (`UsersController#show` → `users/show`) and explicit
155
+ `render :edit` / `render "admin/form"`. Two restrictions keep a seed
156
+ from claiming a type the template will not find. Only assignments
157
+ whose right-hand side is one record and cannot be `nil` contribute:
158
+ `User.find(id)` and `Model.new` do; `find_by` never does, nor a call
159
+ written to return several records — `User.find(a, b)`,
160
+ `User.find([1, 2])`, `User.find(*ids)`, `User.create([…])` or a
161
+ `find` with a block. A single argument that only holds an Array at
162
+ runtime (`User.find(ids)`) still seeds the model. And only
163
+ assignments the action reaches on **every** path contribute — not
164
+ one inside an `if`, a `case`, a `rescue`, a loop or a block, and
165
+ nothing from a `before_action` carrying `if:` / `unless:`. Anything
166
+ else leaves the ivar unseeded, which reads as `Dynamic` and is
167
+ silent. A partial inherits the assigns of its own directory, because
168
+ an ivar is not a local. Locals come from the Rails 7.1 strict-locals
169
+ comment:
170
+
171
+ ```erb
172
+ <%# locals: (user:, admin: false) %>
173
+ ```
174
+
175
+ **`call.*` findings are off inside templates by default**, while the
176
+ synthesised receivers are still coarse — measured on redmine and
177
+ mastodon, the feature adds **zero** new findings to either
178
+ ([the measurement note](../../notes/20260917-erb-template-units.md)).
179
+ Set `view_type_checks: true` to opt in and have `@user.nmae` in
180
+ `show.html.erb` reported like any other call.
181
+
182
+ `flow.*` **reports in a template like anywhere else.** It was
183
+ suppressed alongside `call.*` for one measured reason — a partial's
184
+ optional-local preamble (`<% path = nil unless defined? path %>`)
185
+ really did assign nil, because nothing told the unit its render site
186
+ had bound `path`. Render-site `locals:` are traced now, and the same
187
+ two projects re-measured with the family reporting are byte-identical
188
+ to the runs with it suppressed ([the #1047 note](../../notes/20260917-render-locals-and-layouts.md)).
189
+
190
+ ### Locals come from the render site
191
+
192
+ A partial's parameters are bound by whoever renders it, and every
193
+ spelling of that is read — on both sides of the render:
194
+
195
+ ```erb
196
+ <%= render partial: "card", locals: { user: @user } %>
197
+ <%= render "card", user: @user %> <%# a view's trailing hash IS locals %>
198
+ <%= render partial: "card", collection: @users, as: :row %>
199
+ <%= render partial: "card", object: @user %>
200
+ ```
201
+
202
+ `collection:` binds `row`, `row_counter` and `row_iteration`;
203
+ `object:` and `as:` bind one local named after the partial or after
204
+ `as:`. A controller's `render partial: …, locals: …` is read the same
205
+ way — but a controller's *trailing hash* is options, so
206
+ `render :show, status: :ok` binds nothing.
207
+
208
+ A partial rendered from several sites gets the **union** of the
209
+ names. A name only some of them pass is still bound, typed
210
+ `Dynamic` — absence is what produced the false positives above.
211
+ A **type** is claimed only where every site agrees on one it could
212
+ settle from the call itself (`User.find(1)`, or an ivar the rendering
213
+ action's own seeds typed); anything else is `Dynamic`. A
214
+ strict-locals comment still wins where a template carries one.
215
+
216
+ A partial's **own** optional-local test counts too:
217
+ `<% size = nil unless defined?(size) %>`, `local_assigns[:size]` and
218
+ `local_assigns.key?(:size)` bind `size` even when no render site the
219
+ plugin can read passes it — a `locals: opts` hash, a `render` from a
220
+ helper, or a local with a default nobody passes. A name a helper under
221
+ `app/helpers` defines is left alone, so
222
+ `<% if defined?(current_user) %>` stays a helper call. A helper that a
223
+ gem or a concern defines is not seen by that scan, and its name is
224
+ bound as a `Dynamic` local instead.
225
+
226
+ ### The controller → template edge
227
+
228
+ A controller action's summary **includes what its template does**.
229
+ `render :show`, `render "show"`, `render "admin/form"`,
230
+ `render template:`, `render action:`, `render partial:` (with or
231
+ without `collection:`) and the implicit render of
232
+ `<controller>/<action>` all reach the template's own unit, and a
233
+ template reaches the partials *it* renders — so an `io.db.write` in
234
+ `app/views/users/_card.html.erb` shows up on `UsersController#show`
235
+ three hops away, and `rigor effects explain` prints the path.
236
+
237
+ A partial is looked up in the rendering template's own format, with
238
+ the one fallback Action View itself hard-codes: a `.js.erb` template
239
+ reaches `_list.js.erb` where it exists and `_list.html.erb`
240
+ otherwise, which is how "a JS response that injects rendered HTML"
241
+ works at all. Only one of the two is joined, never both. A `.json`,
242
+ `.xml` or `.turbo_stream` template gets no fallback — what those fall
243
+ back to depends on the request's `Accept` header, which the source
244
+ does not say — and neither does a render site that names its format
245
+ (`formats: [:js]`, `render "list.js"`) or a controller-side `render`.
246
+
247
+ The fallback stops at a template that **exists and produced no
248
+ unit**: a `_list.js.haml`, or a `_list.js.erb` whose compiled Ruby
249
+ does not parse. Rails runs that file, so the `.html` one's effects
250
+ are not what the render produces, and the taint stays. That is why
251
+ this plugin claims `app/views/**/*.{haml,slim,jbuilder,builder,rabl,ruby}`
252
+ and compiles none of them: the claim is how the engine learns a
253
+ template is there. A handler outside that list is invisible, and a
254
+ render of one still falls back.
255
+
256
+ Two approximations ride along, and each costs labels rather than a
257
+ taint. A partial reached *through* the fallback renders its own
258
+ partials in `html`, while Action View's context is still
259
+ `[:js, :html]`; where a nested partial exists in both formats, the
260
+ `.js` template gets the `.html` one's labels. And a template whose
261
+ name carries no format at all (`_row.jbuilder`) blocks nothing, since
262
+ its key has no format to block — which happens to agree with Rails,
263
+ which ranks a formatted template above it. Both are zero occurrences
264
+ on the measured corpus.
265
+
266
+ The `template-not-analysed` taint on a `render` is discharged
267
+ exactly when the edge lands on a real unit. It **stays** when it
268
+ does not, and both cases are common enough to name:
269
+
270
+ - the target is computed — `render params[:view]`, or
271
+ `render formats: some_format`. The render site is read from
272
+ literals only, so anything computed keeps the honest "and possibly
273
+ more";
274
+ - the target names no template this plugin compiled — a `render
275
+ partial: @thing`, or a partial that exists in neither the requested
276
+ format nor its fallback;
277
+ - the template is outside `app/views/**/*.erb` — a Haml, Slim or
278
+ Jbuilder view, which this plugin does not claim.
279
+
280
+ `render json:`, `render plain:` and the rest of the non-template
281
+ family are left alone: they render no template, the rule declines,
282
+ and the row reads exactly as it did before.
283
+
284
+ An action that answered for itself is **not** edged to the
285
+ conventional template. `redirect_to`, `head`, `send_data` and
286
+ `send_file` each mean the implicit render did not happen, and
287
+ attributing `users/away` to an action that redirects would be a view
288
+ it never runs.
289
+
290
+ ### Holding views to an effect budget
291
+
292
+ A view unit is an `effects.envelopes:` subject like any class, and a
293
+ finding is positioned in the template:
294
+
295
+ ```yaml
296
+ effects:
297
+ envelopes:
298
+ - match: "app/views/**/*"
299
+ effect: [mutate.local]
300
+ ```
301
+
302
+ A bound judges only what Rigor proved by reading code, so what this
303
+ catches in a view is what Rigor's own catalogue proves: a `puts`
304
+ (`io.output.stdout`) or a `Time.now` (`nondet.time`).
305
+
306
+ **Plugin-sourced labels are never judged by an envelope.** A plugin's
307
+ statement about a framework method — `User.find` is `io.db.read`,
308
+ `@user.update` is `io.db.write`, `perform_later` is `job.enqueue` —
309
+ rides the declared (`≤`) lane, and the envelope check reads the proven
310
+ one. Listing `io.db.read` in a view's envelope, or leaving it out,
311
+ changes nothing: the lazy `<%= user.posts.count %>` is not a finding
312
+ either way. That is a property of the whole Rails effect layer rather
313
+ than of views ([ADR-103](../../adr/103-effect-labels.md) WD17, ruled
314
+ in [#1059](https://github.com/rigortype/rigor/issues/1059)).
315
+
316
+ What notices a view that starts reading the database is the effect
317
+ snapshot. With `.rigor-effects.yml` committed, `rigor effects check`
318
+ fails by default on drift in either lane, and a template that gains a
319
+ query shows up in the diff as a declared-lane addition:
320
+
321
+ ```
322
+ view:users/show.html ≤+ io.db.read
323
+ ```
324
+
325
+ That is a ratchet on the whole project's recorded effects, reviewed as a
326
+ diff — not a policy scoped to `app/views/**/*`. See
327
+ [Effect labels](../19-effect-labels.md#what-a-bound-can-and-cannot-see).
328
+
96
329
  ## Limitations
97
330
 
331
+ - **A controller's own layout is not edged.** A layout is a unit
332
+ now, and a `render layout:` *inside a view* reaches it — but the
333
+ layout Rails wraps an action's template in (`layouts/application`,
334
+ or whatever `layout "base"` named) is not attributed to that
335
+ action. A callee rule may read the call's literals, the unit's
336
+ owner and the unit's key, and a layout's name is none of those:
337
+ it is a class-body declaration plus a convention lookup against
338
+ the view tree. So the layout's own effects reach a view that
339
+ renders it explicitly and no further.
340
+ - **`yield` in a layout is a `String` and nothing more.** The
341
+ keyword is rewritten into a declared call on the view context so
342
+ the body parses; what the inner template produced is never
343
+ modelled.
344
+ - **Unsaved render sites are not read in the editor.** The
345
+ render-site index reads templates and controllers from disk, so a
346
+ `locals:` you have typed but not saved does not reach the partial
347
+ until the save. A keystroke recompiles only the buffer, and a save
348
+ rebuilds the project's analysis as it always has. A view changed on
349
+ disk *without* a save the editor sees (a `git checkout`, a
350
+ formatter run elsewhere) recompiles every view on the next
351
+ publish, because a partial's locals can come from any of them. A
352
+ full `rigor check` does
353
+ the same compile work it did before — the index hands its compiled
354
+ sources to the unit transform rather than compiling twice.
355
+ - **ERB only, under `app/views`.** `template_globs:` is a manifest
356
+ row, read without running plugin code, so it cannot consult
357
+ `view_search_paths:`. Haml, Slim and Jbuilder are the same seam
358
+ behind a different compiler and are not claimed.
98
359
  - **Implicit-self helpers only.** `*_path` / `*_url` calls with an
99
360
  explicit receiver (`Rails.application.routes.url_helpers.x_path`)
100
361
  are passed through.
@@ -0,0 +1,153 @@
1
+ # rigor-active-model-serializers
2
+
3
+ Teaches Rigor what `object` is inside an ActiveModel::Serializer
4
+ subclass, so the reads below it — `object.username`,
5
+ `object.account.display_name` — resolve against your model instead of
6
+ dispatching on an unknown receiver. It reads source only; no
7
+ ActiveModelSerializers runtime dependency.
8
+
9
+ It ships bundled in `rigortype`. Activate it under `plugins:`, alongside
10
+ `rigor-activerecord`, which is what tells this plugin which models exist
11
+ and what they answer:
12
+
13
+ ```yaml
14
+ plugins:
15
+ - rigor-activerecord
16
+ - rigor-active-model-serializers
17
+ ```
18
+
19
+ ## What it does — no diagnostics
20
+
21
+ The plugin emits nothing of its own. It contributes one return type and
22
+ the gem's framework constants, so that
23
+
24
+ ```ruby
25
+ class REST::AccountSerializer < ActiveModel::Serializer
26
+ attributes :id, :username, :display_name
27
+
28
+ def display_name
29
+ object.display_name
30
+ end
31
+ end
32
+ ```
33
+
34
+ types `object` as `Account`, and `object.display_name` as the
35
+ `accounts.display_name` column's type. What you notice is the diagnostics
36
+ that now CAN fire — a typo below a serializer's `object` used to be
37
+ invisible:
38
+
39
+ ```text
40
+ app/serializers/errors_serializer.rb:11:23: error: undefined method `nickname' for String [call.undefined-method]
41
+ ```
42
+
43
+ and `rigor coverage` reporting a larger share of `app/serializers` as
44
+ precise. On Mastodon that share went from 51.1 % to 53.2 % with no change
45
+ at all to the diagnostic set
46
+ ([the measurement](../../notes/20260917-ams-object-recognizer.md)).
47
+
48
+ ## When `object` is typed, and when it is not
49
+
50
+ Nothing in a serializer's source states which class it serializes — AMS
51
+ binds the resource when the serializer is constructed. So the type is
52
+ always DERIVED, and two independent things have to agree before the
53
+ plugin contributes one:
54
+
55
+ 1. **The name resolves.** `<Model>Serializer` names exactly one model
56
+ your project has — `REST::AccountSerializer` → `Account`.
57
+ 2. **The model answers the serializer.** Every name the serializer will
58
+ read off its resource — the `attributes` / `attribute` / `has_many` /
59
+ `has_one` / `belongs_to` declarations it does not define itself, plus
60
+ every `object.<name>` in its body — is a column, per-column predicate,
61
+ association, enum, alias, scope or macro-defined method of that model,
62
+ or a method your project defines on it or an ancestor.
63
+
64
+ One unanswered name declines the whole serializer, because the resource
65
+ is then something else — commonly a presenter or a decorator around the
66
+ model, which shares most of its surface and is given away by the one or
67
+ two names that differ. On Mastodon that check is what stops
68
+ `REST::ConversationSerializer` typing as `Conversation` when its resource
69
+ is an `AccountConversation`.
70
+
71
+ | your code | `object` types as |
72
+ | --- | --- |
73
+ | `REST::AccountSerializer`, `Account` exists and answers it | `Account` |
74
+ | `Admin::AccountSerializer`, where `Admin::Account` and `Account` both exist | unchanged — the reading is ambiguous and nothing here ranks one above the other |
75
+ | `ConversationSerializer` whose declarations `Conversation` cannot answer | unchanged (`Dynamic`) |
76
+ | a serializer that reads nothing off its resource | unchanged — there is nothing to check the name against |
77
+ | `ContextSerializer`, and no `Context` **model** exists | unchanged (`Dynamic`) |
78
+ | a serializer you listed in `model_overrides` | the model you named |
79
+ | `object` written outside a serializer, or over your own `def object` | unchanged (`Dynamic`) |
80
+
81
+ A serializer is a class under `serializer_search_paths` whose superclass
82
+ chain reaches `ActiveModel::Serializer` — including through your own base
83
+ serializer (`class ApplicationSerializer < ActiveModel::Serializer`, then
84
+ `class AccountSerializer < ApplicationSerializer`). A class the chain does
85
+ not reach is not a serializer here whatever it is called: `object` is an
86
+ ordinary method name, and a `Json::ConversationSerializer` of your own is
87
+ entitled to define it.
88
+
89
+ `class << self` and `def self.` bodies are left alone. An `object` read
90
+ there is a `NoMethodError` at run time — AMS's reader is an instance
91
+ method — so there is nothing to type and nothing the plugin should say
92
+ about it.
93
+
94
+ **The plugin never guesses.** Where the resource cannot be established it
95
+ contributes nothing and `object` keeps the answer it had, because a wrong
96
+ class here would turn every read on it into a false
97
+ `call.undefined-method` on working code.
98
+
99
+ It also does nothing at all without `rigor-activerecord`, or on a project
100
+ with no `db/schema.rb` / `db/structure.sql`: the model set it resolves and
101
+ checks against comes from that plugin, and is withheld when the schema is
102
+ missing.
103
+
104
+ ## Configuration
105
+
106
+ ```yaml
107
+ plugins:
108
+ - gem: rigor-active-model-serializers
109
+ config:
110
+ serializer_search_paths: ["app/serializers", "app/lib"]
111
+ serializer_base_classes: ["ActiveModel::Serializer"]
112
+ model_overrides:
113
+ REST::InstanceSerializer: InstancePresenter
114
+ ```
115
+
116
+ | Key | Default | Meaning |
117
+ | --- | --- | --- |
118
+ | `serializer_search_paths` | `["app/serializers", "app/lib"]` | Where to look for serializer classes. `app/lib` is in the default set because a base serializer routinely lives outside `app/serializers` — Mastodon's `ActivityPub::Serializer`, the parent of 60 of its serializers, is one. A directory you do not have costs one probe. |
119
+ | `serializer_base_classes` | `["ActiveModel::Serializer"]` | The roots the ancestry walk starts from. |
120
+ | `model_overrides` | `{}` | Serializer class name → resource class name, for a serializer the derivation cannot reach — a presenter, a decorator, or a class named for a JSON shape. Your assertion; it is not re-checked. |
121
+
122
+ ## Limitations
123
+
124
+ - **SimpleForm inputs.** `SimpleForm::Inputs::Base#object` shares the name
125
+ and nothing else — its resource comes from the `simple_form_for` call
126
+ site, not from the input class.
127
+ - **`serializer:` / `each_serializer:` options.** They name the
128
+ serializer for an association, never the model for a serializer.
129
+ - **Model surface your schema and source do not state.** A method the
130
+ model gets from `method_missing`, from a gem's macro `rigor-activerecord`
131
+ does not recognise, or from a module included at runtime is invisible to
132
+ the model index, so a serializer reading one is declined.
133
+ `rigor-activerecord` reads `delegate`, the associations a concern
134
+ declares in its `included do`, the Paperclip / Active Storage attachment
135
+ macros and `enum` value predicates
136
+ ([#1049](https://github.com/rigortype/rigor/issues/1049)); anything else
137
+ a macro defines is not folded yet.
138
+ - **Some ways of reading the resource.** `object[:key]`, `object.title =`,
139
+ `object.try(:name)`, `object.present?` and an `attribute(:x) { ... }`
140
+ block are not read as evidence, so a serializer using one may decline
141
+ even where the model is right. Always the safe direction — a decline,
142
+ never a wrong type.
143
+ - **Serializers for plain objects** — `ActiveModelSerializers::Model`
144
+ subclasses, Structs, presenters. Use `model_overrides` where the class
145
+ you want is a real constant you are happy to have checked.
146
+
147
+ ## Plugin internals
148
+
149
+ The serializer discoverer / index, the ancestry closure and the contract
150
+ surfaces this plugin exercises are in the
151
+ [plugin's README](../../../plugins/rigor-active-model-serializers/README.md).
152
+ To write a plugin, see [`examples/`](../../../examples/README.md) and the
153
+ [`rigor-plugin-author`](../08-skills.md) skill.
@@ -95,6 +95,13 @@ nothing loads Solid Queue.
95
95
  evidence. A schedule loaded from Ruby rather than from a file under
96
96
  `recurring_paths` supplies nothing, by the same "read only what is
97
97
  written" rule.
98
+ - **The "failed to discover jobs" warning is run-scoped.** It is
99
+ a fact about your configuration, not about any one source file, so
100
+ it is reported once per run on `.rigor.yml` rather than repeated on
101
+ every analysed file. It is `:warning`, so if you baselined it at its
102
+ old position that entry no longer matches and the row fails a
103
+ `--fail-on=warning` run — regenerate with `rigor baseline
104
+ regenerate`.
98
105
 
99
106
  ## Plugin internals
100
107