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
@@ -163,6 +163,95 @@ class Object
163
163
  # exception to the try-shaped-dispatch rule above (design note § 11.2).
164
164
  %a{pure}
165
165
  def in?: (untyped) -> bool
166
+ # `in?(another_object) ? self : nil` — `params[:kind].presence_in(%w[a b])`.
167
+ # The same `#include?` dispatch as `in?`, and the same exception.
168
+ %a{pure}
169
+ def presence_in: (untyped) -> self?
170
+
171
+ # Issue #673 — `core_ext/object/to_query`, `core_ext/object/duplicable` and
172
+ # `core_ext/object/instance_variables` all hang off `Object`, so every
173
+ # RBS-known receiver reaches them; declaring them only on `Hash` (as
174
+ # `to_param` / `to_query` were) or only on `NilClass` (as `duplicable?` /
175
+ # `instance_values` were) left `"x".to_param`, `1.duplicable?` and friends a
176
+ # `call.undefined-method` on ordinary Rails code.
177
+ #
178
+ # `to_param` is `to_s`; `to_query(key)` interpolates two `CGI.escape` results.
179
+ # `key` is `untyped` because the body only calls `to_param` on it, and both
180
+ # `:name` and `"user[name]"` are ordinary call sites. Neither is `%a{pure}`:
181
+ # both dispatch `to_param` on a value whose own override is invisible here,
182
+ # the same reason `Hash#to_query` below carries no envelope.
183
+ def to_param: () -> String
184
+ def to_query: (untyped key) -> String
185
+
186
+ # `bool` and not `true`: `Object#duplicable?` answers true, but the
187
+ # `Singleton` (and, on a Ruby where methods are not duplicable, `Method` /
188
+ # `UnboundMethod`) overrides answer false. `-> true` on the universal
189
+ # receiver would make every `if x.duplicable?` an always-truthy branch —
190
+ # a false positive on the only shape anybody writes.
191
+ %a{pure}
192
+ def duplicable?: () -> bool
193
+
194
+ # Keys without the leading `@` for `instance_values`, with it for
195
+ # `instance_variable_names`.
196
+ def instance_values: () -> Hash[String, untyped]
197
+ def instance_variable_names: () -> Array[String]
198
+
199
+ # `core_ext/object/deep_dup` — `duplicable? ? dup : self`, plus `Array`
200
+ # (`map(&:deep_dup)`), `Hash` and `Module` overrides. `self` over-claims on
201
+ # an `Array` subclass, whose override answers a plain `Array`. Declared on
202
+ # `Object` and not only on `Hash`, where it sat alone: `[[1], [2]].deep_dup`
203
+ # is the common shape.
204
+ #
205
+ # NOT `%a{pure}`: on `Object` the `dup` runs the receiver class's own
206
+ # `initialize_copy`, an unknown-at-this-declaration dispatch like the one
207
+ # that keeps `as_json` bare. `Hash#deep_dup` below reaches the same `dup`
208
+ # through `value.deep_dup` on every value and is bare for the same reason.
209
+ def deep_dup: () -> self
210
+
211
+ # `core_ext/object/with` (ActiveSupport 7.1+) — sets each keyword through
212
+ # its public writer, yields `self`, and restores the old values in an
213
+ # `ensure`; the answer is the block's. There is no blockless form: `yield` without a block raises.
214
+ # ActiveSupport `undef_method`s it on `NilClass`, `TrueClass`, `FalseClass`,
215
+ # `Integer`, `Float` and `Symbol`, which RBS cannot express, so `1.with(a: 1)`
216
+ # resolves here where Ruby raises — a missed error, not a false one.
217
+ # `Data#with` (core) is a subclass override and keeps its own `-> self`.
218
+ #
219
+ # NOT `%a{pure}`: it writes the receiver's attributes through `public_send`.
220
+ def with: [T] (**untyped attributes) { (self) -> T } -> T
221
+
222
+ # `core_ext/object/with_options` — without a block, the
223
+ # `ActiveSupport::OptionMerger` proxy (not declared here); with one, the
224
+ # block's value. A zero-arity block is `instance_eval`ed on the proxy, the
225
+ # `with_options dependent: :destroy do has_many … end` model-body shape.
226
+ #
227
+ # NOT `%a{pure}`: the proxy forwards every call to the receiver through
228
+ # `method_missing`, a dispatch this audit cannot see.
229
+ def with_options: (untyped options) -> untyped
230
+ | [T] (untyped options) { (?) -> T } -> T
231
+
232
+ # `core_ext/string/output_safety` — `false` on `Object`, `true` on
233
+ # `Numeric`, the buffer's own state on `ActiveSupport::SafeBuffer`. `bool`
234
+ # for the reason `duplicable?` is: `-> false` on the universal receiver makes
235
+ # every `if x.html_safe?` an always-falsy branch on the subclasses that answer
236
+ # true. All three bodies are a constant or an ivar read.
237
+ %a{pure}
238
+ def html_safe?: () -> bool
239
+ end
240
+
241
+ # ---------------------------------------------------------------
242
+ # Kernel — `core_ext/kernel/singleton_class`
243
+ # ---------------------------------------------------------------
244
+
245
+ # `singleton_class.class_eval(*args, &block)` on any object. Declared on
246
+ # `Kernel`, where ActiveSupport defines it; a `Module` receiver still answers
247
+ # core `Module#class_eval`, which is its own alias of `module_eval`. The two
248
+ # overloads mirror that `module_eval`: a string, or a block given the
249
+ # singleton class.
250
+ module Kernel
251
+ # NOT `%a{pure}`: it evaluates a string or a block in the singleton class,
252
+ # and a block that defines methods is the point.
253
+ def class_eval: (string code, ?string? filename, ?int lineno) -> untyped
254
+ | [U] () { (untyped singleton_class) -> U } -> U
166
255
  end
167
256
 
168
257
  # `nil.blank?` / `nil.present?` / `nil.try` are the most frequent
@@ -185,6 +274,13 @@ class NilClass
185
274
  %a{pure}
186
275
  def duplicable?: () -> true
187
276
  def instance_values: () -> Hash[String, untyped]
277
+ # `core_ext/object/to_query` overrides `to_param` on the three singleton
278
+ # values to return `self` rather than `Object#to_param`'s `to_s`, so the
279
+ # `Object` row above would carry a wrong return here. `NilClass#to_query`
280
+ # is its own method (`CGI.escape(key.to_param)`), still a String.
281
+ %a{pure}
282
+ def to_param: () -> nil
283
+ def to_query: (untyped key) -> String
188
284
  end
189
285
 
190
286
  class TrueClass
@@ -192,6 +288,8 @@ class TrueClass
192
288
  def blank?: () -> false
193
289
  %a{pure}
194
290
  def present?: () -> true
291
+ %a{pure}
292
+ def to_param: () -> true
195
293
  end
196
294
 
197
295
  class FalseClass
@@ -199,6 +297,8 @@ class FalseClass
199
297
  def blank?: () -> true
200
298
  %a{pure}
201
299
  def present?: () -> false
300
+ %a{pure}
301
+ def to_param: () -> false
202
302
  end
203
303
 
204
304
  # ---------------------------------------------------------------
@@ -230,6 +330,8 @@ class String
230
330
  def dasherize: () -> String
231
331
  %a{pure}
232
332
  def upcase_first: () -> String
333
+ %a{pure}
334
+ def downcase_first: () -> String # ActiveSupport 7.1+
233
335
  # NOT %a{pure}: transliterates through `I18n.transliterate`, which reads
234
336
  # `I18n.locale` when `locale:` is left at its `nil` default — a request-
235
337
  # scoped global read the inflection-table assumption does not cover.
@@ -245,6 +347,11 @@ class String
245
347
  %a{pure}
246
348
  def humanize: (?capitalize: bool, ?keep_id_suffix: bool) -> String
247
349
 
350
+ # `core_ext/string/behavior` — the `acts_like?(:string)` duck-type hook, a
351
+ # hardcoded `true`.
352
+ %a{pure}
353
+ def acts_like_string?: () -> true
354
+
248
355
  # `core_ext/string/filters`
249
356
  %a{pure}
250
357
  def squish: () -> String
@@ -297,6 +404,15 @@ class String
297
404
  # follow-up to confirm and either implement or remove the declaration.
298
405
  def to_hours: () -> Float
299
406
 
407
+ # `core_ext/string/zones` — `Time.find_zone!(zone).parse(self)`, or
408
+ # `to_time` when no zone is set. `untyped` like the `Time` / `Date` rows:
409
+ # the parse answers an `ActiveSupport::TimeWithZone`, or `nil` on a string
410
+ # with no date parts. NOT `%a{pure}`: the default argument reads
411
+ # `Time.zone`, and `TimeZone#parse(str, now = now())` reads the clock on
412
+ # every call, whether or not the string names every part. The plugin's
413
+ # `effect_attributions:` carry both labels.
414
+ def in_time_zone: (?untyped zone) -> untyped
415
+
300
416
  # `core_ext/string/access`
301
417
  %a{pure}
302
418
  def at: (Integer | Range[Integer] | Regexp position) -> String?
@@ -316,12 +432,29 @@ class String
316
432
  # `core_ext/string/multibyte`
317
433
  %a{pure}
318
434
  def mb_chars: () -> untyped
435
+ # Reads `encoding` / `valid_encoding?`; the `ASCII-8BIT` branch
436
+ # `force_encoding`s a `dup`, never the receiver.
437
+ %a{pure}
438
+ def is_utf8?: () -> bool
319
439
 
320
440
  # `core_ext/string/inquiry`
321
441
  %a{pure}
322
442
  def inquiry: () -> untyped # ActiveSupport::StringInquirer
323
443
  end
324
444
 
445
+ # ---------------------------------------------------------------
446
+ # Symbol — `core_ext/symbol/starts_ends_with`
447
+ # ---------------------------------------------------------------
448
+
449
+ # `alias_method`s of core `start_with?` / `end_with?`, declared with the core
450
+ # parameter lists.
451
+ class Symbol
452
+ %a{pure}
453
+ def starts_with?: (*Regexp | string prefixes) -> bool
454
+ %a{pure}
455
+ def ends_with?: (*string suffixes) -> bool
456
+ end
457
+
325
458
  # ---------------------------------------------------------------
326
459
  # Integer — `core_ext/integer/*`
327
460
  # ---------------------------------------------------------------
@@ -486,6 +619,22 @@ end
486
619
  # purpose — some are already covered by the separate `dynamic_return` rule for `+`/`-`/`*` below, the rest
487
620
  # are simply outside this issue's scope and remain silently `Dynamic` rather than a guess.
488
621
  module ActiveSupport
622
+ # Issue #534 item 7 — `ActiveSupport::Concern` was 87 unresolved constant sites on mastodon, essentially
623
+ # all of them the `extend ActiveSupport::Concern` line at the top of a concern module. Naming it is a
624
+ # constant-resolution fix and nothing more: the three members are declared because `extend`ing a module
625
+ # puts them on the extender's singleton, and leaving them out of a now-RBS-known module is what would
626
+ # turn `included do … end` into a false `call.undefined-method` in every concern in the project. The
627
+ # blocks are `(?) -> untyped` because their body is evaluated against the INCLUDING class, which this
628
+ # signature has no way to name.
629
+ #
630
+ # `Concern` is listed in the manifest's `open_receivers:` for the rest of its surface (`append_features`,
631
+ # `class_methods`' generated `ClassMethods` constant, …).
632
+ module Concern
633
+ def included: (?untyped base) ?{ (?) -> untyped } -> untyped
634
+ def prepended: (?untyped base) ?{ (?) -> untyped } -> untyped
635
+ def class_methods: () { (?) -> untyped } -> untyped
636
+ end
637
+
489
638
  class Duration
490
639
  # The `ago` family (issue #659). `since(time = ::Time.current)` and `ago(time = ::Time.current)` are
491
640
  # the only two real methods; `from_now` / `after` are `alias`es of the first and `until` / `before`
@@ -501,13 +650,17 @@ module ActiveSupport
501
650
  # help there: it protects a Duration receiver, and the diagnostic lands on the `Time` the call
502
651
  # RETURNS. #658 declared that surface and #670 the `Date` / `DateTime` twins, so the chains resolve.
503
652
  #
504
- # `() -> Time` is the correct class, not a compromise. Under a zone these answer an
505
- # `ActiveSupport::TimeWithZone` (verified by calling them), and Rails overrides `TimeWithZone#is_a?`
506
- # to answer true for `::Time` — so `Time` is the class Rails ITSELF tells callers the value is, and
507
- # `Time.current` above already answers `Time` for the same reason. A `Time | TimeWithZone` union was
508
- # measured and rejected: it draws no `undefined-method`, but types every downstream call
509
- # `Dynamic[top]` (`iso8601`, `beginning_of_day`, `to_date`, `- 1.day`), which is worth no more than
510
- # leaving them undeclared while still firing a parameter mismatch on the TimeWithZone arm.
653
+ # `() -> TimeWithZone` since #673, where the class these actually build under a zone (verified by
654
+ # calling them) was finally declared — as a `::Time` SUBCLASS, so nothing a `Time` return bought is
655
+ # given up: every `Time` instance method still resolves on the result, every `Time`-typed parameter
656
+ # still accepts it, and the four TWZ-only readers (`time_zone`, `time`, `period`, `comparable_time`)
657
+ # stop being a false `call.undefined-method` on `1.hour.ago`. It was `() -> Time` before that, on the
658
+ # ground that Rails overrides `TimeWithZone#is_a?` to answer true for `::Time` — true, and the
659
+ # subclass is the same claim made where the analyser can also see the extra readers. A
660
+ # `Time | TimeWithZone` UNION was measured and rejected: it draws no `undefined-method`, but types
661
+ # every downstream call `Dynamic[top]` (`iso8601`, `beginning_of_day`, `to_date`, `- 1.day`), which is
662
+ # worth no more than leaving them undeclared while still firing a parameter mismatch on the
663
+ # TimeWithZone arm.
511
664
  #
512
665
  # The with-an-argument form stays `(untyped) -> untyped` on purpose, and this is not caution: the
513
666
  # runtime return depends on the argument's class AND on whether the duration carries a date-scale
@@ -520,17 +673,17 @@ module ActiveSupport
520
673
  # what stops `created_at > 7.days.ago` folding to a constant. They are already attributed `nondet.time`
521
674
  # in `lib/rigor/plugin/activesupport_core_ext/effects.rb` — annotating them here would duplicate an
522
675
  # envelope the manifest already owns, and risk the two disagreeing.
523
- def ago: () -> Time
676
+ def ago: () -> TimeWithZone
524
677
  | (untyped time) -> untyped
525
- def until: () -> Time
678
+ def until: () -> TimeWithZone
526
679
  | (untyped time) -> untyped
527
- def before: () -> Time
680
+ def before: () -> TimeWithZone
528
681
  | (untyped time) -> untyped
529
- def since: () -> Time
682
+ def since: () -> TimeWithZone
530
683
  | (untyped time) -> untyped
531
- def from_now: () -> Time
684
+ def from_now: () -> TimeWithZone
532
685
  | (untyped time) -> untyped
533
- def after: () -> Time
686
+ def after: () -> TimeWithZone
534
687
  | (untyped time) -> untyped
535
688
 
536
689
  # `to_i` returns `@value.to_i` — always Integer, whether `@value` itself is Integer (`1.day`) or Float
@@ -574,6 +727,48 @@ module ActiveSupport
574
727
  %a{pure}
575
728
  def parts: () -> Hash[Symbol, Integer | Float]
576
729
  end
730
+
731
+ # ---------------------------------------------------------------
732
+ # ActiveSupport::TimeWithZone — issue #673
733
+ # ---------------------------------------------------------------
734
+ #
735
+ # `Time.current` and the `Duration#ago` family above really answer a `TimeWithZone` under a zone, and
736
+ # this class was not modelled at all, so the four readers a TWZ has and a `Time` does not —
737
+ # `time_zone`, `time`, `period`, `comparable_time` — were a false `call.undefined-method` on every
738
+ # `Time.current` receiver. Declaring them on `Time` instead would have been wrong: a plain `Time`
739
+ # genuinely lacks all four, and the diagnostic would stop firing where it is correct.
740
+ #
741
+ # `< ::Time` rather than a `Time | TimeWithZone` union at the producers. The union was measured during
742
+ # #632 and rejected — it types every downstream call `Dynamic[top]`. Inheritance is both cheaper and
743
+ # more honest: Rails overrides `TimeWithZone#is_a?` to answer true for `::Time`, so `Time` is the class
744
+ # Rails itself tells callers a TWZ is, and TWZ's `method_missing` forwards every name it does not
745
+ # define to the wrapped `::Time`, which is exactly what inheriting `Time`'s surface models. A TWZ
746
+ # therefore satisfies every `Time`-typed parameter, and chains keep resolving instead of collapsing.
747
+ #
748
+ # The cost, taken deliberately: `Time.current` answers a plain `Time` when `Time.zone` is nil, so
749
+ # `Time.current.time_zone` in a zone-less activesupport-only project is a real `NoMethodError` this
750
+ # now types. Rails sets `config.time_zone` ("UTC") by default, so the shape is rare, and the false
751
+ # positive it replaces fired on correct Rails code.
752
+ class TimeWithZone < ::Time
753
+ # `Time`'s own `initialize` takes the `(year, month, …)` parts; inheriting it would make
754
+ # `TimeWithZone.new(utc, zone)` — how Rails constructs one — a `call.argument-type-mismatch`.
755
+ # `untyped` because the zone and period carriers (`ActiveSupport::TimeZone`,
756
+ # `TZInfo::TimezonePeriod`) are not declared anywhere in this bundle.
757
+ def initialize: (untyped utc_time, untyped time_zone, ?untyped local_time, ?untyped period) -> void
758
+
759
+ # The `attr_reader`; an `ActiveSupport::TimeZone`, undeclared here.
760
+ %a{pure}
761
+ def time_zone: () -> untyped
762
+
763
+ # The wall-clock `::Time` in the zone. `utc` / `getutc` / `getgm` / `gmtime` are `Time`'s own names
764
+ # and stay inherited; `comparable_time` is the alias of `utc` that `Time` does not carry. Neither is
765
+ # `%a{pure}`: both memoise into an ivar on first call.
766
+ def time: () -> ::Time
767
+ def comparable_time: () -> ::Time
768
+
769
+ # A `TZInfo::TimezonePeriod`, undeclared here. Memoised like `time`, so not `%a{pure}`.
770
+ def period: () -> untyped
771
+ end
577
772
  end
578
773
 
579
774
  # ---------------------------------------------------------------
@@ -584,7 +779,11 @@ class Time
584
779
  # NOT %a{pure}: already attributed nondet.time/global.read/global.write
585
780
  # by `lib/rigor/plugin/activesupport_core_ext/effects.rb` (issue #387).
586
781
  # Duplicating an RBS envelope here risks the two disagreeing.
587
- def self.current: () -> Time
782
+ #
783
+ # `Time.zone ? Time.zone.now : Time.now` — an `ActiveSupport::TimeWithZone` under a zone, which since
784
+ # #673 is declared as a `::Time` subclass so the four TWZ-only readers resolve without the rest of the
785
+ # chain losing its `Time` typing. See that class's own note for the zone-less cost.
786
+ def self.current: () -> ActiveSupport::TimeWithZone
588
787
  def self.zone: () -> untyped # ActiveSupport::TimeZone | nil
589
788
  def self.zone=: (String | Symbol | untyped) -> untyped
590
789
 
@@ -1264,6 +1463,10 @@ class Array[unchecked out Elem]
1264
1463
  def fifth: () -> Elem?
1265
1464
  %a{pure}
1266
1465
  def forty_two: () -> Elem?
1466
+ %a{pure}
1467
+ def second_to_last: () -> Elem?
1468
+ %a{pure}
1469
+ def third_to_last: () -> Elem?
1267
1470
 
1268
1471
  # `core_ext/array/grouping`
1269
1472
  %a{pure}
@@ -1295,6 +1498,9 @@ class Array[unchecked out Elem]
1295
1498
  # `core_ext/array/extract`
1296
1499
  def extract!: () { (Elem) -> bool } -> Array[Elem] # NOT %a{pure}: bang, mutates in place.
1297
1500
 
1501
+ # `core_ext/array/extract_options`
1502
+ def extract_options!: () -> Hash[untyped, untyped] # NOT %a{pure}: pops the trailing options Hash.
1503
+
1298
1504
  # `core_ext/object/blank` / `core_ext/enumerable` — both
1299
1505
  # ActiveSupport additions, shipped on Array specifically.
1300
1506
  %a{pure}
@@ -1339,6 +1545,15 @@ module Enumerable[unchecked out Elem]
1339
1545
  def sole: () -> Elem
1340
1546
  %a{pure}
1341
1547
  def compact_blank: () -> Array[Elem]
1548
+ %a{pure}
1549
+ def many?: () -> bool
1550
+ | () { (Elem) -> boolish } -> bool
1551
+ # `key` and `series` are untyped on purpose: the default `filter: true`
1552
+ # path is `group_by(&key).values_at(*series)`, so a Proc key or a Set /
1553
+ # Range series is correct Rails code. `filter:` is ActiveSupport 8.x;
1554
+ # passing it on 7.x raises, which this unversioned row does not catch.
1555
+ %a{pure}
1556
+ def in_order_of: (untyped key, untyped series, ?filter: boolish) -> Array[Elem]
1342
1557
  end
1343
1558
 
1344
1559
  # ---------------------------------------------------------------
@@ -1369,10 +1584,17 @@ class Hash[unchecked out K, unchecked out V]
1369
1584
  %a{pure}
1370
1585
  def deep_transform_values: () { (V) -> untyped } -> Hash[K, untyped]
1371
1586
  def deep_transform_values!: () { (V) -> untyped } -> self
1587
+ # `alias_method`s of `symbolize_keys` / `symbolize_keys!`.
1588
+ %a{pure}
1589
+ def to_options: () -> Hash[Symbol, V]
1590
+ def to_options!: () -> self
1372
1591
 
1373
1592
  # `core_ext/hash/deep_dup` — allocates a deep copy, never touches the
1374
- # receiver.
1375
- %a{pure}
1593
+ # receiver. NOT `%a{pure}`: it calls `deep_dup` on every value, and on
1594
+ # every key that is not a `String` or `Symbol` — an ELEMENT of unknown class
1595
+ # whose `dup` runs that class's own `initialize_copy`. That is the dispatch
1596
+ # the purity rule at the top of this file excludes, and the reason
1597
+ # `Object#deep_dup` above is bare.
1376
1598
  def deep_dup: () -> Hash[K, V]
1377
1599
 
1378
1600
  # `core_ext/hash/deep_merge` — the block receives `(key, this_val,
@@ -1383,6 +1605,10 @@ class Hash[unchecked out K, unchecked out V]
1383
1605
  | (Hash[K, V]) { (K, V, V) -> V } -> Hash[K, V]
1384
1606
  def deep_merge!: (Hash[K, V]) -> self
1385
1607
  | (Hash[K, V]) { (K, V, V) -> V } -> self
1608
+ # `other.is_a?(Hash)` — the hook `ActiveSupport::DeepMergeable` asks before
1609
+ # recursing into a value. `:nodoc:`, but public.
1610
+ %a{pure}
1611
+ def deep_merge?: (untyped other) -> bool
1386
1612
 
1387
1613
  # `core_ext/hash/except` — `Hash#except` is in core RBS as of
1388
1614
  # Ruby 3.0+; `except!` is ActiveSupport-only. ActiveSupport
@@ -1407,6 +1633,9 @@ class Hash[unchecked out K, unchecked out V]
1407
1633
  # `%a{pure}` explicitly.
1408
1634
  %a{pure}
1409
1635
  def with_indifferent_access: () -> untyped
1636
+ # An `alias` of `with_indifferent_access`.
1637
+ %a{pure}
1638
+ def nested_under_indifferent_access: () -> untyped
1410
1639
 
1411
1640
  # `core_ext/hash/conversions`
1412
1641
  def self.from_xml: (String, ?Symbol disallowed_types) -> Hash[String, untyped]
@@ -1417,15 +1646,57 @@ class Hash[unchecked out K, unchecked out V]
1417
1646
  def compact_blank: () -> Hash[K, V]
1418
1647
  def compact_blank!: () -> self # NOT %a{pure}: bang (`delete_if`), mutates in place.
1419
1648
 
1420
- # `core_ext/hash/reverse_merge`
1649
+ # `core_ext/array/extract_options`
1650
+ %a{pure}
1651
+ def extractable_options?: () -> bool
1652
+
1653
+ # `core_ext/hash/reverse_merge` — `other_hash.merge(self)`, so the result
1654
+ # carries the argument's keys and values as well as the receiver's, the
1655
+ # same shape as core `Hash#merge`. `-> Hash[K, V]` typed
1656
+ # `opts.reverse_merge(size: 25, velocity: 10)` as `Hash[:size, 1]`.
1421
1657
  %a{pure}
1422
- def reverse_merge: (Hash[K, V]) -> Hash[K, V]
1658
+ def reverse_merge: [A, B] (Hash[A, B] other_hash) -> Hash[A | K, B | V]
1423
1659
  def reverse_merge!: (Hash[K, V]) -> self # NOT %a{pure}: bang (`replace`), mutates in place.
1660
+ # `alias_method`s: `with_defaults` of `reverse_merge`, `with_defaults!`
1661
+ # and `reverse_update` of `reverse_merge!`.
1662
+ %a{pure}
1663
+ def with_defaults: [A, B] (Hash[A, B] other_hash) -> Hash[A | K, B | V]
1664
+ def with_defaults!: (Hash[K, V]) -> self # NOT %a{pure}: bang (`replace`), mutates in place.
1665
+ def reverse_update: (Hash[K, V]) -> self # NOT %a{pure}: `reverse_merge!` under another name.
1424
1666
 
1425
1667
  # `core_ext/hash/slice` — `Hash#except` is in core RBS (Ruby 3.0+);
1426
1668
  # `Hash#slice` is in core RBS (Ruby 2.5+); the bang variants are
1427
- # ActiveSupport-only.
1669
+ # ActiveSupport-only. `extract!` deletes the named keys and answers them
1670
+ # as a new hash; a key the receiver lacks is skipped, not `nil`-filled.
1428
1671
  def slice!: (*K) -> Hash[K, V] # NOT %a{pure}: bang (`replace`), mutates in place.
1672
+ def extract!: (*K) -> Hash[K, V] # NOT %a{pure}: bang, mutates via `delete`.
1673
+ end
1674
+
1675
+ # ---------------------------------------------------------------
1676
+ # Range — `core_ext/range/*`
1677
+ # ---------------------------------------------------------------
1678
+
1679
+ # `[out E]` matches core `range.rbs`. A reopening whose parameters differ in
1680
+ # count or variance (`class Range`, `class Range[E]`) raises
1681
+ # `RBS::GenericParameterMismatchError` and collapses `Range`.
1682
+ class Range[out E]
1683
+ # `core_ext/range/overlap` — an `alias` of `overlap?`, which rbs declares
1684
+ # since Ruby 3.3 (ActiveSupport defines the method itself only on an older
1685
+ # Ruby). `overlap?` is therefore NOT redeclared here: a second declaration
1686
+ # is a `DuplicatedMethodDefinitionError` that collapses the whole class.
1687
+ %a{pure}
1688
+ def overlaps?: (Range[untyped]) -> bool
1689
+
1690
+ # `core_ext/range/conversions` — `ActiveSupport::RangeWithFormat`, prepended
1691
+ # onto `Range`. An unknown format falls back to `to_s`; `:db` answers `nil`
1692
+ # for a beginless-and-endless range, which the declared `String` does not
1693
+ # model — `(nil..nil).to_fs(:db)` is not a shape correct code writes.
1694
+ #
1695
+ # NOT `%a{pure}`, for the `Array#to_fs` reasons: `RANGE_FORMATS` is a
1696
+ # mutable constant an initializer extends, and `:db` calls `to_fs(:db)` on
1697
+ # both endpoints.
1698
+ def to_fs: (?Symbol format) -> String
1699
+ def to_formatted_s: (?Symbol format) -> String
1429
1700
  end
1430
1701
 
1431
1702
  # ---------------------------------------------------------------
@@ -60,6 +60,7 @@ module Rigor
60
60
  class Devise < Rigor::Plugin::Base
61
61
  manifest(
62
62
  id: "devise",
63
+ target_gems: ["devise"],
63
64
  version: "0.1.0",
64
65
  description: "Recognises Devise's `devise :strategy` DSL via ADR-16 Tier B.",
65
66
  trait_registries: [
@@ -30,6 +30,7 @@ module Rigor
30
30
  source_path: __FILE__,
31
31
  source_line: __LINE__
32
32
  )
33
+ Ractor.make_shareable(RESULT_DEFINITION)
33
34
 
34
35
  MAYBE_DEFINITION = Inference::HktRegistry.definition_with_body_tree(
35
36
  uri: :"dry_monads::maybe",
@@ -38,12 +39,14 @@ module Rigor
38
39
  source_path: __FILE__,
39
40
  source_line: __LINE__
40
41
  )
42
+ Ractor.make_shareable(MAYBE_DEFINITION)
41
43
 
42
44
  CONSTRUCTOR_METHODS = %i[Success Failure Some None].freeze
43
45
  UNWRAP_METHODS = %i[value! failure value_or].freeze
44
46
 
45
47
  manifest(
46
48
  id: "dry-monads",
49
+ target_gems: ["dry-monads"],
47
50
  version: "0.1.0",
48
51
  description: "Lightweight HKT support for dry-monads Result and Maybe carriers",
49
52
  hkt_registrations: [RESULT_REGISTRATION, MAYBE_REGISTRATION],
@@ -40,24 +40,26 @@ module Rigor
40
40
 
41
41
  module_function
42
42
 
43
- # @param paths [Array<String>] absolute paths to `.rb` files the project's `paths:` resolves to.
44
- # @param type_aliases [Hash{String => String}] the ADR-9 `:dry_type_aliases` fact published by
43
+ # @param paths — absolute paths to `.rb` files the project's `paths:` resolves to.
44
+ # @param io_boundary — the plugin's {Rigor::Plugin::IoBoundary}; every source read goes
45
+ # through it (#630) so each scanned file is a recorded cache dependency.
46
+ # @param type_aliases — the ADR-9 `:dry_type_aliases` fact published by
45
47
  # `rigor-dry-types` when loaded. Used to resolve `value(Types::Email)` references to their
46
48
  # underlying class. Empty when the plugin isn't loaded.
47
- # @return [Hash{String => Hash{Symbol => Hash{Symbol => String}}}] frozen per-schema typed-key
49
+ # @return frozen per-schema typed-key
48
50
  # table. Empty when no recognisable schema declaration is found.
49
- def scan(paths:, type_aliases: {})
51
+ def scan(paths:, io_boundary:, type_aliases: {})
50
52
  table = {}
51
53
  paths.each do |path|
52
- scan_file(path, type_aliases).each do |schema_const, shape|
54
+ scan_file(path, io_boundary, type_aliases).each do |schema_const, shape|
53
55
  table[schema_const] ||= shape
54
56
  end
55
57
  end
56
58
  table.freeze
57
59
  end
58
60
 
59
- def scan_file(path, type_aliases)
60
- source = File.read(path)
61
+ def scan_file(path, io_boundary, type_aliases)
62
+ source = io_boundary.read_file(path)
61
63
  parse_result = Prism.parse(source, filepath: path)
62
64
  return {} unless parse_result.errors.empty?
63
65
 
@@ -78,6 +78,7 @@ module Rigor
78
78
  class DrySchema < Rigor::Plugin::Base
79
79
  manifest(
80
80
  id: "dry-schema",
81
+ target_gems: ["dry-schema"],
81
82
  version: "0.1.0",
82
83
  description: "Recognises `Dry::Schema.{Params,JSON,define} { ... }` declarations " \
83
84
  "and publishes the per-schema typed-key table.",
@@ -130,7 +131,8 @@ module Rigor
130
131
  # `paths:`, parse errors degrade silently.
131
132
  def prepare(services)
132
133
  type_aliases = services.fact_store.read(plugin_id: "dry-types", name: :dry_type_aliases) || {}
133
- table = SchemaScanner.scan(paths: scannable_paths(services), type_aliases: type_aliases)
134
+ table = SchemaScanner.scan(paths: scannable_paths(services), io_boundary: io_boundary,
135
+ type_aliases: type_aliases)
134
136
  return if table.empty?
135
137
 
136
138
  services.fact_store.publish(
@@ -164,11 +166,13 @@ module Rigor
164
166
  shapes[name] = entry.nil? ? nil : ResultShape.build(entry)
165
167
  end
166
168
 
169
+ # ADR-45 WD1b (#613 / #630) — the classification probes go through the boundary, so an entry that
170
+ # is not there yet (or stops being a directory) is a recorded dependency of the scan's input set.
167
171
  def scannable_paths(services)
168
172
  @scannable_paths ||= services.configuration.paths.flat_map do |entry|
169
- if File.directory?(entry)
173
+ if io_boundary.directory?(entry)
170
174
  Dir.glob(File.join(entry, "**", "*.rb"), sort: true)
171
- elsif File.file?(entry) && entry.end_with?(".rb")
175
+ elsif io_boundary.file?(entry) && entry.end_with?(".rb")
172
176
  [entry]
173
177
  else
174
178
  []
@@ -51,6 +51,7 @@ module Rigor
51
51
  class DryStruct < Rigor::Plugin::Base
52
52
  manifest(
53
53
  id: "dry-struct",
54
+ target_gems: ["dry-struct"],
54
55
  version: "0.2.0",
55
56
  description: "Recognises dry-struct `attribute :name, T` DSL via ADR-16 Tier C; " \
56
57
  "promotes the reader's return type through ADR-18's `returns_from_arg:` " \
@@ -42,11 +42,13 @@ module Rigor
42
42
 
43
43
  module_function
44
44
 
45
- # @param paths [Array<String>] absolute paths to `.rb` files the project's `paths:` resolves to.
46
- # @return [Hash{String => String}] frozen `{aliased_name => underlying_class_name}` map. Empty
45
+ # @param paths — absolute paths to `.rb` files the project's `paths:` resolves to.
46
+ # @param io_boundary — the plugin's {Rigor::Plugin::IoBoundary}; every source read goes
47
+ # through it (#630) so each scanned file is a recorded cache dependency.
48
+ # @return frozen `{aliased_name => underlying_class_name}` map. Empty
47
49
  # when no `include Dry.Types()` declaration is found.
48
- def scan(paths:)
49
- results = paths.flat_map { |path| scan_file(path) }
50
+ def scan(paths:, io_boundary:)
51
+ results = paths.flat_map { |path| scan_file(path, io_boundary) }
50
52
  modules = results.map { |r| r[:module_name] }.uniq
51
53
  return {}.freeze if modules.empty?
52
54
 
@@ -75,8 +77,8 @@ module Rigor
75
77
  end
76
78
  private_class_method :canonical_table
77
79
 
78
- def scan_file(path)
79
- source = File.read(path)
80
+ def scan_file(path, io_boundary)
81
+ source = io_boundary.read_file(path)
80
82
  parse_result = Prism.parse(source, filepath: path)
81
83
  return [] unless parse_result.errors.empty?
82
84
 
@@ -58,6 +58,7 @@ module Rigor
58
58
  class DryTypes < Rigor::Plugin::Base
59
59
  manifest(
60
60
  id: "dry-types",
61
+ target_gems: ["dry-types"],
61
62
  version: "0.1.0",
62
63
  description: "Recognises `module X; include Dry.Types(); end` and publishes the alias table.",
63
64
  produces: [:dry_type_aliases]
@@ -74,10 +75,11 @@ module Rigor
74
75
  # `watch:` covers exactly the files the scan reads: one glob per project `paths:` entry, co-extensive
75
76
  # with {#scannable_paths}. A {Cache::Descriptor::GlobEntry} digests every file matching its glob, so a
76
77
  # content edit, a file addition, and a file removal anywhere under those paths all move the digest and
77
- # invalidate the entry. The scan reads no file individually through the `IoBoundary`, so the evaluated
78
- # `watch:` globs are the entire dependency descriptor — and they fully cover the scan's input.
78
+ # invalidate the entry. Since #630 the scan ALSO reads each file through the `IoBoundary`, so the
79
+ # descriptor carries a per-file row beside the globs: the globs alone already covered the scan's
80
+ # input, and the per-file rows are what a subset or incremental run has to invalidate on.
79
81
  producer :dry_type_aliases, watch: -> { alias_watch_globs } do |_params|
80
- AliasScanner.scan(paths: scannable_paths)
82
+ AliasScanner.scan(paths: scannable_paths, io_boundary: io_boundary)
81
83
  end
82
84
 
83
85
  # Builds the (cached) alias table and publishes it via the ADR-9 fact store. `producer_value` runs the
@@ -102,11 +104,13 @@ module Rigor
102
104
  # Resolves the project's `paths:` to a flat list of `.rb` files the scanner walks. Mirrors
103
105
  # `Analysis::Runner`'s `expand_paths` floor; we don't need the runner's full exclude/sort surface
104
106
  # because the alias table is a union — any duplicate scan is a no-op.
107
+ # ADR-45 WD1b (#613 / #630) — the classification probes go through the boundary, so an entry that
108
+ # is not there yet (or stops being a directory) is a recorded dependency of the scan's input set.
105
109
  def scannable_paths
106
110
  @scannable_paths ||= services.configuration.paths.flat_map do |entry|
107
- if File.directory?(entry)
111
+ if io_boundary.directory?(entry)
108
112
  Dir.glob(File.join(entry, "**", "*.rb"), sort: true)
109
- elsif File.file?(entry) && entry.end_with?(".rb")
113
+ elsif io_boundary.file?(entry) && entry.end_with?(".rb")
110
114
  [entry]
111
115
  else
112
116
  []
@@ -122,9 +126,9 @@ module Rigor
122
126
  # would read is watched, so any edit / addition / removal under those paths invalidates the cache.
123
127
  def alias_watch_globs
124
128
  services.configuration.paths.filter_map do |entry|
125
- if File.directory?(entry)
129
+ if io_boundary.directory?(entry)
126
130
  [entry, "**/*.rb"]
127
- elsif File.file?(entry) && entry.end_with?(".rb")
131
+ elsif io_boundary.file?(entry) && entry.end_with?(".rb")
128
132
  [File.dirname(entry), File.basename(entry)]
129
133
  end
130
134
  end