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
@@ -3,12 +3,14 @@
3
3
  require "tmpdir"
4
4
 
5
5
  require_relative "../../environment"
6
+ require_relative "../../project_environment"
6
7
  require_relative "../diagnostic"
7
8
  require_relative "../worker_session"
8
9
  require_relative "../run_stats"
9
10
  require_relative "../../effects/signature_sources"
10
11
  require_relative "../../rbs_extended/conformance_checker"
11
12
  require_relative "../../runtime/jit"
13
+ require_relative "../../cache/engine_source"
12
14
 
13
15
  module Rigor
14
16
  module Analysis
@@ -29,30 +31,21 @@ module Rigor
29
31
  # delegated back through an injected `analyze_file` callable so the CheckRules / recorder /
30
32
  # plugin-emission machinery stays on the {Runner}.
31
33
  class PoolCoordinator # rubocop:disable Metrics/ClassLength
32
- # @param configuration [Rigor::Configuration]
33
- # @param cache_store [Rigor::Cache::Store, nil]
34
- # @param explain [Boolean]
35
- # @param workers [Integer]
36
- # @param collect_stats [Boolean]
37
- # @param buffer [BufferBinding, nil]
38
- # @param environment_override [Rigor::Environment, nil]
39
- # @param rbs_extended_reporter [RbsExtended::Reporter]
40
- # @param boundary_cross_reporter [DependencySourceInference::BoundaryCrossReporter]
41
- # @param source_rbs_synthesis_reporter [Plugin::SourceRbsSynthesisReporter]
42
- # @param snapshots [RunSnapshots] shared end-of-pass snapshot sink.
43
- # @param plugin_registry [#call] reader for the current registry.
44
- # @param dependency_source_index [#call] reader.
45
- # @param synthetic_method_index [#call] reader.
46
- # @param project_patched_methods [#call] reader.
47
- # @param project_scope_seed [#call] reader for the cross-file pre-pass seed tables
34
+ # @param snapshots — shared end-of-pass snapshot sink.
35
+ # @param plugin_registry — reader for the current registry.
36
+ # @param dependency_source_index — reader.
37
+ # @param synthetic_method_index — reader.
38
+ # @param project_patched_methods — reader.
39
+ # @param project_scope_seed — reader for the cross-file pre-pass seed tables
48
40
  # (`Runner#project_scope_seed_tables`).
49
- # @param analyze_file [#call] `(path, environment) -> diagnostics`.
41
+ # @param analyze_file — `(path, environment) -> diagnostics`.
50
42
  def initialize(configuration:, cache_store:, explain:, workers:, collect_stats:, # rubocop:disable Metrics/ParameterLists
51
43
  buffer:, environment_override:, rbs_extended_reporter:,
52
44
  boundary_cross_reporter:, source_rbs_synthesis_reporter:,
53
45
  snapshots:, plugin_registry:, dependency_source_index:,
54
46
  synthetic_method_index:, project_patched_methods:,
55
- analyze_file:, project_scope_seed: -> { {} }, record_dependencies: false)
47
+ analyze_file:, project_scope_seed: -> { {} }, record_dependencies: false,
48
+ restored_run_level_rows: nil, template_units: -> { TemplateUnits.empty })
56
49
  @configuration = configuration
57
50
  @cache_store = cache_store
58
51
  @explain = explain
@@ -67,6 +60,11 @@ module Rigor
67
60
  # `#collected_effects`, so a pooled run's effect graph is the sequential run's graph.
68
61
  @record_effects = configuration.effects_enabled?
69
62
  @collected_effects = {}
63
+ # Issue #1051 — run-scoped plugin disclosures ({Plugin::Base#disclose_once}), keyed
64
+ # `[plugin id, key]` so the FIRST worker to register a pair wins and every later copy is dropped.
65
+ # The de-duplication has to happen here, on the parent, because each fork worker owns its own
66
+ # plugin instances and therefore its own `@run_disclosures` table.
67
+ @collected_run_disclosures = {}
70
68
  @collect_stats = collect_stats
71
69
  @buffer = buffer
72
70
  @environment_override = environment_override
@@ -79,7 +77,15 @@ module Rigor
79
77
  @synthetic_method_index_reader = synthetic_method_index
80
78
  @project_patched_methods_reader = project_patched_methods
81
79
  @project_scope_seed_reader = project_scope_seed
80
+ # #392 — read through a proc, like every other runner-owned table here: the units are synthesised
81
+ # on the parent during the run and handed to the ONE pre-fork {WorkerSession}, so every child
82
+ # inherits the same compiled bytes.
83
+ @template_units_reader = template_units
82
84
  @analyze_file = analyze_file
85
+ # Issues #796 / #794 — the previous full run's run-level rows, handed down by
86
+ # {Analysis::IncrementalSession} on a NARROWED run only (a recheck's closure, a
87
+ # `--verify-incremental` partition). A full run leaves this nil and derives everything itself.
88
+ @restored_run_level_rows = restored_run_level_rows
83
89
  end
84
90
 
85
91
  # ADR-46 — the per-file cross-file read records the fork pool captured this run (empty unless
@@ -92,6 +98,25 @@ module Rigor
92
98
  # these with its own sequential records before the post-pool fixpoint.
93
99
  attr_reader :collected_effects
94
100
 
101
+ # Issue #1051 — the run-scoped disclosures a pool run collected, de-duplicated by `[plugin id, key]`
102
+ # and in first-registration order. Empty on a sequential run: there the runner's own plugin
103
+ # registry is the one that registered them, and {Runner} reads it directly.
104
+ def collected_run_disclosures
105
+ @collected_run_disclosures.values
106
+ end
107
+
108
+ # Issue #1051 — folds one session's / one worker payload's disclosures in, keeping the first
109
+ # registration of each `[plugin id, key]` pair. Nothing here inspects the message: two workers that
110
+ # disclose the same key with differently interpolated text still collapse to one row, which is the
111
+ # point — the key is the identity a plugin author controls, and an author who interpolates
112
+ # worker-varying text into a message has authored one disclosure, not N.
113
+ def merge_run_disclosures(records)
114
+ Array(records).each do |record|
115
+ identity = [record[:plugin_id], record[:key]].freeze
116
+ @collected_run_disclosures[identity] ||= record
117
+ end
118
+ end
119
+
95
120
  # ADR-15 Phase 4b — pool mode is enabled when `@workers > 0`. Editor mode (`buffer:` non-nil)
96
121
  # silently overrides pool mode to sequential: per design § "Ractor pool mode", the pool's warm-up
97
122
  # cost dominates one-file wall time, so the pool gains nothing on a per-buffer invocation. The
@@ -115,47 +140,12 @@ module Rigor
115
140
  # sources. The env stays a LOCAL variable (not an ivar) so it goes GC-eligible when the method
116
141
  # returns — holding it as long-lived state added memory pressure that surfaced as a Bus Error
117
142
  # during the spec suite under Ruby 4.0 + rbs 4.0.2.
118
- # @param project_files [Array<String>, nil] issue #784 — the WHOLE project's analyzed file set
119
- # (`expansion.fetch(:files)`), independent of any `analyze_only` narrowing of `files`. Read only
120
- # when `files` is empty, to decide whether anyone could have demanded the HKT registry at all.
143
+ # @param project_files — issue #784 — the WHOLE project's analyzed file set
144
+ # (`expansion.fetch(:files)`), independent of any `analyze_only` narrowing of `files`; see
145
+ # {#serve_empty_closure} for what an empty `files` does with it.
121
146
  def analyze_files(files, environment: nil, project_files: nil)
122
147
  if files.empty?
123
- # Issue #784 — an EMPTY analyze set still owes the run its HKT-scan row: the per-file cache never
124
- # holds it (`IncrementalSession` caches only `Runner#per_file_diagnostics`, so every run-level row
125
- # is regenerated every run), so returning here without recording flips a red project green — and
126
- # the shipping `--incremental` path reaches this branch with NO environment in hand on every
127
- # warm recheck that changed nothing (`CheckCommand#run_incremental_check` builds its session
128
- # without one). So: an environment already in hand is consulted; otherwise one is resolved over
129
- # the project's OWN file list — not `[]`, which would drop every plugin-synthesized virtual RBS
130
- # (`Environment.collect_virtual_rbs` short-circuits on an empty list) and scan a different type
131
- # universe from the one a full run analyses — and only when the project HAS files: with none,
132
- # nobody could have demanded a registry, and an empty project keeps paying no env build. Keyed
133
- # on the project's files rather than on `analyze_only`, because a recheck over an EMPTY project
134
- # narrows to `Set[]`, which is non-nil.
135
- env = environment || @environment_override
136
- if project_files && !project_files.empty?
137
- env ||= resolve_sequential_environment(source_files: project_files)
138
- end
139
- # #788 rounds 6 and 9 — everything the run owes from its environment that no per-file analysis
140
- # produces is taken here, the way `analyze_files_sequentially` takes it: the project-signature
141
- # state (`synthesized-namespace`, `quarantined-signature`, `environment-build-failed`, the
142
- # conformance results), the effect-annotation carrier the residual pass reads, and the HKT-scan
143
- # outcome. Now that run-level rows are never served from the per-file cache, this branch is the
144
- # only producer on a warm recheck that changed nothing — leaving any of them out turned a red
145
- # project green on its second `--incremental` run (the inline-only `effect.annotations-unchecked`
146
- # went 1 → 0; a quarantined `signature_paths:` file went 1 → 0; a `conforms-to` class whose
147
- # definition build fails went 1 → 0). Same ORDER as the sequential path, because the order is
148
- # the contract: the conformance scan inside the signature-state snapshot demands the definition
149
- # of every `rigor:v1:conforms-to` class — a user-authored, invocation-independent demand that
150
- # #696 counts — so the definition-build failures are read AFTER it and BEFORE the HKT demand,
151
- # exactly where `analyze_files_sequentially` reads them relative to its own. What this branch
152
- # cannot regenerate is the part of that set the per-file ANALYSIS demanded (#796). This also
153
- # retires the #441 "`.rbs` lane only when the run analyses nothing" boundary: its cost premise
154
- # (no environment on this path) stopped holding the moment the branch above resolved one.
155
- snapshot_project_signature_state(env)
156
- snapshot_effect_annotation_carrier(env&.rbs_loader)
157
- record_definition_build_failures(env&.rbs_loader&.definition_build_failures)
158
- record_hkt_scan_failure(hkt_scan_outcome(env))
148
+ serve_empty_closure(environment, project_files)
159
149
  return []
160
150
  end
161
151
  # Issue #784 / #793 — `files` is what this run ANALYSES; `source_files` is what its environment
@@ -166,11 +156,152 @@ module Rigor
166
156
  # whose trigger lived in an excluded file's synthesized RBS fired on the full run and vanished on
167
157
  # the subset. Every environment and worker this run builds now takes the whole project.
168
158
  source_files = project_files || files
169
- return dispatch_pool(files, source_files: source_files) if pool_mode?
159
+ result =
160
+ if pool_mode?
161
+ dispatch_pool(files, source_files: source_files)
162
+ else
163
+ analyze_files_sequentially(
164
+ files, environment || resolve_sequential_environment(source_files: source_files)
165
+ )
166
+ end
167
+ # Issues #796 / #794 — a narrowed run's own demand set is a SUBSET of the full run's, so the two
168
+ # rows below are folded in after it, whichever backend produced the per-file stream.
169
+ replayed_restored_run_level_rows?(closure: files)
170
+ result
171
+ end
170
172
 
171
- analyze_files_sequentially(
172
- files, environment || resolve_sequential_environment(source_files: source_files)
173
+ # Issue #784 — an EMPTY analyze set still owes the run its run-level rows: the per-file cache never
174
+ # holds them (`IncrementalSession` caches only `Runner#per_file_diagnostics`), so returning here
175
+ # without recording flips a red project green — and the shipping `--incremental` path reaches this
176
+ # branch with NO environment in hand on every warm recheck that changed nothing
177
+ # (`CheckCommand#run_incremental_check` builds its session without one).
178
+ #
179
+ # #788 rounds 6 and 9 — everything the run owes from its environment that no per-file analysis
180
+ # produces is taken here, the way `analyze_files_sequentially` takes it: the project-signature state
181
+ # (`synthesized-namespace`, `quarantined-signature`, `environment-build-failed`, the conformance
182
+ # results), the effect-annotation carrier the residual pass reads, and the HKT-scan outcome. Leaving
183
+ # any of them out turned a red project green on its second `--incremental` run. Same ORDER as the
184
+ # sequential path, because the order is the contract: the conformance scan inside the
185
+ # signature-state snapshot demands the definition of every `rigor:v1:conforms-to` class — a
186
+ # user-authored, invocation-independent demand that #696 counts — so the definition-build failures
187
+ # are read AFTER it and BEFORE the HKT outcome.
188
+ #
189
+ # Issue #794 — the environment is resolved LAZILY, by the two snapshots that need one and only when
190
+ # their own gates say so: a project with no `signature_paths:`, no plugin-deferred signatures and no
191
+ # source-RBS synthesizer now reaches the end of this branch without building an environment at all,
192
+ # where #788 built one unconditionally to make the HKT demand. Issue #796 — what this branch cannot
193
+ # derive is the part of the definition-build set the per-file ANALYSIS demanded, and #696 forbids a
194
+ # Rigor-owned demand from standing in for it; the previous full run's set is replayed instead.
195
+ # @param project_files — issue #784 — the WHOLE project's analyzed file set
196
+ # (`expansion.fetch(:files)`), independent of any `analyze_only` narrowing. Read here to decide
197
+ # whether anyone could have demanded anything at all: with no files, nobody could have, and an
198
+ # empty project keeps paying no env build. Keyed on the project's files rather than on
199
+ # `analyze_only`, because a recheck over an EMPTY project narrows to `Set[]`, which is non-nil.
200
+ def serve_empty_closure(environment, project_files)
201
+ env = environment || @environment_override
202
+ # Resolved over the project's OWN file list — not `[]`, which would drop every plugin-synthesized
203
+ # virtual RBS (`Environment.collect_virtual_rbs` short-circuits on an empty list) and scan a
204
+ # different type universe from the one a full run analyses.
205
+ resolve = lambda do
206
+ next env if env
207
+ next nil if project_files.nil? || project_files.empty?
208
+
209
+ env = resolve_sequential_environment(source_files: project_files)
210
+ end
211
+ snapshot_project_signature_state(signature_state_environment(resolve))
212
+ snapshot_effect_annotation_carrier(
213
+ annotation_carrier_environment(resolve, in_hand: !env.nil?)&.rbs_loader
173
214
  )
215
+ # `env` rather than `resolve.call`: read the demand the snapshots above just made when they built
216
+ # an environment, and force nothing when they did not.
217
+ record_definition_build_failures(env&.rbs_loader&.definition_build_failures)
218
+ return if replayed_restored_run_level_rows?
219
+
220
+ record_hkt_scan_failure(hkt_scan_outcome(resolve.call))
221
+ end
222
+
223
+ # Issue #794 — the signature-state snapshot's own gates, asked BEFORE an environment exists. Both
224
+ # answers are loader-free (the configuration and the plugin registry), and with neither gate open
225
+ # {#snapshot_project_signature_state} writes the same empty state a nil environment gives it.
226
+ def signature_state_environment(resolve)
227
+ plugin_signature_paths? || project_signature_paths? ? resolve.call : nil
228
+ end
229
+
230
+ # Issue #794 — the same, for the #441 carrier. The carrier is a filter over the loader's
231
+ # `virtual_rbs`, and an environment whose loader can hold none of it answers the same empty carrier
232
+ # a nil environment does. Two ways it can hold some: a plugin registry that declares a synthesizer
233
+ # (`Environment.collect_virtual_rbs` short-circuits to `[]` without one — and ADR-93 auto-wires
234
+ # `rigor-rbs-inline`, so this is the shape almost every project is in), or an environment ALREADY in
235
+ # hand, whose loader is free to read and may carry virtual buffers from a route this coordinator
236
+ # never saw (a restored env cache, a caller-supplied override). Only the third shape — no
237
+ # synthesizer AND no environment yet — declines, which is the #794 saving: nothing is built to
238
+ # discover an emptiness the registry already proves.
239
+ def annotation_carrier_environment(resolve, in_hand: false)
240
+ return nil if @record_effects
241
+ return resolve.call if in_hand
242
+
243
+ registry = plugin_registry
244
+ return nil unless registry.respond_to?(:source_rbs_synthesizers)
245
+ return nil if registry.source_rbs_synthesizers.empty?
246
+
247
+ resolve.call
248
+ end
249
+
250
+ # Issues #796 / #794 — fold the previous full run's run-level rows into this narrowed run's
251
+ # snapshots. The definition-build set is UNIONed (`#record_definition_build_failures` dedupes by
252
+ # class), so a class this run demanded for itself and the replayed set agree; the HKT outcome is a
253
+ # single tuple whoever demanded it, so `||=` keeps this run's own answer when it has one. Returns
254
+ # true when a replay was available, which is what tells {#serve_empty_closure} it owes no HKT demand
255
+ # of its own — and so no environment.
256
+ #
257
+ # @param closure — the files this run analyses; see {#stale_replayed_failure?}.
258
+ def replayed_restored_run_level_rows?(closure: nil)
259
+ rows = @restored_run_level_rows
260
+ return false if rows.nil?
261
+
262
+ replayable = Array(rows.definition_build_failures).reject do |failure|
263
+ stale_replayed_failure?(failure, closure)
264
+ end
265
+ record_definition_build_failures(replayable)
266
+ record_hkt_scan_failure(rows.hkt_scan_failure)
267
+ true
268
+ end
269
+
270
+ # A replayed definition-build failure the snapshot's own fingerprint cannot vouch for. The
271
+ # fingerprint digests the three signature sources it can read WITHOUT an environment — `sig:`,
272
+ # `Gemfile.lock` / `rbs_collection.lock.yaml`, and the configuration — but NOT the virtual RBS a
273
+ # source-RBS synthesizer derives from `.rb` files (under ADR-93's auto-wired `rigor-rbs-inline`,
274
+ # every `#:` annotation). So a duplicate declaration can be REMOVED from a `.rb` file without moving
275
+ # the fingerprint, and the replayed row would outlive its own cause: the edited file enters the
276
+ # closure, its analysis demands nothing that would re-derive the row, and the run reports a
277
+ # collision between files that no longer collide.
278
+ #
279
+ # The narrowed run cannot re-derive the row either (#696 forbids Rigor demanding a definition the
280
+ # analysis did not), so the honest answer is to DROP it and under-report rather than assert a
281
+ # diagnostic on a program that may now be correct — false positives outrank worst-case reading. The
282
+ # cost is bounded to the row's own conflicting buffers: a collision between two files neither of
283
+ # which this run touches still replays.
284
+ def stale_replayed_failure?(failure, closure)
285
+ return false if closure.nil?
286
+
287
+ buffers = failure.is_a?(Array) ? failure[3] : nil
288
+ return false if buffers.nil?
289
+
290
+ closure_paths = closure.to_set { |path| File.expand_path(path.to_s) }
291
+ Array(buffers).any? do |name|
292
+ path = virtual_buffer_path(name)
293
+ !path.nil? && closure_paths.include?(File.expand_path(path))
294
+ end
295
+ end
296
+
297
+ # The source file behind a `virtual:<plugin id>:<path>` buffer name
298
+ # (`Environment.collect_virtual_rbs`), or nil for any other buffer — a `sig:` file, a bundled `.rbs`
299
+ # — which the fingerprint does cover and which therefore never goes stale under a replay.
300
+ def virtual_buffer_path(name)
301
+ parts = name.to_s.split(":", 3)
302
+ return nil unless parts.length == 3 && parts.first == "virtual"
303
+
304
+ parts.last
174
305
  end
175
306
 
176
307
  def analyze_files_sequentially(files, environment)
@@ -206,6 +337,13 @@ module Rigor
206
337
  # `.rigor/cache` on a bare `--no-stats` run. The loader has already memoised each answer, so reading
207
338
  # them here is free.
208
339
  def snapshot_project_signature_state(environment)
340
+ # Issue #610 — NOT behind the `signature_paths:` gate below: the source a bundled plugin's `sig/`
341
+ # stands down against is typically an `rbs collection install` the configuration never lists
342
+ # there. Gated instead on a loaded plugin contributing signatures at all — the registry answers
343
+ # that without touching the env, and it is exactly the set the loader defers — so a project with
344
+ # no such plugin reads nothing off its loader here, as before.
345
+ @snapshots.signature_standdowns =
346
+ plugin_signature_paths? ? signature_standdowns_for(environment&.rbs_loader) : []
209
347
  unless project_signature_paths?
210
348
  @snapshots.synthesized_namespaces = []
211
349
  @snapshots.quarantined_signatures = []
@@ -279,7 +417,7 @@ module Rigor
279
417
  # An effects run (ADR-103 WD13) is pinned for exactly the same reason — the Ractor messages carry
280
418
  # no side-table channel — and degrades the same way. The degrade is sound rather than merely safe:
281
419
  # the sequential fallback still collects, so the effect graph is complete either way.
282
- # @param source_files [Array<String>] the file list every worker's / the fallback's environment is
420
+ # @param source_files — the file list every worker's / the fallback's environment is
283
421
  # built over — the whole project (issue #793), defaulting to `files` for direct callers.
284
422
  def dispatch_pool(files, source_files: files)
285
423
  if @record_dependencies || @record_effects
@@ -318,11 +456,7 @@ module Rigor
318
456
  rbs_extended_reporter: @rbs_extended_reporter,
319
457
  boundary_cross_reporter: @boundary_cross_reporter,
320
458
  source_rbs_synthesis_reporter: @source_rbs_synthesis_reporter,
321
- bundler_bundle_path: @configuration.bundler_bundle_path,
322
- bundler_auto_detect: @configuration.bundler_auto_detect,
323
- bundler_lockfile: @configuration.bundler_lockfile,
324
- rbs_collection_lockfile: @configuration.rbs_collection_lockfile,
325
- rbs_collection_auto_detect: @configuration.rbs_collection_auto_detect,
459
+ **ProjectEnvironment.dependency_discovery_options(@configuration),
326
460
  synthetic_method_index: synthetic_method_index,
327
461
  project_patched_methods: project_patched_methods,
328
462
  source_files: source_files
@@ -369,6 +503,20 @@ module Rigor
369
503
  # (shareable) registry into the class-ivar cache before any worker reads.
370
504
  Environment::ClassRegistry.default
371
505
 
506
+ # #1055 — the same treatment for the engine-source digest. Every worker's `Environment.for_project`
507
+ # reaches `Cache::EngineSource.key_config_entries` whenever a source-RBS synthesizer is wired (the
508
+ # ADR-93 `rigor-rbs-inline` auto-wire, on by default and off throughout the spec suite), and the
509
+ # memo behind it is a 90 ms tree walk that cannot be eager-loaded at require time. Warming it here
510
+ # is what makes the worker's access a READ of a frozen String rather than a forbidden class-ivar
511
+ # write. `Unavailable` is left to the worker: it raises rather than assigning, so it costs the pool
512
+ # no more than it costs a sequential run, and swallowing it here would hide a tree the engine
513
+ # cannot identify.
514
+ begin
515
+ Cache::EngineSource.process_identity
516
+ rescue Cache::EngineSource::Unavailable
517
+ nil
518
+ end
519
+
372
520
  # ADR-15 Phase 4b.x — pre-warm the RBS cache so workers serve every reflection query from the
373
521
  # Marshal blob on disk. Without this, the first cache MISS inside a worker falls through to
374
522
  # `RBS::EnvironmentLoader.new`, which reads a chain of non-`Ractor.shareable?` RubyGems / RBS
@@ -389,15 +537,33 @@ module Rigor
389
537
  # finds. `#prewarm_rbs_cache_for_pool` already builds and fully loads exactly this
390
538
  # coordinator-side environment (to warm the cache before any worker spawns); it now hands it
391
539
  # back so its RBS state can be read the same way the fork pool reads its pre-fork session
392
- # environment. The conformance scan inside the snapshot demands the definition of every
393
- # `rigor:v1:conforms-to` class — a demand no Ractor worker shares memory with — so a resulting
394
- # definition-build failure is recorded explicitly right after, exactly as the empty-closure
395
- # branch reads its own environment's demand (#788). No matching explicit call for the HKT-scan
396
- # outcome: every worker already demands it from its OWN environment in `#drain_reporters`, and
397
- # the scan has one outcome whoever demands it, so a worker's report already says what the
398
- # coordinator's own demand would.
399
- warm_env = prewarm_rbs_cache_for_pool
540
+ # environment. The effect-annotation carrier (#441 — the inline stratum of
541
+ # `effect.annotations-unchecked`, read by the residual pass after this environment is gone) is
542
+ # taken off the same environment, in the same ORDER as the fork pool and the sequential path
543
+ # take theirs: it had no producer here either, so an annotation living only in an rbs-inline
544
+ # comment lost its row under this backend alone. That read is only as good as the environment's
545
+ # virtual RBS, which is why the prewarm is built the way the workers build theirs (see
546
+ # {#prewarm_rbs_cache_for_pool}). The conformance scan inside the signature-state snapshot
547
+ # demands the definition of every `rigor:v1:conforms-to` class — a demand no Ractor worker
548
+ # shares memory with — so a resulting definition-build failure is recorded explicitly right
549
+ # after, exactly as the empty-closure branch reads its own environment's demand (#788). No
550
+ # matching explicit call for the HKT-scan outcome: every worker already demands it from its OWN
551
+ # environment in `#drain_reporters`, and the scan has one outcome whoever demands it, so a
552
+ # worker's report already says what the coordinator's own demand would.
553
+ # #1064 — the lockfile is resolved HERE, once, and travels to every worker as frozen data the way
554
+ # `source_files` does: Bundler's parser reads RubyGems module state a non-main Ractor may not touch,
555
+ # so a worker resolving its own raised `Ractor::IsolationError`, printed a "malformed Gemfile.lock"
556
+ # warning per worker, and analysed with the project's gem signatures silently missing.
557
+ locked_gems = Ractor.make_shareable(
558
+ Environment::LockfileResolver.locked_gems(
559
+ lockfile_path: @configuration.bundler_lockfile,
560
+ project_root: Dir.pwd,
561
+ auto_detect: @configuration.bundler_auto_detect
562
+ )
563
+ )
564
+ warm_env = prewarm_rbs_cache_for_pool(source_files: source_files, locked_gems: locked_gems)
400
565
  snapshot_project_signature_state(warm_env)
566
+ snapshot_effect_annotation_carrier(warm_env&.rbs_loader)
401
567
  record_definition_build_failures(warm_env&.rbs_loader&.definition_build_failures)
402
568
 
403
569
  configuration = @configuration
@@ -411,14 +577,15 @@ module Rigor
411
577
  shareable_source_files = source_files.map { |path| path.to_s.dup.freeze }.freeze
412
578
 
413
579
  pool = Array.new(@workers) do
414
- Ractor.new(configuration, cache_root, blueprints, explain, shareable_source_files) do |configuration, cache_root, blueprints, explain, shareable_source_files| # rubocop:disable Layout/LineLength
580
+ Ractor.new(configuration, cache_root, blueprints, explain, shareable_source_files, locked_gems) do |configuration, cache_root, blueprints, explain, shareable_source_files, locked_gems| # rubocop:disable Layout/LineLength
415
581
  cache_store = cache_root ? Rigor::Cache::Store.new(root: cache_root) : nil
416
582
  session = Rigor::Analysis::WorkerSession.new(
417
583
  configuration: configuration,
418
584
  cache_store: cache_store,
419
585
  plugin_blueprints: blueprints,
420
586
  explain: explain,
421
- source_files: shareable_source_files
587
+ source_files: shareable_source_files,
588
+ locked_gems: locked_gems
422
589
  )
423
590
  main = Ractor.main
424
591
  main.send([:prepare, session.prepare_diagnostics])
@@ -430,7 +597,10 @@ module Rigor
430
597
  main.send([:file, msg, session.analyze(msg)])
431
598
  end
432
599
 
433
- main.send([:done, session.drain_reporters])
600
+ # Issue #1051 — sent with `:done`, not with `:prepare`: this backend builds no parent session,
601
+ # so the worker's own table is the only place a disclosure exists, and it is complete only
602
+ # once the worker has finished its files.
603
+ main.send([:done, session.drain_reporters, session.drain_run_disclosures])
434
604
  end
435
605
  end
436
606
 
@@ -464,7 +634,8 @@ module Rigor
464
634
  when :file
465
635
  results_by_path[message[1]] = message[2]
466
636
  when :done
467
- merge_worker_reporters(message.last)
637
+ merge_worker_reporters(message[1])
638
+ merge_run_disclosures(message[2])
468
639
  end
469
640
  end
470
641
 
@@ -492,6 +663,18 @@ module Rigor
492
663
  # definition-build failures the re-analysis demanded (#696) and the HKT-scan outcome, demanded
493
664
  # once more by the run itself (#784 — a rescued scan failure otherwise vanished with the worker).
494
665
  # The fork backend needs none of this: it re-analyses on the parent {WorkerSession} and drains it.
666
+ #
667
+ # Issue #1051, known exception: run-scoped disclosures a plugin registered from `#prepare` are NOT
668
+ # recovered here — nor, since #1060, a positioned `#emit_once` batch registered there, which rides
669
+ # the same table. There is no session to drain — this backend builds none on the coordinator — and
670
+ # `#prepare` deliberately does not run on the coordinator-side registry under pool mode, so a
671
+ # prepare-time disclosure lives only inside the Ractor that died with it. A disclosure registered
672
+ # from `#diagnostics_for_file` IS recovered, because the re-analysis below runs the coordinator-side
673
+ # plugin instances that {DiagnosticAggregator#plugin_run_disclosure_diagnostics} reads directly.
674
+ # Left as is: recovering the rest means running `#prepare` on the coordinator registry, which would
675
+ # re-publish every cross-plugin fact for a degrade, and this backend is off by default. Since #1055
676
+ # the path is also rare rather than universal — it used to be taken on every run, because every
677
+ # worker died in its constructor on a class-ivar memo.
495
678
  def reanalyze_degraded_in_process(degraded, results_by_path, source_files:)
496
679
  return if degraded.empty?
497
680
 
@@ -529,7 +712,8 @@ module Rigor
529
712
  project_patched_methods: project_patched_methods,
530
713
  project_scope_seed: project_scope_seed,
531
714
  source_files: source_files,
532
- record_dependencies: @record_dependencies
715
+ record_dependencies: @record_dependencies,
716
+ template_units: template_units
533
717
  )
534
718
  # Force the full RBS load on the parent so children copy-on-write inherit a warm Environment
535
719
  # rather than each rebuilding it after the fork.
@@ -550,6 +734,14 @@ module Rigor
550
734
  # same project).
551
735
  snapshot_project_signature_state(session.environment)
552
736
  snapshot_effect_annotation_carrier(session.environment.rbs_loader)
737
+ # Issue #1051 — taken BEFORE the fork, for the same reason and with the same effect as the two
738
+ # snapshots above: `#prepare` ran on the parent when the session was built, so every disclosure a
739
+ # plugin registers there (the normal case — a disclosure is a fact about the project's inputs,
740
+ # known before a file is read) is already in this table, in the same order the sequential path
741
+ # would produce. Each child inherits the identical table and re-offers it in its payload, where
742
+ # `merge_run_disclosures` drops it as a duplicate; only a disclosure a worker registers while
743
+ # analysing its own slice adds anything.
744
+ merge_run_disclosures(session.drain_run_disclosures)
553
745
  snapshot_fork_pool_stats(session) if @collect_stats
554
746
 
555
747
  worker_count = [@workers, files.size].min
@@ -568,6 +760,8 @@ module Rigor
568
760
  unless degraded.empty?
569
761
  degraded.each { |path| results_by_path[path] = session.analyze(path) }
570
762
  merge_worker_reporters(session.drain_reporters)
763
+ # The degraded slice ran on the parent session, so a disclosure it registered lives there.
764
+ merge_run_disclosures(session.drain_run_disclosures)
571
765
  # The degraded slice was re-analysed on the parent session, so its dependency records live there,
572
766
  # not in a child payload — fold them in alongside the successful children's.
573
767
  @collected_dependencies.merge!(session.drain_dependencies) if @record_dependencies
@@ -592,7 +786,8 @@ module Rigor
592
786
  Runtime::Jit.rearm_after_fork
593
787
  results = slice.to_h { |path| [path, session.analyze(path)] }
594
788
  payload = { results: results, reporters: session.drain_reporters,
595
- dependencies: session.drain_dependencies }
789
+ dependencies: session.drain_dependencies,
790
+ disclosures: session.drain_run_disclosures }
596
791
  # ADR-103 WD13 — the effects slot exists only on a collecting run, so a normal `--workers N` run
597
792
  # marshals exactly the payload it marshalled before the feature existed.
598
793
  payload[:effects] = session.drain_effects if @record_effects
@@ -624,6 +819,7 @@ module Rigor
624
819
  if payload
625
820
  results_by_path.merge!(payload.fetch(:results))
626
821
  merge_worker_reporters(payload.fetch(:reporters))
822
+ merge_run_disclosures(payload.fetch(:disclosures, []))
627
823
  @collected_dependencies.merge!(payload.fetch(:dependencies, {})) if @record_dependencies
628
824
  @collected_effects.merge!(payload.fetch(:effects, {})) if @record_effects
629
825
  else
@@ -633,7 +829,7 @@ module Rigor
633
829
  degraded
634
830
  end
635
831
 
636
- # @return [Hash, nil] the child's `{results:, reporters:}` payload, or nil when the child exited
832
+ # @return the child's `{results:, reporters:}` payload, or nil when the child exited
637
833
  # abnormally or wrote no readable payload. `Marshal.load` is safe here: the blob was written by
638
834
  # our own forked child to a temp file we created.
639
835
  def fork_worker_payload(status, out_path)
@@ -658,19 +854,43 @@ module Rigor
658
854
  # ADR-15 Phase 4b.x — drives every cached RBS producer on the main Ractor so each worker can serve
659
855
  # all reflection queries from disk (Marshal-load only). Builds a single coordinator-side
660
856
  # {Environment} for this purpose and returns it fully loaded — issue #798: the caller also reads
661
- # the project-signature state off it, since it is the only environment this backend's coordinator
662
- # ever holds. Workers still build their OWN `Environment.for_project` inside the Ractor body, which
663
- # then routes through `cached_env` instead of `RBS::EnvironmentLoader.new`.
664
- def prewarm_rbs_cache_for_pool
857
+ # the project-signature state and the effect-annotation carrier off it, since it is the only
858
+ # environment this backend's coordinator ever holds. Workers still build their OWN
859
+ # `Environment.for_project` inside the Ractor body, which then routes through `cached_env` instead
860
+ # of `RBS::EnvironmentLoader.new`.
861
+ #
862
+ # Built with the loaded plugin registry over the project's WHOLE file list (#793's rule), exactly
863
+ # as each worker builds its own. `Environment.for_project` collects plugin-synthesized virtual RBS
864
+ # only when handed BOTH (`collect_virtual_rbs` short-circuits on a nil registry and on an empty
865
+ # list), and this used to pass neither: its loader's `virtual_rbs` read empty on every project, so
866
+ # a carrier snapshotted off it would have been `[]` whatever the rbs-inline comments said — the
867
+ # method called, the row still missing. Two more things follow from building the environment the
868
+ # workers build: the env-cache descriptor digests the same signature roots (a plugin's
869
+ # `signature_paths:`) and the same virtual set, so the slot this warms is the slot the workers
870
+ # read; and each synthesizer runs once here and is memoised through the cache store (ADR-32 WD5)
871
+ # instead of once per worker.
872
+ #
873
+ # Deliberately NOT {#build_runner_environment}: that hands the environment the run's own reporter
874
+ # accumulators, and an environment BUILD writes to one of them — `collect_virtual_rbs` records a
875
+ # synthesizer's parse failures and unhonoured annotations on the `source_rbs_synthesis` reporter.
876
+ # Every Ractor worker builds this same environment with reporters of its own and drains them back
877
+ # through {#merge_worker_reporters}, whose replay of that stream appends without dedup, so a
878
+ # coordinator-side reporter here would count each entry once more than the drains already do. The
879
+ # other two are written only by demands this environment never receives (the HKT scan, per-file
880
+ # dispatch); all three are withheld together so the no-double-count holds by construction rather
881
+ # than by which reads happen to be made. The dispatch-only per-run state (the dependency-source
882
+ # index, the synthetic-method / project-patched indexes) is withheld for the same reason: nothing
883
+ # here dispatches.
884
+ # @param source_files — the WHOLE project's file list, never the analyzed subset.
885
+ def prewarm_rbs_cache_for_pool(source_files:, locked_gems: nil)
665
886
  warm_env = Environment.for_project(
666
887
  libraries: @configuration.libraries,
667
888
  signature_paths: @configuration.signature_paths,
668
889
  cache_store: @cache_store,
669
- bundler_bundle_path: @configuration.bundler_bundle_path,
670
- bundler_auto_detect: @configuration.bundler_auto_detect,
671
- bundler_lockfile: @configuration.bundler_lockfile,
672
- rbs_collection_lockfile: @configuration.rbs_collection_lockfile,
673
- rbs_collection_auto_detect: @configuration.rbs_collection_auto_detect
890
+ plugin_registry: plugin_registry,
891
+ source_files: source_files,
892
+ locked_gems: locked_gems,
893
+ **ProjectEnvironment.dependency_discovery_options(@configuration)
674
894
  )
675
895
  warm_env.rbs_loader&.prewarm
676
896
  warm_env
@@ -680,11 +900,16 @@ module Rigor
680
900
  # (currently: `--no-cache` would force workers through `EnvironmentLoader.new`), degrade to
681
901
  # sequential analysis with a `:warning` `pool-degraded` diagnostic at run start. The actual
682
902
  # per-file analysis runs on the coordinator, identical to the default sequential path.
683
- # @param source_files [Array<String>] issue #793 — the whole project, so this path's environment
903
+ # @param source_files — issue #793 — the whole project, so this path's environment
684
904
  # carries the same plugin-synthesized RBS the pool workers' would. It used to build over `[]`,
685
905
  # i.e. with no synthesized RBS at all, even on a full run.
686
906
  def analyze_files_sequentially_fallback(files, reason:, source_files: files)
687
907
  environment = build_runner_environment(source_files: source_files)
908
+ # Same set, same ORDER as `#analyze_files_sequentially`. The signature state used to be hand-copied
909
+ # here slot by slot, which dropped `synthesized-namespace` and every conformance row. The conformance
910
+ # scan inside it demands the definition of every `rigor:v1:conforms-to` class, so it has to run
911
+ # BEFORE the definition-build failures are read below, or a failure only that scan reaches is lost.
912
+ snapshot_project_signature_state(environment)
688
913
  snapshot_effect_annotation_carrier(environment.rbs_loader)
689
914
  diagnostics = files.flat_map { |path| @analyze_file.call(path, environment) }
690
915
  loader = environment.rbs_loader
@@ -699,9 +924,6 @@ module Rigor
699
924
  record_hkt_scan_failure(hkt_scan_outcome(environment))
700
925
  @snapshots.class_decl_paths = loader&.class_decl_paths || {}.freeze
701
926
  @snapshots.signature_paths = loader&.signature_paths || [].freeze
702
- @snapshots.quarantined_signatures =
703
- project_signature_paths? ? (loader&.quarantined_signatures || []) : []
704
- @snapshots.env_build_failure = project_signature_paths? ? loader&.env_build_failure : nil
705
927
  diagnostics.unshift(
706
928
  Diagnostic.new(
707
929
  path: ".rigor.yml", line: 1, column: 1,
@@ -723,9 +945,17 @@ module Rigor
723
945
  end
724
946
  # ADR-32 WD6 — merge per-worker synthesizer failures back into the coordinator's reporter. Fetched
725
947
  # with a default empty array so older drains (pre-slice-2) remain compatible.
948
+ #
949
+ # `kind:` rides along (issue #824): without it every drained entry replayed as WD6's default
950
+ # `:failed`, so under `--workers N` a WD12 "parsed but not honoured" row came back as
951
+ # `source-rbs-synthesis-failed` — a different id, and a message telling the user the file
952
+ # contributed nothing when all but one of its annotations bound. Every worker reads the same
953
+ # sources and so hands over the same entries; the reporter's own dedup collapses them. `line:` rides
954
+ # along for the same reason (#1075): a consistency error is positioned at its `.rbs` member.
726
955
  Array(drained[:source_rbs_synthesis]).each do |entry|
727
956
  @source_rbs_synthesis_reporter.record(
728
- plugin_id: entry.plugin_id, path: entry.path, message: entry.message
957
+ plugin_id: entry.plugin_id, path: entry.path, message: entry.message, kind: entry.kind,
958
+ line: entry.line
729
959
  )
730
960
  end
731
961
  # Issue #696. Fetched with a default so an older drain stays compatible, exactly as the line above.
@@ -764,6 +994,18 @@ module Rigor
764
994
  message: entry.message, path: entry.path, line: entry.line, column: entry.column
765
995
  )
766
996
  end
997
+ replay_deprecated_forms(rbs)
998
+ end
999
+
1000
+ # ADR-109 WD3 — the fourth stream, read with the nil default like the hkt one so an older drain
1001
+ # shape records nothing.
1002
+ def replay_deprecated_forms(rbs)
1003
+ Array(rbs[:deprecated_forms]).each do |entry|
1004
+ @rbs_extended_reporter.record_deprecated_form(
1005
+ payload: entry.payload, replacement: entry.replacement,
1006
+ path: entry.path, line: entry.line, column: entry.column
1007
+ )
1008
+ end
767
1009
  end
768
1010
 
769
1011
  # Issue #696 — accumulate the per-class `RBS::DefinitionBuilder` failures this run observed, deduped
@@ -822,8 +1064,7 @@ module Rigor
822
1064
  # build on a project with no files (see {#analyze_files}). Memoised, so on a reused Environment
823
1065
  # this is a hash read.
824
1066
  #
825
- # @param environment [Rigor::Environment, nil]
826
- # @return [Array, nil] the recorded tuple, or nil (no environment, or the scan built)
1067
+ # @return the recorded tuple, or nil (no environment, or the scan built)
827
1068
  def hkt_scan_outcome(environment)
828
1069
  return nil if environment.nil?
829
1070
 
@@ -838,6 +1079,21 @@ module Rigor
838
1079
  !(paths.nil? || paths.empty?)
839
1080
  end
840
1081
 
1082
+ # Issue #610 — see {#snapshot_project_signature_state}. The registry's `signature_paths` is what
1083
+ # `Environment.for_project` defers, so this is the loader-free form of "is anything deferred?".
1084
+ def plugin_signature_paths?
1085
+ registry = plugin_registry
1086
+ registry.respond_to?(:signature_paths) && !registry.signature_paths.empty?
1087
+ end
1088
+
1089
+ # Empty for a loader with nothing deferred, without touching its env.
1090
+ def signature_standdowns_for(loader)
1091
+ return [] if loader.nil? || !loader.respond_to?(:deferred_signature_paths)
1092
+ return [] if loader.deferred_signature_paths.empty?
1093
+
1094
+ loader.signature_standdowns
1095
+ end
1096
+
841
1097
  def plugin_registry
842
1098
  @plugin_registry_reader.call
843
1099
  end
@@ -854,6 +1110,10 @@ module Rigor
854
1110
  @project_patched_methods_reader.call
855
1111
  end
856
1112
 
1113
+ def template_units
1114
+ @template_units_reader.call
1115
+ end
1116
+
857
1117
  def project_scope_seed
858
1118
  @project_scope_seed_reader.call
859
1119
  end