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
@@ -20,9 +20,9 @@ module Rigor
20
20
  @project_context = project_context
21
21
  end
22
22
 
23
- # @param positions [Array<Hash>] LSP `Position[]` — each
23
+ # @param positions — LSP `Position[]` — each
24
24
  # `{ line:, character: }` 0-based.
25
- # @return [Array<Hash>, nil] one `SelectionRange` per
25
+ # @return one `SelectionRange` per
26
26
  # position, or nil when the URI / buffer isn't resolvable.
27
27
  def provide(uri, positions)
28
28
  path, entry = buffer_for(uri)
@@ -42,11 +42,11 @@ module Rigor
42
42
  :signature_help_provider, :folding_range_provider,
43
43
  :selection_range_provider, :project_context
44
44
 
45
- # @param completion_provider [Rigor::LanguageServer::CompletionProvider, nil]
45
+ # @param completion_provider —
46
46
  # resolves `textDocument/completion`. Nil → `MethodNotFound`.
47
- # @param signature_help_provider [Rigor::LanguageServer::SignatureHelpProvider, nil]
47
+ # @param signature_help_provider —
48
48
  # resolves `textDocument/signatureHelp`. Nil → `MethodNotFound`.
49
- # @param project_context [Rigor::LanguageServer::ProjectContext, nil] the per-session cache of
49
+ # @param project_context — the per-session cache of
50
50
  # `Environment` + `Cache::Store` the providers read on every request. When present,
51
51
  # `workspace/didChangeWatchedFiles` and `workspace/didChangeConfiguration` invalidate the cache; nil
52
52
  # means "no project context": each request rebuilds env from scratch (mainly for specs and backward
@@ -69,7 +69,7 @@ module Rigor
69
69
  @project_context = project_context
70
70
  end
71
71
 
72
- # @return [Boolean] true once the client has called `exit` and
72
+ # @return true once the client has called `exit` and
73
73
  # the server has set its terminal exit code. The CLI loop
74
74
  # reads this between dispatches to know when to stop.
75
75
  def exited?
@@ -78,10 +78,10 @@ module Rigor
78
78
 
79
79
  # Routes one LSP method call.
80
80
  #
81
- # @param method [String] the LSP method name (e.g. "initialize").
82
- # @param params [Hash, nil] the LSP `params` payload (Hash for
81
+ # @param method — the LSP method name (e.g. "initialize").
82
+ # @param params — the LSP `params` payload (Hash for
83
83
  # request / notification methods; nil for the empty case).
84
- # @return [Hash, nil] one of:
84
+ # @return one of:
85
85
  # - the response result Hash for request methods,
86
86
  # - nil for notification methods,
87
87
  # - { error: { code:, message: } } for state / shape errors.
@@ -191,8 +191,8 @@ module Rigor
191
191
  caps
192
192
  end
193
193
 
194
- # `initialized` is a notification — no response body. Slice 7 will hook this to register
195
- # `workspace/didChangeWatchedFiles` if the client advertised the capability.
194
+ # `initialized` is a notification — no response body. Nothing is registered here yet: the server handles
195
+ # `workspace/didChangeWatchedFiles` when a client sends it, but does not register for it dynamically.
196
196
  def handle_initialized
197
197
  nil
198
198
  end
@@ -44,7 +44,7 @@ module Rigor
44
44
  @project_context = project_context
45
45
  end
46
46
 
47
- # @return [Hash, nil] LSP `SignatureHelp` payload or nil
47
+ # @return LSP `SignatureHelp` payload or nil
48
48
  # when the cursor isn't inside a resolvable method call.
49
49
  def provide(uri:, line:, character:, context: nil)
50
50
  _ = context # Trigger info accepted but not routed in v1.
@@ -14,7 +14,7 @@ module Rigor
14
14
  FILE_SCHEME = "file://"
15
15
  private_constant :FILE_SCHEME
16
16
 
17
- # @return [String, nil] absolute filesystem path for a
17
+ # @return absolute filesystem path for a
18
18
  # `file://` URI, or nil for unsupported schemes.
19
19
  def to_path(uri)
20
20
  return nil unless uri.is_a?(String) && uri.start_with?(FILE_SCHEME)
@@ -94,7 +94,7 @@ module Rigor
94
94
  type: "string",
95
95
  enum: %w[untyped observed],
96
96
  description: "Parameter policy: untyped (default) or observed " \
97
- "(harvests call-site argument types from spec/)"
97
+ "(harvests call-site argument types from the configured test_paths)"
98
98
  },
99
99
  config: { type: "string", description: "Path to .rigor.yml (optional)" }
100
100
  }
@@ -134,6 +134,7 @@ module Rigor
134
134
  }
135
135
  }
136
136
  ].freeze
137
+ Ractor.make_shareable(TOOLS)
137
138
 
138
139
  def initialize(config_path: nil, err: $stderr)
139
140
  @config_path = config_path
@@ -8,6 +8,7 @@ require "prism"
8
8
  require_relative "manifest"
9
9
  require_relative "node_context"
10
10
  require_relative "../analysis/diagnostic"
11
+ require_relative "../cache/engine_source"
11
12
  # `producer generation_cap:` defaults to (and validates against) `Cache::Store::UNBOUNDED_GENERATIONS`, and a
12
13
  # plugin class body can be evaluated before anything else pulled the cache layer in.
13
14
  require_relative "../cache/store"
@@ -40,6 +41,13 @@ module Rigor
40
41
  #
41
42
  # Rigor::Plugin.register(MyRailsPlugin)
42
43
  class Base # rubocop:disable Metrics/ClassLength
44
+ # Issue #701 — the kind wrapper a `dynamic_return receivers:` entry may carry, spelled as RBS spells a
45
+ # class object and as the `call.undefined-method` message prints one, so an author writes the gate the
46
+ # way the diagnostic they are answering names the receiver. Declared on the class rather than inside
47
+ # `class << self` because both the load-time validator and the per-dispatch gate read it.
48
+ SINGLETON_RECEIVER_PATTERN = /\Asingleton\((.+)\)\z/
49
+ private_constant :SINGLETON_RECEIVER_PATTERN
50
+
43
51
  class << self
44
52
  # Declares the plugin's manifest. Called once at class definition time — the resulting {Manifest} is
45
53
  # cached on the class so {Rigor::Plugin::Loader} reads it without constructing the plugin.
@@ -241,8 +249,21 @@ module Rigor
241
249
  # end
242
250
  #
243
251
  # `receivers:` is a non-empty Array of class names; the engine calls the block only when the call's
244
- # receiver type's class equals or inherits from one of them (via `Environment#class_ordering`). It
245
- # MAY be omitted — then the rule is receiver-independent and fires on `methods:` alone.
252
+ # receiver type's class equals or inherits from one of them (via `Environment#class_ordering`) AND
253
+ # is of the kind the entry names. It MAY be omitted — then the rule is receiver-independent and
254
+ # fires on `methods:` alone.
255
+ #
256
+ # Issue #701 — an entry carries the receiver KIND in RBS's own spelling. `"Widget"` matches an
257
+ # INSTANCE receiver; `"singleton(Widget)"` matches the class object itself; a rule that wants both
258
+ # lists both:
259
+ #
260
+ # dynamic_return receivers: ["Widget"], methods: [:price] # Widget#price
261
+ # dynamic_return receivers: ["singleton(Widget)"], methods: [:build] # Widget.build
262
+ # dynamic_return receivers: ["Widget", "singleton(Widget)"] # either
263
+ #
264
+ # An instance rule that also answered on the class typed a call the plugin never modelled, and since
265
+ # #653 a plugin's answer suppresses `call.undefined-method` — so the class-level miss went silent on
266
+ # the say-so of a rule written for the instance.
246
267
  #
247
268
  # `methods:` is an Array of Symbol method names. When provided, the block is skipped unless
248
269
  # `call_node.name` is in the list — declarative and cheaper than an in-block guard (the engine
@@ -261,6 +282,9 @@ module Rigor
261
282
  # # found; the block does the precise per-call lookup
262
283
  # end
263
284
  #
285
+ # A callable's entries carry the kind the same way — `-> { names.map { "singleton(#{_1})" } }` for a
286
+ # discovered set of class-level receivers.
287
+ #
264
288
  # The callable runs through `instance_exec`, so it reads the plugin's own `#prepare`-built indexes.
265
289
  # It MUST be idempotent and post-`#prepare`-safe — reference a lazily-built / memoised index (as
266
290
  # activestorage's `attachment_index` and activerecord's `model_index` are), never a value captured at
@@ -376,11 +400,23 @@ module Rigor
376
400
  # ADR-52 slice 3 — a run-time callable is resolved per instance after `#prepare`; its shape is
377
401
  # checked at resolution time.
378
402
  return if receivers.respond_to?(:call)
379
- return if receivers.is_a?(Array) && !receivers.empty? && receivers.all? { |r| r.is_a?(String) && !r.empty? }
403
+ return if receivers.is_a?(Array) && !receivers.empty? &&
404
+ receivers.all? { |r| valid_dynamic_return_receiver?(r) }
380
405
 
381
406
  raise ArgumentError,
382
407
  "Plugin::Base.dynamic_return receivers: must be a non-empty Array of class-name Strings " \
383
- "or a callable, got #{receivers.inspect}"
408
+ "(\"Foo\" for an instance receiver, \"singleton(Foo)\" for the class itself) or a " \
409
+ "callable, got #{receivers.inspect}"
410
+ end
411
+
412
+ # Issue #701 — the entry grammar: a non-empty class name, optionally wrapped in RBS's own
413
+ # `singleton(...)`. A bare `singleton()` is rejected rather than read as a class literally named
414
+ # that, so a typo in the kind wrapper fails at load instead of never matching.
415
+ def valid_dynamic_return_receiver?(entry)
416
+ return false unless entry.is_a?(String) && !entry.empty?
417
+ return SINGLETON_RECEIVER_PATTERN.match?(entry) if entry.start_with?("singleton(")
418
+
419
+ true
384
420
  end
385
421
 
386
422
  def validate_dynamic_return_methods!(methods)
@@ -397,7 +433,8 @@ module Rigor
397
433
  end
398
434
 
399
435
  private :validate_dynamic_return_gate!, :validate_dynamic_return_receivers!,
400
- :validate_dynamic_return_methods!, :validate_dynamic_return_file_methods!
436
+ :validate_dynamic_return_methods!, :validate_dynamic_return_file_methods!,
437
+ :valid_dynamic_return_receiver?
401
438
 
402
439
  # ADR-37 slice 2 — declares a predicate/assertion narrowing contribution, method-gated. The narrow
403
440
  # successor to the `post_return_facts` slot of the deleted `flow_contribution_for` hook (ADR-52 WD3):
@@ -456,6 +493,10 @@ module Rigor
456
493
  @fact_cache = {}
457
494
  @producer_value_cache = {}
458
495
  @producer_errors = {}
496
+ # Issue #1051 — the run-scoped disclosure table ({#disclose_once}, and since #1060 {#emit_once}).
497
+ # Allocated here for the same reason as the three memos above: a registration is a Hash-content
498
+ # mutation, so it stays sound on a plugin that freezes itself in `initialize`.
499
+ @run_disclosures = {}
459
500
  end
460
501
 
461
502
  # Override in subclasses to wire any state the plugin needs from the injected service container.
@@ -509,6 +550,38 @@ module Rigor
509
550
  []
510
551
  end
511
552
 
553
+ # #392 — the **template-unit** transform: compile one non-Ruby project file into the Ruby the engine
554
+ # analyses (ADR-16 Tier D, revived; design note § 11.3). The engine globs the manifest's
555
+ # `template_globs:`, reads each match's bytes, and calls this once per match on the PARENT, before any
556
+ # analysis — so the transform runs exactly once per file per run and its result marshals into every
557
+ # fork-pool worker.
558
+ #
559
+ # Returns an `Array<Rigor::Plugin::TemplateUnit>`; an empty array declines the file, which is the
560
+ # right answer for a match the plugin's glob caught but its compiler cannot read. The default returns
561
+ # `[]`, so a plugin that declares no `template_globs:` is never asked and one that does may still
562
+ # decline per file.
563
+ #
564
+ # A raise isolates exactly like `#diagnostics_for_file`: the file contributes no unit and the run
565
+ # continues. The hook is the one place a template-unit plugin runs code — everything downstream is
566
+ # the frozen value object — which is what keeps the engine's analysis of a unit identical to its
567
+ # analysis of a `.rb` file.
568
+ def template_units_for_file(path:, source:) # rubocop:disable Lint/UnusedMethodArgument
569
+ []
570
+ end
571
+
572
+ # #1047 — called once at the start of every template-unit COLLECTION PASS over this plugin's claim,
573
+ # before any `#template_units_for_file` of that pass and whether or not the pass ends up calling it
574
+ # (a warm pass may carry every unit and compile none). A plugin whose transform reads state gathered
575
+ # ACROSS its claimed files — rigor-actionpack seeds a partial's locals from the render sites in every
576
+ # view — memoises that state on its instance, and a long-lived owner keeps the instance across
577
+ # passes; this is where such a plugin learns that the memo may now be stale. The collector's
578
+ # per-call order is not a substitute: a warm pass offers only the editor's buffer, so no sequence of
579
+ # paths can tell one pass from the next.
580
+ #
581
+ # The default does nothing. A raise is swallowed: the pass continues, and the plugin's own
582
+ # revalidation simply does not run for it.
583
+ def template_units_pass_started; end
584
+
512
585
  # ADR-37 slice 1 — runs the plugin's declared {.node_rule}s over one file and returns their
513
586
  # diagnostics. The engine owns the single AST walk here so plugin authors never hand-roll a traversal:
514
587
  # every node reachable from `root` is offered to each rule whose `node_type` it satisfies
@@ -545,19 +618,15 @@ module Rigor
545
618
 
546
619
  # ADR-37 slice 2 — the return type contributed by this plugin's {.dynamic_return} rules for a call, or
547
620
  # nil. The engine calls this from `MethodDispatcher`; a rule fires only when `receiver_type`'s class
548
- # equals or inherits from one of its declared `receivers:`. First non-nil wins (declaration order).
549
- # Failures isolate to nil.
621
+ # equals or inherits from one of its declared `receivers:` AND is of that entry's kind (#701). First
622
+ # non-nil wins (declaration order). Failures isolate to nil.
550
623
  def dynamic_return_type(call_node:, scope:, receiver_type:)
551
624
  rules = self.class.dynamic_returns
552
625
  return nil if rules.empty? || receiver_type.nil?
553
626
 
554
- # `class_name` is nil for a receiver carrier with no nominal class (a refinement dimension, an
555
- # inferred shape) — fine for a receiver-less (methods-only) rule (ADR-52 WD2), which gates on the
556
- # method name alone and reads the receiver shape inside its own block.
557
- class_name = dynamic_return_receiver_class_name(receiver_type)
558
627
  environment = scope&.environment
559
628
  rules.each do |rule|
560
- next unless dynamic_return_rule_applies?(rule, call_node, class_name, environment, scope)
629
+ next unless dynamic_return_rule_applies?(rule, call_node, receiver_type, environment, scope)
561
630
 
562
631
  result = instance_exec(call_node, scope, &rule[:block])
563
632
  return result if result
@@ -587,6 +656,27 @@ module Rigor
587
656
  []
588
657
  end
589
658
 
659
+ # ADR-113 WD4 (#1082) — enumerates the members this plugin synthesizes on `class_name`, for the
660
+ # `rigor lens` declaration map. A `dynamic_return` rule answers a TYPE for one call site at a time;
661
+ # nothing on that path can say "`User` has `email`, `name`, …", which is the fact grep can never
662
+ # find on a DSL-generated member. This hook is that per-class enumeration.
663
+ #
664
+ # Returns an `Array` of `{name:, kind:, type:}` Hashes. `name` is the member name as a caller spells
665
+ # it (String); `kind` is a Symbol grouping same-shaped members (`:column_reader`) — the lens
666
+ # collapses a group into one line; `type` is the `Rigor::Type` the plugin commits to for the member
667
+ # itself, or nil where it does not commit to one. It is the member-level answer, not the best a
668
+ # typed call site can do: a plugin that narrows `user.name` to `String` on a written receiver but
669
+ # declines to type the bare `name` read reports `Dynamic[top]` here. A member the plugin claims
670
+ # but deliberately leaves dynamic carries `Rigor::Type::Combinator.untyped` — the explicit
671
+ # `Dynamic[top]` answer, distinct from nil's "no answer".
672
+ #
673
+ # The hook is off the `check` hot path (ADR-52): `rigor lens` invokes it, `check` never does. It may
674
+ # read state `#prepare` built. The default returns `[]` — a plugin that synthesizes no enumerable
675
+ # members has nothing to declare.
676
+ def declared_members(class_name) # rubocop:disable Lint/UnusedMethodArgument
677
+ []
678
+ end
679
+
590
680
  # Builds a `Rigor::Analysis::Diagnostic` positioned at a Prism `node` for return from
591
681
  # `#diagnostics_for_file`. Internalises the 1-based `line` / `start_column + 1` convention every plugin
592
682
  # otherwise re-derives by hand, so authors pass the node and the message/severity/rule rather than
@@ -654,12 +744,108 @@ module Rigor
654
744
  end
655
745
 
656
746
  # ADR-60 WD4 — the `StandardError` a prior {#producer_value} call rescued for `id`, or nil when it
657
- # succeeded or was never called. Plugins surface it as a load-error diagnostic from
658
- # `#diagnostics_for_file`.
747
+ # succeeded or was never called. Plugins surface it as a run-scoped `load-error` disclosure through
748
+ # {#disclose_once} (#1056) — a failed index is a fact about the run's inputs, not about a file.
659
749
  def producer_error(id)
660
750
  @producer_errors[id.to_sym]
661
751
  end
662
752
 
753
+ # Issue #1051 — records a PROJECT-GLOBAL disclosure: a notice about the run's inputs ("the schema file
754
+ # is not there, so column checks are off") rather than about a line of a file. Registering the same
755
+ # `key` again — on this instance or on any other instance of this plugin in the same run — is a no-op,
756
+ # so the run emits the row exactly once however many workers, files or call sites reached it.
757
+ #
758
+ # disclose_once(:missing_schema,
759
+ # message: "rigor-activerecord: schema file `db/schema.rb` not found; …",
760
+ # severity: :info, rule: "load-error")
761
+ #
762
+ # Callable from any hook — `#prepare` (the usual place: the disclosure is a fact about the project,
763
+ # known before a single file is read), `#diagnostics_for_file`, or a `node_rule` block. It returns
764
+ # `nil` and emits NOTHING itself: the engine harvests the table after analysis, de-duplicates it by
765
+ # `(plugin id, key)` across the coordinator and every pool worker, and emits one row per surviving
766
+ # pair at `.rigor.yml:1:1` — the position every other run-level plugin row already uses (plugin load
767
+ # errors, `#prepare` raises, `plugin_trust.read-refused`).
768
+ #
769
+ # That position is the point of the facility. The pre-#1051 idiom was an `@emitted` flag consulted from
770
+ # `#diagnostics_for_file`, which positions the row at whichever file the plugin instance happened to
771
+ # see first — per WORKER under `--workers N`, so the row was duplicated, and after #393 the file it
772
+ # landed on could be a template unit's `.erb` path, reading as a claim about a view. A disclosure has
773
+ # no source position to be right about; `.rigor.yml` is where the user configured the input it is
774
+ # about.
775
+ #
776
+ # `severity:` defaults to `:info`, the grade plugins use for "here is what I recognised"; pass
777
+ # `:warning` for "I could not run". `key` is any object with a stable `#to_s` (a Symbol reads best) and
778
+ # is never shown to the user — it is the de-duplication identity, so it must not interpolate anything
779
+ # that differs between workers.
780
+ def disclose_once(key, message:, severity: :info, rule: "load-error")
781
+ id = key.to_s
782
+ return nil if run_registration_taken?(id, batch: false)
783
+
784
+ @run_disclosures[id] = {
785
+ key: id, message: message.to_s, severity: severity.to_sym, rule: rule.to_s
786
+ }.freeze
787
+ nil
788
+ end
789
+
790
+ # Issue #1060 — the POSITIONED sibling of {#disclose_once}: registers a whole batch of fully built
791
+ # diagnostics under `key`, emitted once per run however many plugin instances (fork-pool workers)
792
+ # register it. Each row keeps its own `path` / `line` / `column` — use this when a project-wide scan
793
+ # produces rows that are right about a position (a view template's line) but no single analysed file
794
+ # owns them, so returning them from `#diagnostics_for_file` would repeat them once per worker.
795
+ #
796
+ # emit_once(:view_diagnostics, scan_view_files(index))
797
+ #
798
+ # The question that chooses between the two channels is whether the row has a position it could be
799
+ # right about: a notice about the run's inputs has none and goes through {#disclose_once}
800
+ # (`.rigor.yml:1:1`); a row naming a real file and line comes here and keeps it.
801
+ #
802
+ # De-duplication is by `key` alone and the first registration wins: a later batch under the same key
803
+ # is dropped whole, on this instance or on any other instance in the run, never merged row by row.
804
+ # Every worker scans the same project, so a row-wise union could only add a worker's partial or
805
+ # divergent view. `key` shares one namespace per plugin with {#disclose_once} — the two channels are
806
+ # one registration table — but a key keeps the kind it was first registered as: repeating a key on the
807
+ # same channel is a no-op, and reusing it on the OTHER channel (either order) raises `ArgumentError`
808
+ # rather than silently dropping a genuine batch or disclosure.
809
+ #
810
+ # Callable from `#prepare`, `#diagnostics_for_file`, or a `node_rule` block; returns `nil` and emits
811
+ # nothing itself. The engine stamps `source_family: "plugin.<id>"` exactly as it does for a per-file
812
+ # row, so the qualified rule and the position a baseline keys on are the ones `#diagnostics_for_file`
813
+ # would have produced. Each row is copied and frozen on registration so the batch rides the fork
814
+ # payload and the Ractor `:done` message unchanged; a non-{Rigor::Analysis::Diagnostic} element
815
+ # raises `ArgumentError`, which the engine reports as this plugin's `runtime-error`.
816
+ def emit_once(key, diagnostics)
817
+ id = key.to_s
818
+ return nil if run_registration_taken?(id, batch: true)
819
+
820
+ batch = Array(diagnostics).map do |row|
821
+ next row.dup.freeze if row.is_a?(Rigor::Analysis::Diagnostic)
822
+
823
+ raise ArgumentError, "emit_once expects Rigor::Analysis::Diagnostic rows, got #{row.class}"
824
+ end
825
+ @run_disclosures[id] = { key: id, diagnostics: batch.freeze }.freeze
826
+ nil
827
+ end
828
+
829
+ # Engine-facing reader for {#disclose_once} and {#emit_once}: this instance's registrations in
830
+ # registration order; a positioned batch is the record carrying `:diagnostics`. Plugin authors never
831
+ # call it — {Rigor::Analysis::Runner} and {Rigor::Analysis::WorkerSession} drain it, and the
832
+ # de-duplication across instances happens there.
833
+ def run_disclosure_records
834
+ @run_disclosures.values
835
+ end
836
+
837
+ # #1060 — true when `id` is already registered on the same channel (the repeat is a no-op); raises when
838
+ # it is registered on the other one, because either answer there would drop a genuine registration.
839
+ def run_registration_taken?(id, batch:)
840
+ existing = @run_disclosures[id]
841
+ return false if existing.nil?
842
+ return true if existing.key?(:diagnostics) == batch
843
+
844
+ taken = batch ? "a #disclose_once disclosure" : "an #emit_once batch"
845
+ raise ArgumentError, "run-scoped key #{id.inspect} is already registered as #{taken}; use a distinct key"
846
+ end
847
+ private :run_registration_taken?
848
+
663
849
  # Boilerplate-reduction helper (review §1.3): the "did you mean …?" suggestion every
664
850
  # diagnostic-emitting plugin otherwise hand-rolls. Returns the closest of `candidates` to `name` via
665
851
  # `DidYouMean::SpellChecker` (the same engine Ruby's own `NoMethodError` hints use), or `nil` when
@@ -673,7 +859,6 @@ module Rigor
673
859
  DidYouMean::SpellChecker.new(dictionary: dictionary).correct(name.to_s).first
674
860
  end
675
861
 
676
- # Convenience accessor — `manifest` on the instance returns the class-level manifest declaration.
677
862
  def manifest
678
863
  self.class.manifest
679
864
  end
@@ -776,7 +961,12 @@ module Rigor
776
961
  return compute unless store
777
962
 
778
963
  prefixed_id = "plugin.#{manifest.id}.#{producer_id}"
779
- key_descriptor = compose_key_descriptor(descriptor)
964
+ begin
965
+ key_descriptor = compose_key_descriptor(descriptor)
966
+ rescue Cache::EngineSource::Unavailable
967
+ # An unidentifiable engine runs the producer uncached rather than keyed without it (issue #1009).
968
+ return compute
969
+ end
780
970
  lambda do
781
971
  store.fetch_or_validate(
782
972
  producer_id: prefixed_id,
@@ -803,10 +993,9 @@ module Rigor
803
993
  # survives only as the building block for the rare producer that needs `FileEntry` rows directly;
804
994
  # plugin code calls `watch:`.
805
995
  #
806
- # @param roots [Array<String>] search roots (relative to the project root, or absolute paths)
807
- # @param patterns [Array<String>] glob suffixes joined under each root via `File.join(root, pattern)`.
996
+ # @param roots — search roots (relative to the project root, or absolute paths)
997
+ # @param patterns — glob suffixes joined under each root via `File.join(root, pattern)`.
808
998
  # Multiple patterns union into one descriptor (`"**/*.erb", "**/*.html"` etc.).
809
- # @return [Rigor::Cache::Descriptor]
810
999
  def glob_descriptor(roots, *patterns)
811
1000
  files = collect_glob_files(Array(roots), patterns)
812
1001
  entries = files.map do |path|
@@ -836,7 +1025,9 @@ module Rigor
836
1025
 
837
1026
  # ADR-37 slice 2 — the class name to match a `dynamic_return` `receivers:` entry against, from a
838
1027
  # receiver `Type`. Covers the instance (`Nominal[X]`) and class (`Singleton[X]`) shapes; other carriers
839
- # decline (nil → no match).
1028
+ # decline (nil → no match). Which of the two KINDS the name is then matched as is
1029
+ # {#dynamic_return_receiver_matches?}'s question — the `Result` / `Maybe` carriers are instances of
1030
+ # their class, like `Nominal`.
840
1031
  def dynamic_return_receiver_class_name(receiver_type)
841
1032
  case receiver_type
842
1033
  when Rigor::Type::Nominal, Rigor::Type::Singleton then receiver_type.class_name
@@ -848,7 +1039,7 @@ module Rigor
848
1039
  # The gate for one `dynamic_return` rule. Method-name gate first — a Symbol-array probe vs the receiver
849
1040
  # ancestry resolution below (ADR-52 WD1); both are pure predicates, so order only affects cost. A
850
1041
  # receiver-less rule (ADR-52 WD2) skips the ancestry check entirely and fires on the method name alone.
851
- def dynamic_return_rule_applies?(rule, call_node, class_name, environment, scope)
1042
+ def dynamic_return_rule_applies?(rule, call_node, receiver_type, environment, scope)
852
1043
  return false if rule[:methods] && !resolved_dynamic_return_methods(rule).include?(call_node.name)
853
1044
 
854
1045
  if rule[:file_methods]
@@ -858,11 +1049,29 @@ module Rigor
858
1049
  return false unless resolved_dynamic_return_file_methods(rule, path).include?(call_node.name)
859
1050
  end
860
1051
 
861
- receivers = resolved_dynamic_return_receivers(rule)
862
- return true if receivers.nil?
1052
+ entries = resolved_dynamic_return_receiver_entries(rule)
1053
+ return true if entries.nil?
1054
+
1055
+ dynamic_return_receiver_matches?(entries, receiver_type, environment)
1056
+ end
1057
+
1058
+ # Issue #701 — the receiver gate, kind included. An entry written for an instance (`"Widget"`) must not
1059
+ # answer for the class object: the block was never given the class-level call to model, and since #653
1060
+ # its answer would suppress that call's `call.undefined-method`, hiding a genuine miss on the plugin's
1061
+ # say-so. A rule that wants the class object declares `"singleton(Widget)"`, and one that wants both
1062
+ # lists both.
1063
+ #
1064
+ # A nil class name is a carrier with no nominal class (a refinement dimension, an inferred shape); a
1065
+ # receiver-gated rule cannot speak about it and declines. A receiver-less rule (ADR-52 WD2) never
1066
+ # reaches here.
1067
+ def dynamic_return_receiver_matches?(entries, receiver_type, environment)
1068
+ class_name = dynamic_return_receiver_class_name(receiver_type)
863
1069
  return false if class_name.nil?
864
1070
 
865
- receivers.any? { |c| class_matches_receiver?(class_name, c, environment) }
1071
+ singleton = receiver_type.is_a?(Rigor::Type::Singleton)
1072
+ entries.any? do |name, singleton_entry|
1073
+ singleton_entry == singleton && class_matches_receiver?(class_name, name, environment)
1074
+ end
866
1075
  end
867
1076
 
868
1077
  # ADR-52 slice 4 — the rule's method-name set. A static Array is returned as-is (`#include?` over
@@ -910,6 +1119,24 @@ module Rigor
910
1119
  Array(instance_exec(&receivers)).map { |c| c.to_s.dup.freeze }.freeze
911
1120
  end
912
1121
 
1122
+ # Issue #701 — the resolved `receivers:` entries as `[class_name, singleton?]` pairs, or nil for a
1123
+ # receiver-less rule. Memoised per rule beside the callable resolution above, so the kind is parsed
1124
+ # once per run rather than once per dispatch (this sits on the per-dispatch plugin path).
1125
+ def resolved_dynamic_return_receiver_entries(rule)
1126
+ receivers = resolved_dynamic_return_receivers(rule)
1127
+ return nil if receivers.nil?
1128
+
1129
+ (@dynamic_return_runtime_cache ||= {})[[:receiver_entries, rule]] ||=
1130
+ receivers.map { |entry| parse_dynamic_return_receiver(entry) }.freeze
1131
+ end
1132
+
1133
+ def parse_dynamic_return_receiver(entry)
1134
+ match = SINGLETON_RECEIVER_PATTERN.match(entry)
1135
+ return [match[1].freeze, true].freeze if match
1136
+
1137
+ [entry, false].freeze
1138
+ end
1139
+
913
1140
  # True when `class_name` equals or inherits from `constraint`, matched through
914
1141
  # `Environment#class_ordering` (the mechanism `MacroBlockSelfType` / `additional_initializers` use).
915
1142
  # Degrades to "no match" on any resolution failure (false-positive-safe).
@@ -957,8 +1184,13 @@ module Rigor
957
1184
  # plugin-author-supplied extension carrying IDENTITY inputs (gem-version pins, `ConfigEntry` rows,
958
1185
  # configuration-file digests). The IoBoundary read history deliberately does NOT enter the key — it is
959
1186
  # recorded post-compute into the dependency descriptor instead (see {#producer_dependency_descriptor}).
1187
+ #
1188
+ # Issue #1009 — the engine's source identity rides in the auto-built half. The manifest version is the
1189
+ # plugin author's promise, and a checkout editing a bundled plugin (or the engine API a producer computes
1190
+ # through) keeps it, so without the identity the next session served the previous build's value into a
1191
+ # run the run-result key had correctly re-analysed.
960
1192
  def compose_key_descriptor(extra)
961
- auto_built = Cache::Descriptor.new(plugins: [plugin_entry])
1193
+ auto_built = Cache::Descriptor.new(plugins: [plugin_entry], configs: Cache::EngineSource.key_config_entries)
962
1194
  return auto_built if extra.nil?
963
1195
 
964
1196
  Cache::Descriptor.compose(auto_built, extra)
@@ -976,7 +1208,7 @@ module Rigor
976
1208
  Cache::Descriptor.new(
977
1209
  files: boundary.files,
978
1210
  configs: boundary.configs,
979
- globs: watch_glob_entries(producer[:watch])
1211
+ globs: (watch_glob_entries(producer[:watch]) + boundary.globs).uniq
980
1212
  )
981
1213
  end
982
1214
 
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rbconfig"
4
+
5
+ module Rigor
6
+ module Plugin
7
+ # Decides, before `exe/rigor` re-execs itself under `RUBY_BOX=1`, whether the running Ruby can host
8
+ # Rigor inside `Ruby::Box` at all. A released Ruby up to and including 4.0.7 cannot: `env_copy()` drops
9
+ # the box a class/module-body proc carries when `Ractor.make_shareable` isolates it, so the first
10
+ # method call inside such a proc dereferences a NULL box and the VM segfaults (Ruby Bug #22260, fixed on
11
+ # CRuby master by `a4ad8e461a`). Rigor defines Ractor-shareable lambdas at module scope as a standing
12
+ # pattern, so on an affected Ruby the `ruby_box` strategy crashes the whole run rather than declining.
13
+ #
14
+ # The check is behavioural, not a version comparison: a child Ruby runs the bug's minimal reproducer
15
+ # under `RUBY_BOX=1` and must print the right answer. That admits a 4.1.0dev build with the fix and a
16
+ # future 4.0.x that backports it, and refuses a development build cut before the fix — none of which a
17
+ # `RUBY_VERSION` bound gets right. The probe costs one short-lived `ruby --disable-gems` process, paid
18
+ # only when a run opts into `ruby_box`.
19
+ #
20
+ # Loaded by `exe/rigor` before anything else, so this file requires nothing from Rigor.
21
+ module BoxProbe
22
+ # A class-body lambda isolated by `Ractor.make_shareable`, then a method dispatch inside it — the
23
+ # four ingredients the crash needs. Exits 2 when `Ruby::Box` is not active (a Ruby without the
24
+ # feature ignores `RUBY_BOX=1`), so a missing feature is told apart from a crash.
25
+ SCRIPT = <<~PROBE
26
+ exit 2 unless defined?(Ruby::Box) && Ruby::Box.respond_to?(:enabled?) && Ruby::Box.enabled?
27
+ module RigorBoxProbe
28
+ RATIONAL = Ractor.make_shareable(lambda { |*args| Rational(*args) })
29
+ end
30
+ print RigorBoxProbe::RATIONAL.call(3, 4).inspect
31
+ PROBE
32
+
33
+ EXPECTED_OUTPUT = "(3/4)"
34
+
35
+ # The reproducer answers in well under a second; a child still running after this is killed and
36
+ # counted as a failure, so a wedged probe cannot hang the launcher (or `rigor lsp` behind it).
37
+ DEADLINE_SECONDS = 10
38
+
39
+ module_function
40
+
41
+ # nil when `ruby` can run Rigor under `RUBY_BOX=1`; otherwise a short reason for the launcher's
42
+ # warning. Only a child killed by a signal is blamed on the bug; any other failure to answer is
43
+ # reported as the probe failing, never as a pass.
44
+ def unsupported_reason(ruby = RbConfig.ruby)
45
+ output, status = run(ruby)
46
+ return "the Ruby::Box probe could not run #{ruby}" if status.nil?
47
+ return nil if status.success? && output == EXPECTED_OUTPUT
48
+ return "Ruby::Box is not available in Ruby #{RUBY_VERSION}" if status.exitstatus == 2
49
+ return "the Ruby::Box probe failed (#{status})" unless status.signaled?
50
+
51
+ "Ruby #{RUBY_VERSION} crashes running Ractor-shareable procs inside Ruby::Box " \
52
+ "(Ruby Bug #22260; a Ruby carrying its fix is needed)"
53
+ end
54
+
55
+ # `RUBYOPT` is cleared so a `-r` of the caller's (bootsnap, a debugger, a coverage hook) cannot fail
56
+ # to load under `--disable-gems` and be mistaken for the crash; the probe needs nothing but core.
57
+ def run(ruby)
58
+ reader, writer = IO.pipe
59
+ pid = ::Process.spawn({ "RUBY_BOX" => "1", "RUBYOPT" => nil }, ruby, "--disable-gems", "-e", SCRIPT,
60
+ in: File::NULL, out: writer, err: File::NULL)
61
+ writer.close
62
+ output = read_until_eof(reader)
63
+ return [output, ::Process.wait2(pid).last] if output
64
+
65
+ ::Process.kill(:KILL, pid)
66
+ ::Process.wait(pid)
67
+ [nil, nil]
68
+ rescue ::SystemCallError
69
+ [nil, nil]
70
+ ensure
71
+ writer&.close unless writer&.closed?
72
+ reader&.close
73
+ end
74
+
75
+ # The child's whole stdout, or nil when it has not closed it by the deadline.
76
+ def read_until_eof(reader)
77
+ deadline = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC) + DEADLINE_SECONDS
78
+ output = +""
79
+ loop do
80
+ remaining = deadline - ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
81
+ return nil unless remaining.positive? && reader.wait_readable(remaining)
82
+
83
+ chunk = reader.read_nonblock(4096, exception: false)
84
+ return output if chunk.nil?
85
+
86
+ output << chunk if chunk.is_a?(String)
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end