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
@@ -54,6 +54,11 @@ module Rigor
54
54
  "def.override-return-widened" => :off,
55
55
  "def.override-param-narrowed" => :off,
56
56
  "def.ivar-write-mismatch" => :warning,
57
+ # Issue #1367 — both report a write its setter rejects, so it raises on every run. The type check rests on
58
+ # an inferred value type, so it follows `call.argument-type-mismatch` (warning under lenient); the
59
+ # read-only write needs no type at all, so it is an error in every profile.
60
+ "global.write-type-mismatch" => :warning,
61
+ "global.readonly-write" => :error,
57
62
  # ADR-8 companion (PHPStan IgnoreParseErrorRule-modelled): a broken suppression comment is
58
63
  # equally bad in every profile — it silently fails to do what the author believes it does — so
59
64
  # both rules stay :warning across all three profiles (including strict).
@@ -80,7 +85,12 @@ module Rigor
80
85
  # Opt-in author assertion: you only see it if you wrote a
81
86
  # `conforms-to` directive, so it stays a :warning even in
82
87
  # lenient — it is never unsolicited noise.
83
- "rbs_extended.unsatisfied-conformance" => :warning
88
+ "rbs_extended.unsatisfied-conformance" => :warning,
89
+ # ADR-112 WD5 / #1075 — an error in every profile, lenient included. The rule guards its
90
+ # false-positive rate through its narrow definition of a contradiction, and ADR-112 WD5 rules
91
+ # out lowering the severity instead; only a proven disagreement between two declarations the
92
+ # project wrote itself can fire.
93
+ "rbs.contradicting-signature" => :error
84
94
  }.freeze,
85
95
  balanced: {
86
96
  "call.undefined-method" => :error,
@@ -110,6 +120,8 @@ module Rigor
110
120
  "def.override-return-widened" => :warning,
111
121
  "def.override-param-narrowed" => :warning,
112
122
  "def.ivar-write-mismatch" => :warning,
123
+ "global.write-type-mismatch" => :error,
124
+ "global.readonly-write" => :error,
113
125
  "suppression.unknown-rule" => :warning,
114
126
  "suppression.empty" => :warning,
115
127
  "suppression.unknown-marker" => :warning,
@@ -118,7 +130,8 @@ module Rigor
118
130
  "effect.liskov-widened" => :warning,
119
131
  "effect.unknown-label" => :info,
120
132
  "effect.annotations-unchecked" => :info,
121
- "rbs_extended.unsatisfied-conformance" => :warning
133
+ "rbs_extended.unsatisfied-conformance" => :warning,
134
+ "rbs.contradicting-signature" => :error
122
135
  }.freeze,
123
136
  strict: {
124
137
  "call.undefined-method" => :error,
@@ -147,6 +160,8 @@ module Rigor
147
160
  "def.override-return-widened" => :error,
148
161
  "def.override-param-narrowed" => :error,
149
162
  "def.ivar-write-mismatch" => :error,
163
+ "global.write-type-mismatch" => :error,
164
+ "global.readonly-write" => :error,
150
165
  "suppression.unknown-rule" => :warning,
151
166
  "suppression.empty" => :warning,
152
167
  "suppression.unknown-marker" => :warning,
@@ -158,7 +173,8 @@ module Rigor
158
173
  # `:info` even under strict: a residual that failed a build would punish the project for
159
174
  # carrying an annotation it has not opted into checking, which is the opposite of the point.
160
175
  "effect.annotations-unchecked" => :info,
161
- "rbs_extended.unsatisfied-conformance" => :error
176
+ "rbs_extended.unsatisfied-conformance" => :error,
177
+ "rbs.contradicting-signature" => :error
162
178
  }.freeze
163
179
  }.freeze
164
180
 
@@ -167,20 +183,20 @@ module Rigor
167
183
  # Resolves the configured severity for a diagnostic given the active profile and any per-rule
168
184
  # overrides.
169
185
  #
170
- # @param rule [String, nil] canonical rule id (`call.undefined-method`).
171
- # @param authored_severity [Symbol] severity the rule emitted the diagnostic with (`:error`, `:warning`,
186
+ # @param rule — canonical rule id (`call.undefined-method`).
187
+ # @param authored_severity — severity the rule emitted the diagnostic with (`:error`, `:warning`,
172
188
  # `:info`).
173
- # @param profile [Symbol] one of {VALID_PROFILES}; falls back to {DEFAULT_PROFILE} for unknown values.
174
- # @param overrides [Hash{String => Symbol}] per-rule severity overrides from `.rigor.yml`'s
189
+ # @param profile — one of {VALID_PROFILES}; falls back to {DEFAULT_PROFILE} for unknown values.
190
+ # @param overrides — per-rule severity overrides from `.rigor.yml`'s
175
191
  # `severity_overrides:` map. Keys are canonical rule ids; values are {VALID_SEVERITIES} symbols.
176
192
  # Family-wildcard keys (`call`) match every rule under that prefix.
177
- # @param bleeding_edge_overrides [Hash{String => Symbol}] the severity map imposed by the active ADR-50
193
+ # @param bleeding_edge_overrides — the severity map imposed by the active ADR-50
178
194
  # § WD2 bleeding-edge features ({Rigor::BleedingEdge.severity_overrides_for}). Consulted *below* the
179
195
  # user's own `overrides` (so an explicit `severity_overrides:` entry, exact or family wildcard,
180
196
  # always wins) and *above* the profile table. Exact rule ids only — the overlay never carries family
181
197
  # wildcards. Empty while the overlay is unpopulated, so the default leaves resolution bit-for-bit
182
198
  # unchanged.
183
- # @return [Symbol] the resolved severity. Returns `:off` to mean "drop the diagnostic entirely".
199
+ # @return the resolved severity. Returns `:off` to mean "drop the diagnostic entirely".
184
200
  def resolve(rule:, authored_severity:, profile: DEFAULT_PROFILE, overrides: {}, bleeding_edge_overrides: {})
185
201
  return authored_severity if rule.nil?
186
202
 
@@ -49,6 +49,9 @@ module Rigor
49
49
  **/node_modules/**
50
50
  ].freeze
51
51
 
52
+ # The conventional test roots `test_paths: nil` auto-detects, in this order.
53
+ CONVENTIONAL_TEST_PATHS = %w[spec test].freeze
54
+
52
55
  DEFAULTS = {
53
56
  "target_ruby" => "4.0",
54
57
  "paths" => ["lib"],
@@ -57,6 +60,20 @@ module Rigor
57
60
  "disable" => [],
58
61
  "libraries" => [],
59
62
  "signature_paths" => nil,
63
+ # The project's test roots: the directories (or files) holding its tests. `nil` (the default) auto-detects
64
+ # whichever of `spec/` and `test/` exist, the same way `signature_paths: nil` auto-detects `sig/`; `[]`
65
+ # declares that the project has none. `rigor sig-gen --params=observed` harvests call-site observations
66
+ # from them when no `--observe=PATH` is given. See {#resolved_test_paths}.
67
+ "test_paths" => nil,
68
+ # ADR-112 WD4 — `rigor sig-gen` settings. `inline_declared:` decides what happens to a method the inline
69
+ # reader (`rigor-rbs-inline`) already declares from `# @rbs` / `#:`. `write` (the default) copies the
70
+ # inline declaration into `sig/`, so the generated signature is the complete contract a gem ships;
71
+ # `skip` leaves every member that reader declares out of `sig/`, for a project whose Steep reads the
72
+ # same annotations (`inline: true` beside `signature "sig"`), where the copy would be a second
73
+ # declaration of one method (`DuplicatedMethodDefinition`). See {#sig_gen_inline_declared}.
74
+ "sig_gen" => {
75
+ "inline_declared" => "write"
76
+ },
60
77
  # ADR-17 — project-side monkey-patch pre-evaluation. Empty by default; users opt in by listing explicit
61
78
  # files that the analyzer walks before per-file inference so patched-method declarations are visible
62
79
  # across the project (e.g. `lib/core_ext/string_extensions.rb`). Slice 1 plumbing only — listed files
@@ -117,6 +134,16 @@ module Rigor
117
134
  # `"stat"` (or `RIGOR_CI_DETECT=0`).
118
135
  "validation" => "auto"
119
136
  },
137
+ # ADR-39 slice 5 / #911 — the plugin-isolation strategy for target-library invocation. `nil` (the
138
+ # default) names nothing, leaving the choice to `RIGOR_PLUGIN_ISOLATION` and then to
139
+ # `Plugin::Isolation::DEFAULT`. The environment variable WINS over this key: it is the
140
+ # one-invocation operator override (`RIGOR_PLUGIN_ISOLATION=none rigor check` on a platform where
141
+ # the fork worker misbehaves), and an override a committed file can veto is not an override.
142
+ # Only `none` and `process` are selectable here — `ruby_box` needs `RUBY_BOX=1` set before Ruby
143
+ # boots, which is what `exe/rigor` re-execs for, and the launcher deliberately does not parse YAML
144
+ # before that re-exec (ADR-87 / ADR-104 boot-slim), so naming it here raises instead of silently
145
+ # resolving to a different strategy.
146
+ "plugins_isolation" => nil,
120
147
  "plugins_io" => {
121
148
  "network" => "disabled",
122
149
  "allowed_paths" => [],
@@ -192,11 +219,12 @@ module Rigor
192
219
  "auto_detect" => true
193
220
  }
194
221
  }.freeze
222
+ Ractor.make_shareable(DEFAULTS)
195
223
 
196
224
  # Top-level keys whose values are file/directory paths that MUST be resolved relative to the config
197
225
  # file's directory. `exclude:` is intentionally NOT in this list — its entries are glob patterns
198
226
  # (`**/vendor/**`), not paths.
199
- PATH_KEYS = %w[paths signature_paths pre_eval].freeze
227
+ PATH_KEYS = %w[paths signature_paths test_paths pre_eval].freeze
200
228
  private_constant :PATH_KEYS
201
229
 
202
230
  # Top-level keys this implementation DECLARES but never reads — see `docs/internal-spec/config.md`
@@ -212,6 +240,10 @@ module Rigor
212
240
  # reservation FINDABLE by the next person adding config validation (#166 is exactly that), and to
213
241
  # give the schema-parity spec something to key on: a reserved namespace is by definition absent
214
242
  # from DEFAULTS, so the DEFAULTS-driven schema gate can never see it.
243
+ # The isolation strategies a `.rigor.yml` may name (ADR-39 slice 5 / #911). `ruby_box` is
244
+ # deliberately absent: it is a boot-time decision the config file is read too late to make.
245
+ CONFIGURABLE_PLUGIN_ISOLATIONS = %w[none process].freeze
246
+
215
247
  RESERVED_NAMESPACES = %w[rigor_rs].freeze
216
248
 
217
249
  # Every top-level key a conforming `.rigor.yml` may carry: the keys this implementation owns, plus
@@ -237,7 +269,8 @@ module Rigor
237
269
 
238
270
  attr_reader :target_ruby, :paths, :exclude_patterns, :plugins, :cache_path, :cache_max_bytes,
239
271
  :cache_validation, :disabled_rules,
240
- :libraries, :signature_paths, :fold_platform_specific_paths, :parameter_inference,
272
+ :libraries, :signature_paths, :test_paths, :fold_platform_specific_paths, :parameter_inference,
273
+ :plugins_isolation,
241
274
  :plugins_io_network, :plugins_io_allowed_paths,
242
275
  :plugins_io_allowed_url_hosts,
243
276
  :severity_profile, :severity_overrides,
@@ -245,7 +278,7 @@ module Rigor
245
278
  :dependencies, :parallel_workers,
246
279
  :bundler_bundle_path, :bundler_auto_detect, :bundler_lockfile,
247
280
  :rbs_collection_lockfile, :rbs_collection_auto_detect,
248
- :pre_eval, :baseline_path, :effects,
281
+ :pre_eval, :baseline_path, :effects, :sig_gen_inline_declared,
249
282
  :effects_snapshot_path, :effects_snapshot_reach, :effects_snapshot_gate, :effects_tolerated,
250
283
  :effects_labels, :effects_attribution, :effects_envelopes
251
284
 
@@ -267,6 +300,16 @@ module Rigor
267
300
  @effects_check
268
301
  end
269
302
 
303
+ # The test roots a run uses: the declared `test_paths:` as written (already resolved against the
304
+ # config file's directory by {.load}), or — when the key is unset — whichever of
305
+ # {CONVENTIONAL_TEST_PATHS} is a directory under `root`, as absolute paths. `root` is the directory
306
+ # the run treats as the project root (the CLI's CWD), as for `signature_paths:` auto-detection.
307
+ def resolved_test_paths(root: Dir.pwd)
308
+ return test_paths unless test_paths.nil?
309
+
310
+ CONVENTIONAL_TEST_PATHS.map { |dir| File.expand_path(dir, root) }.select { |dir| File.directory?(dir) }
311
+ end
312
+
270
313
  # Loads a configuration file.
271
314
  #
272
315
  # `path == nil` triggers auto-discovery against {DISCOVERY_ORDER}. The first present file in that list is
@@ -466,7 +509,7 @@ module Rigor
466
509
 
467
510
  # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
468
511
  #
469
- # @param effects_key_present [Boolean] ADR-103 WD15 — whether the file this `data` came from carried an
512
+ # @param effects_key_present — ADR-103 WD15 — whether the file this `data` came from carried an
470
513
  # `effects:` key at all, independent of its value. Defaults to `data.key?("effects")`, which is the
471
514
  # right answer for a caller that passes a raw (non-`DEFAULTS`-merged) hash directly — the common shape
472
515
  # in specs — but is always `true` once `data` has been through `DEFAULTS.merge`, because `DEFAULTS`
@@ -494,6 +537,9 @@ module Rigor
494
537
  @libraries = Array(data.fetch("libraries", DEFAULTS.fetch("libraries"))).map(&:to_s).freeze
495
538
  sig_paths = data.fetch("signature_paths", DEFAULTS.fetch("signature_paths"))
496
539
  @signature_paths = sig_paths.nil? ? nil : Array(sig_paths).map(&:to_s).freeze
540
+ test_paths = data.fetch("test_paths", DEFAULTS.fetch("test_paths"))
541
+ @test_paths = test_paths.nil? ? nil : Array(test_paths).map(&:to_s).freeze
542
+ @sig_gen_inline_declared = coerce_sig_gen_inline_declared(data.fetch("sig_gen", nil))
497
543
  @pre_eval = expand_pre_eval_entries(
498
544
  Array(data.fetch("pre_eval", DEFAULTS.fetch("pre_eval"))).map(&:to_s)
499
545
  )
@@ -520,6 +566,9 @@ module Rigor
520
566
  raw_max = cache.fetch("max_bytes")
521
567
  @cache_max_bytes = raw_max.nil? ? nil : Integer(raw_max)
522
568
  @cache_validation = coerce_cache_validation(cache.fetch("validation", "auto"))
569
+ @plugins_isolation = coerce_plugins_isolation(
570
+ data.fetch("plugins_isolation", DEFAULTS.fetch("plugins_isolation"))
571
+ )
523
572
  @plugins_io_network = coerce_network_policy(plugins_io.fetch("network"))
524
573
  @plugins_io_allowed_paths = Array(plugins_io.fetch("allowed_paths")).map(&:to_s).freeze
525
574
  @plugins_io_allowed_url_hosts = Array(plugins_io.fetch("allowed_url_hosts")).map(&:to_s).freeze
@@ -575,6 +624,9 @@ module Rigor
575
624
  "disable" => disabled_rules,
576
625
  "libraries" => libraries,
577
626
  "signature_paths" => signature_paths,
627
+ # `test_paths:` is deliberately absent, as `effects:` is: `to_h` feeds the run cache key and the
628
+ # incremental-snapshot fingerprint, and the test roots change no diagnostic, so editing them must not
629
+ # invalidate either. `sig_gen:` is absent for the same reason: only `rigor sig-gen` reads it.
578
630
  "pre_eval" => pre_eval,
579
631
  "fold_platform_specific_paths" => fold_platform_specific_paths,
580
632
  "parameter_inference" => parameter_inference,
@@ -583,6 +635,7 @@ module Rigor
583
635
  "max_bytes" => cache_max_bytes,
584
636
  "validation" => cache_validation
585
637
  },
638
+ "plugins_isolation" => plugins_isolation,
586
639
  "plugins_io" => {
587
640
  "network" => plugins_io_network.to_s,
588
641
  "allowed_paths" => plugins_io_allowed_paths,
@@ -626,9 +679,8 @@ module Rigor
626
679
  # the same checkout: a typo is a bug, and reading it as `false` would ship the feature permanently off
627
680
  # with no signal.
628
681
  #
629
- # @param id [String] a feature id from {BleedingEdge::FEATURES} or {BleedingEdge::GRADUATED}.
630
- # @return [Boolean]
631
- # @raise [ArgumentError] if `id` names no known feature.
682
+ # @param id — a feature id from {BleedingEdge::FEATURES} or {BleedingEdge::GRADUATED}.
683
+ # @raise ArgumentError — if `id` names no known feature.
632
684
  def bleeding_edge_active?(id)
633
685
  return true if BleedingEdge.graduated?(id)
634
686
  unless BleedingEdge.known_id?(id)
@@ -962,11 +1014,61 @@ module Rigor
962
1014
  VALID_CACHE_VALIDATIONS = %w[auto stat digest].freeze
963
1015
  private_constant :VALID_CACHE_VALIDATIONS
964
1016
 
1017
+ # The `sig_gen.inline_declared:` values, as the Symbols {#sig_gen_inline_declared} answers.
1018
+ VALID_SIG_GEN_INLINE_DECLARED = %w[write skip].freeze
1019
+ private_constant :VALID_SIG_GEN_INLINE_DECLARED
1020
+
1021
+ # Rejects rather than fails soft: an unrecognised value read as `write` would put a second declaration of
1022
+ # every inline-annotated method into `sig/` for exactly the project that asked for none. A `sig_gen:`
1023
+ # block that omits the key, or is written empty, keeps the default.
1024
+ def coerce_sig_gen_inline_declared(section)
1025
+ section = {} if section.nil?
1026
+ raise ConfigurationError, "sig_gen must be a mapping, got #{section.inspect}" unless section.is_a?(Hash)
1027
+
1028
+ # The schema closes this object (`additionalProperties: false`); a misspelled key here would otherwise
1029
+ # load as the default and write exactly what `inline_declare: skip` was meant to prevent.
1030
+ unknown = section.keys.map(&:to_s) - DEFAULTS.fetch("sig_gen").keys
1031
+ unless unknown.empty?
1032
+ raise ConfigurationError,
1033
+ "sig_gen has unknown key(s) #{unknown.inspect}; known keys: #{DEFAULTS.fetch('sig_gen').keys.inspect}"
1034
+ end
1035
+
1036
+ value = section.fetch("inline_declared", DEFAULTS.dig("sig_gen", "inline_declared")).to_s
1037
+ unless VALID_SIG_GEN_INLINE_DECLARED.include?(value)
1038
+ raise ConfigurationError,
1039
+ "sig_gen.inline_declared must be one of #{VALID_SIG_GEN_INLINE_DECLARED.inspect}, " \
1040
+ "got #{section['inline_declared'].inspect}"
1041
+ end
1042
+
1043
+ value.to_sym
1044
+ end
1045
+
965
1046
  def coerce_cache_validation(value)
966
1047
  str = value.to_s
967
1048
  VALID_CACHE_VALIDATIONS.include?(str) ? str : "auto"
968
1049
  end
969
1050
 
1051
+ # Rejects rather than fails soft — a strategy that silently resolves to a different one is exactly
1052
+ # the failure #911 exists to close, and the run's isolation is not a detail the user can be assumed
1053
+ # not to have meant.
1054
+ def coerce_plugins_isolation(value)
1055
+ return nil if value.nil?
1056
+
1057
+ str = value.to_s
1058
+ return str if CONFIGURABLE_PLUGIN_ISOLATIONS.include?(str)
1059
+
1060
+ if str == "ruby_box"
1061
+ raise ConfigurationError,
1062
+ "plugins_isolation: ruby_box cannot be selected from the configuration file — `Ruby::Box` " \
1063
+ "must be active before Ruby boots. Run rigor with RIGOR_PLUGIN_ISOLATION=ruby_box instead " \
1064
+ "(the launcher re-execs itself with RUBY_BOX=1 set)."
1065
+ end
1066
+
1067
+ raise ConfigurationError,
1068
+ "plugins_isolation must be one of #{CONFIGURABLE_PLUGIN_ISOLATIONS.inspect} " \
1069
+ "(`ruby_box` is environment-only), got #{value.inspect}"
1070
+ end
1071
+
970
1072
  def coerce_network_policy(value)
971
1073
  sym = value.to_sym
972
1074
  unless VALID_NETWORK_POLICIES.include?(sym)
@@ -0,0 +1,191 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "prism"
4
+
5
+ require_relative "../source/constant_path"
6
+ require_relative "file_collection"
7
+
8
+ module Rigor
9
+ module Effects
10
+ # The ancestry half of a file's {FileCollection}: what each class declares above itself, what it
11
+ # includes, and where the scan has to admit it could not read the ancestry at all.
12
+ #
13
+ # Split out of {Scanner} because the three questions are one subject with one invariant — a name goes
14
+ # in **as written**, and the propagator, which is the only place the whole project is in view, decides
15
+ # which constant it meant. The scanner owns unit identity and delegates this; the two tables are handed
16
+ # back by {#superclasses} / {#includes} and go into the collection unchanged.
17
+ class AncestryRecorder
18
+ attr_reader :superclasses, :includes
19
+
20
+ def initialize
21
+ @superclasses = {}
22
+ @includes = {}
23
+ end
24
+
25
+ # `class Loud < Base` inside `module Tracer` names `Base`, so the candidates go in and the propagator
26
+ # picks the one the merged project defines.
27
+ #
28
+ # A superclass expression that is **not** a constant path — `class K < Struct.new(:a)`,
29
+ # `< Data.define(:a)`, `< DelegateClass(X)`, `< Sequel::Model(:t)` — records
30
+ # {FileCollection::OPAQUE_ANCESTOR} rather than nothing (#1039). Recording nothing made it
31
+ # indistinguishable from a class with no `<` at all, which is the one reading the constructor rule
32
+ # turns into an answer; such a class inherits a constructor built at load time, which is the
33
+ # opposite of an absent one.
34
+ def record_superclass(full_name, node, prefix)
35
+ return if node.superclass.nil? || opaque?(full_name)
36
+
37
+ superclass = Source::ConstantPath.qualified_name(node.superclass)
38
+ @superclasses[full_name] =
39
+ superclass ? lexical_candidates(superclass, prefix) : [FileCollection::OPAQUE_ANCESTOR]
40
+ end
41
+
42
+ # A class BUILT AT LOAD TIME and assigned to a constant — `Anon = Class.new(Base)`,
43
+ # `Point = Struct.new(:x)`, `Rec = Data.define(:x)`, `Dele = DelegateClass(X)` — records the opaque
44
+ # sentinel (#1039). Without it a later `class Point; def more; end; end` reopening is the only thing
45
+ # the scan sees: the class becomes project-known, nothing says what is above it, and the constructor
46
+ # rule would read that silence as "no constructor anywhere" when the constructor is precisely what
47
+ # the load-time call built. It is the `class K < Struct.new(:a)` case in its other spelling.
48
+ #
49
+ # Only a **call**-valued assignment qualifies. A literal cannot be a class, and a constant-path value
50
+ # (`Alias = Real`) is a second name for a class whose own ancestry the scan already recorded.
51
+ #
52
+ # The sentinel is **sticky**: it overwrites a spelled `< Base` and no later one displaces it, here or
53
+ # in {FileCollection.merge_all}. A reopening's `class Anon < Base` is not more than this says but
54
+ # less — `Anon = Class.new(Base) { def initialize; … end }` puts a constructor in the block, which the
55
+ # scan files under the enclosing namespace and cannot attribute to `Anon` at all, and the spelled
56
+ # superclass says nothing about it. Sticky is also what makes the answer independent of which file a
57
+ # run reads first.
58
+ #
59
+ # `Class.new(Base)` additionally keeps `Base` beside the sentinel. The sentinel still declines
60
+ # `Anon.new` itself, and the parent link is what files `Anon` under `Base` in the subclass index, so
61
+ # a `self.class.new` in `Base` — which the closed-world join says may construct `Anon` — sees an
62
+ # unreadable constructor below it and declines too.
63
+ def record_constant_class(node, prefix)
64
+ value = node.value
65
+ return unless value.is_a?(Prism::CallNode)
66
+
67
+ name = constant_write_name(node, prefix)
68
+ return if name.nil?
69
+
70
+ @superclasses[name] = [FileCollection::OPAQUE_ANCESTOR, *load_time_parent(value, prefix)]
71
+ end
72
+
73
+ # A receiver-less `include` / `prepend` in `class_name`'s body, its constant arguments recorded as
74
+ # written.
75
+ #
76
+ # Both are calls on `self`, like `define_method`, and the include table is the instance ancestry that
77
+ # `super` and the constructor rule walk, so a call is recorded only where `self` is the class the body
78
+ # opened (see {#instance_side_self?}). Inside `class << self`, or a `class_eval` / `instance_eval`
79
+ # block on `singleton_class`, it mixes the module into the singleton class, which is what `extend`
80
+ # does, and the collection is as blind to it as it is to `extend`.
81
+ #
82
+ # @param context — the {DefinitionContext} of the class-body position the call sits at
83
+ def record_includes(class_name, node, prefix, context)
84
+ return unless instance_side_self?(context)
85
+
86
+ names = node.arguments&.arguments&.filter_map { |argument| Source::ConstantPath.qualified_name(argument) }
87
+ candidates = (names || []).flat_map { |name| lexical_candidates(name, prefix) }
88
+ (@includes[class_name] ||= []).concat(candidates) unless candidates.empty?
89
+ end
90
+
91
+ # `alias initialize setup` / `alias_method :initialize, :setup` makes the constructor another
92
+ # method's body, and the scan models no aliases at all (#1039). The opaque sentinel is the minimal
93
+ # honest answer: the ancestry stops being readable, the constructor rule declines, and this class's
94
+ # callers stay exactly as unclaimed as they were before that rule existed.
95
+ def record_initialize_alias(class_name)
96
+ (@includes[class_name] ||= []) << FileCollection::OPAQUE_ANCESTOR
97
+ end
98
+
99
+ # Whether this node aliases the instance-side `initialize`, in either spelling. A `CallNode` qualifies
100
+ # only as a receiver-less `alias_method` whose first symbol argument is the new name.
101
+ #
102
+ # `alias` works on the default definee and `alias_method` on `self`, so the two part in an
103
+ # `instance_eval` block, and inside `class << self` both alias the singleton class's `initialize`,
104
+ # a class method `new` never calls. Where the syntax does not say which class either one is, it
105
+ # records nothing here, as an include there does not (see {#instance_side_self?}).
106
+ #
107
+ # @param context — the {DefinitionContext} of the class-body position the node sits at
108
+ def alias_to_initialize?(node, context)
109
+ case node
110
+ when Prism::AliasMethodNode
111
+ context.definee_singleton == false && literal_name(node.new_name) == "initialize"
112
+ when Prism::CallNode
113
+ node.receiver.nil? && node.name == :alias_method && instance_side_self?(context) &&
114
+ literal_name(first_argument(node)) == "initialize"
115
+ else false
116
+ end
117
+ end
118
+
119
+ # An ancestry name is recorded AS WRITTEN — a single file cannot say which constant it resolves to.
120
+ # So the candidates Ruby's own lexical lookup would try go in, most-qualified first, and the
121
+ # propagator picks the one the merged project actually defines. Same shape as `ScopeIndexer`'s
122
+ # as-written superclass table, resolved at the same point: when the whole project is in view.
123
+ def lexical_candidates(name, prefix)
124
+ return [name] if prefix.empty? || name.start_with?("#{prefix.join('::')}::")
125
+
126
+ prefix.length.downto(1).map { |depth| "#{prefix.first(depth).join('::')}::#{name}" } + [name]
127
+ end
128
+
129
+ private
130
+
131
+ # Whether the syntax shows that a call on `self` written here works on the class the enclosing body
132
+ # opened, or on `Object` through `main` at the top level, so what it declares is that class's instance
133
+ # ancestry.
134
+ #
135
+ # It does not where `self` is the singleton class, nor where the syntax does not say what `self` is:
136
+ # the block of `Class.new` and its kin, an eval on another receiver, `class << obj`. The syntax does not
137
+ # name the class the module goes into there, and filing it under the enclosing class would put it
138
+ # where `super` and the constructor rule look for that class. The class Ruby does put it in misses it,
139
+ # as it would an `extend`, and so does `W` for a `W.class_eval { include M }` inside `class W`, which
140
+ # the context reads as an eval on any other receiver (#1322).
141
+ def instance_side_self?(context)
142
+ !context.self_singleton_class? && context.self_kind != :unknown
143
+ end
144
+
145
+ def opaque?(full_name)
146
+ @superclasses.fetch(full_name, []).include?(FileCollection::OPAQUE_ANCESTOR)
147
+ end
148
+
149
+ # The superclass a `Class.new(Base)` names, as candidates. Read from `Class.new` alone: it is the one
150
+ # load-time builder whose first argument IS the superclass, and inventing an ancestry edge from any
151
+ # other call's first constant argument would let an unrelated class's methods resolve through it.
152
+ def load_time_parent(value, prefix)
153
+ return [] unless value.name == :new && constant_receiver_name(value.receiver) == "Class"
154
+
155
+ argument = value.arguments&.arguments&.first
156
+ name = argument && Source::ConstantPath.qualified_name(argument)
157
+ name ? lexical_candidates(name, prefix) : []
158
+ end
159
+
160
+ def constant_receiver_name(receiver)
161
+ Source::ConstantPath.qualified_name(receiver) if receiver.is_a?(Prism::ConstantReadNode)
162
+ end
163
+
164
+ # `alias_method :initialize, :setup` and `alias_method "initialize", "setup"` are the same
165
+ # declaration; only an interpolated name is beyond the scan.
166
+ def literal_name(node)
167
+ node.unescaped if node.is_a?(Prism::SymbolNode) || node.is_a?(Prism::StringNode)
168
+ end
169
+
170
+ # The key a constant assignment declares, qualified by the nesting it is written in.
171
+ def constant_write_name(node, prefix)
172
+ case node
173
+ when Prism::ConstantWriteNode then [*prefix, node.name.to_s].join("::")
174
+ when Prism::ConstantPathWriteNode then qualified_write_name(node, prefix)
175
+ end
176
+ end
177
+
178
+ def qualified_write_name(node, prefix)
179
+ target = Source::ConstantPath.qualified_name(node.target)
180
+ return nil if target.nil?
181
+ return target if prefix.empty? || target.start_with?("#{prefix.join('::')}::")
182
+
183
+ [*prefix, target].join("::")
184
+ end
185
+
186
+ def first_argument(node)
187
+ node.arguments&.arguments&.first
188
+ end
189
+ end
190
+ end
191
+ end
@@ -26,11 +26,15 @@ module Rigor
26
26
  EMPTY_ROWS = {}.freeze
27
27
  private_constant :EMPTY_ROWS
28
28
 
29
- def self.empty
30
- @empty ||= new({})
29
+ # The run's empty table. Eagerly built on the main Ractor at load time (bottom of the class body)
30
+ # rather than memoised on first use: `@empty ||= new({})` is a class-ivar WRITE, which a non-main
31
+ # Ractor may not perform, and every project that configures no attribution reaches it — including
32
+ # from inside {Analysis::WorkerSession#initialize}, which is where a Ractor-pool worker runs (#1055).
33
+ class << self
34
+ attr_reader :empty
31
35
  end
32
36
 
33
- # @param table [Hash{String => Array<String>}] `Configuration#effects_attribution` — method key to
37
+ # @param table — `Configuration#effects_attribution` — method key to
34
38
  # label list, both already shape-validated at load (tier 2).
35
39
  def self.build(table)
36
40
  return empty if table.nil? || table.empty?
@@ -49,8 +53,6 @@ module Rigor
49
53
 
50
54
  # The labels attributed to a method key (`Net::HTTP.get`), or nil when the table says nothing about
51
55
  # it. The key is spelled by the caller, which already builds the same string for the catalogue.
52
- #
53
- # @return [LabelSet, nil]
54
56
  def [](key)
55
57
  @rows[key]
56
58
  end
@@ -71,6 +73,11 @@ module Rigor
71
73
  out[key.to_s] = set unless set.empty?
72
74
  end.freeze
73
75
  end
76
+
77
+ # Populates the `@empty` singleton on the main Ractor at load time. `Ractor.make_shareable` rather
78
+ # than `freeze` so a worker's READ of the class ivar is legal too: a non-main Ractor may read a
79
+ # class/module ivar only when the value is deeply shareable.
80
+ @empty = Ractor.make_shareable(new({}))
74
81
  end
75
82
  end
76
83
  end