rigortype 0.4.6 → 0.4.7

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 (417) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/data/gem_overlay/activesupport/core_ext.rbs +16 -13
  4. data/data/vendored_gem_sigs/bundler/bundler.rbs +1 -1
  5. data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +4 -3
  6. data/docs/handbook/01-getting-started.md +5 -2
  7. data/docs/handbook/11-sig-gen.md +50 -3
  8. data/docs/handbook/appendix-phpstan.md +4 -4
  9. data/docs/manual/02-cli-reference.md +152 -26
  10. data/docs/manual/03-configuration.md +36 -3
  11. data/docs/manual/06-baseline.md +6 -4
  12. data/docs/manual/09-editor-integration.md +13 -2
  13. data/docs/manual/10-mcp-server.md +38 -2
  14. data/docs/manual/12-caching.md +4 -3
  15. data/docs/manual/plugins/rigor-actionpack.md +19 -11
  16. data/docs/manual/plugins/rigor-activesupport-core-ext.md +20 -1
  17. data/lib/rigor/analysis/baseline.rb +24 -27
  18. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +5 -6
  19. data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +2 -2
  20. data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +4 -4
  21. data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +2 -2
  22. data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +85 -13
  23. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +3 -2
  24. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +0 -1
  25. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -8
  26. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +1 -1
  27. data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +3 -3
  28. data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
  29. data/lib/rigor/analysis/check_rules/rule_ids.rb +1 -1
  30. data/lib/rigor/analysis/check_rules/rule_walk.rb +9 -7
  31. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +3 -4
  32. data/lib/rigor/analysis/check_rules/special_global_setters.rb +3 -3
  33. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
  34. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +1 -1
  35. data/lib/rigor/analysis/check_rules.rb +124 -44
  36. data/lib/rigor/analysis/crash_signature.rb +2 -2
  37. data/lib/rigor/analysis/dependency_recorder.rb +4 -4
  38. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +14 -3
  39. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +1 -1
  40. data/lib/rigor/analysis/dependency_source_inference/index.rb +6 -8
  41. data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
  42. data/lib/rigor/analysis/edit_replay.rb +93 -6
  43. data/lib/rigor/analysis/fact_store.rb +3 -3
  44. data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +2 -2
  45. data/lib/rigor/analysis/incremental_run_slot.rb +2 -2
  46. data/lib/rigor/analysis/incremental_session.rb +27 -16
  47. data/lib/rigor/analysis/rule_catalog.rb +2 -2
  48. data/lib/rigor/analysis/run_stats.rb +2 -2
  49. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +2 -2
  50. data/lib/rigor/analysis/runner/declaration_position.rb +1 -1
  51. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +193 -93
  52. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +1 -1
  53. data/lib/rigor/analysis/runner/pool_coordinator.rb +147 -323
  54. data/lib/rigor/analysis/runner/project_pre_passes.rb +3 -3
  55. data/lib/rigor/analysis/runner/run_level_facts.rb +304 -0
  56. data/lib/rigor/analysis/runner/run_snapshots.rb +44 -54
  57. data/lib/rigor/analysis/runner/worker_payload.rb +36 -0
  58. data/lib/rigor/analysis/runner.rb +77 -71
  59. data/lib/rigor/analysis/self_call_resolution_recorder.rb +1 -1
  60. data/lib/rigor/analysis/template_unit_positions.rb +2 -3
  61. data/lib/rigor/analysis/template_units.rb +9 -1
  62. data/lib/rigor/analysis/worker_session.rb +24 -31
  63. data/lib/rigor/bleeding_edge.rb +3 -6
  64. data/lib/rigor/builtins/imported_refinements.rb +6 -8
  65. data/lib/rigor/builtins/predefined_constant_refinements.rb +1 -1
  66. data/lib/rigor/builtins/regex_refinement.rb +4 -6
  67. data/lib/rigor/cache/descriptor.rb +6 -5
  68. data/lib/rigor/cache/incremental_snapshot.rb +31 -12
  69. data/lib/rigor/cache/rbs_cache_producer.rb +2 -2
  70. data/lib/rigor/cache/rbs_class_ancestor_table.rb +1 -1
  71. data/lib/rigor/cache/rbs_class_type_param_names.rb +2 -2
  72. data/lib/rigor/cache/rbs_constant_table.rb +1 -1
  73. data/lib/rigor/cache/rbs_descriptor.rb +54 -3
  74. data/lib/rigor/cache/rbs_environment.rb +4 -1
  75. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +2 -2
  76. data/lib/rigor/cache/rbs_known_class_names.rb +1 -1
  77. data/lib/rigor/cache/store.rb +11 -10
  78. data/lib/rigor/ci_detector.rb +1 -1
  79. data/lib/rigor/cli/annotate_command.rb +24 -19
  80. data/lib/rigor/cli/baseline_command.rb +145 -55
  81. data/lib/rigor/cli/catalogue.rb +256 -0
  82. data/lib/rigor/cli/check_command.rb +108 -102
  83. data/lib/rigor/cli/check_runner_factory.rb +2 -2
  84. data/lib/rigor/cli/command.rb +15 -1
  85. data/lib/rigor/cli/coverage_command.rb +21 -8
  86. data/lib/rigor/cli/coverage_mutation.rb +5 -7
  87. data/lib/rigor/cli/coverage_renderer.rb +2 -1
  88. data/lib/rigor/cli/coverage_scan.rb +3 -5
  89. data/lib/rigor/cli/diff_command.rb +25 -20
  90. data/lib/rigor/cli/docs_command.rb +1 -2
  91. data/lib/rigor/cli/doctor_command.rb +12 -12
  92. data/lib/rigor/cli/effects_command.rb +50 -40
  93. data/lib/rigor/cli/effects_diff_renderer.rb +18 -17
  94. data/lib/rigor/cli/effects_explain_renderer.rb +13 -12
  95. data/lib/rigor/cli/effects_renderer.rb +2 -1
  96. data/lib/rigor/cli/effects_snapshot_command.rb +18 -17
  97. data/lib/rigor/cli/explain_command.rb +18 -8
  98. data/lib/rigor/cli/fused_protection_renderer.rb +2 -1
  99. data/lib/rigor/cli/json_document.rb +41 -0
  100. data/lib/rigor/cli/lens_command.rb +12 -8
  101. data/lib/rigor/cli/lsp_command.rb +250 -23
  102. data/lib/rigor/cli/mcp_command.rb +12 -7
  103. data/lib/rigor/cli/mutation_fork_scan.rb +1 -1
  104. data/lib/rigor/cli/mutation_protection_renderer.rb +3 -2
  105. data/lib/rigor/cli/options.rb +40 -1
  106. data/lib/rigor/cli/plugin_command.rb +93 -46
  107. data/lib/rigor/cli/plugins_command.rb +17 -18
  108. data/lib/rigor/cli/plugins_renderer.rb +10 -6
  109. data/lib/rigor/cli/protection_fork_scan.rb +2 -2
  110. data/lib/rigor/cli/protection_renderer.rb +2 -1
  111. data/lib/rigor/cli/show_bleedingedge_command.rb +21 -15
  112. data/lib/rigor/cli/sig_gen_command.rb +84 -27
  113. data/lib/rigor/cli/skill_command.rb +32 -3
  114. data/lib/rigor/cli/skill_deep_probe.rb +1 -1
  115. data/lib/rigor/cli/skill_describe.rb +1 -1
  116. data/lib/rigor/cli/trace_command.rb +14 -9
  117. data/lib/rigor/cli/triage_command.rb +10 -11
  118. data/lib/rigor/cli/triage_renderer.rb +2 -1
  119. data/lib/rigor/cli/type_of_command.rb +16 -11
  120. data/lib/rigor/cli/type_of_project_scope.rb +1 -4
  121. data/lib/rigor/cli/type_of_renderer.rb +4 -1
  122. data/lib/rigor/cli/type_scan_command.rb +14 -9
  123. data/lib/rigor/cli/type_scan_renderer.rb +2 -1
  124. data/lib/rigor/cli/unused_command.rb +27 -17
  125. data/lib/rigor/cli/upgrade_command.rb +5 -0
  126. data/lib/rigor/cli.rb +209 -54
  127. data/lib/rigor/config_audit.rb +1 -1
  128. data/lib/rigor/configuration/severity_profile.rb +5 -5
  129. data/lib/rigor/configuration.rb +72 -30
  130. data/lib/rigor/effects/definition_context.rb +0 -2
  131. data/lib/rigor/effects/envelope.rb +1 -1
  132. data/lib/rigor/effects/framework_units.rb +1 -1
  133. data/lib/rigor/effects/mutation_classifier.rb +4 -7
  134. data/lib/rigor/effects/propagator.rb +1 -1
  135. data/lib/rigor/effects/registry.rb +2 -2
  136. data/lib/rigor/effects/snapshot.rb +1 -1
  137. data/lib/rigor/effects/taint_cause.rb +0 -2
  138. data/lib/rigor/effects/unit_scan.rb +9 -13
  139. data/lib/rigor/environment/bundle_sig_discovery.rb +5 -9
  140. data/lib/rigor/environment/class_registry.rb +3 -3
  141. data/lib/rigor/environment/core_root.rb +77 -0
  142. data/lib/rigor/environment/default_libraries.rb +1 -1
  143. data/lib/rigor/environment/deferred_signature_plan.rb +283 -0
  144. data/lib/rigor/environment/gated_signature_guard.rb +1 -1
  145. data/lib/rigor/environment/gated_signature_plan.rb +5 -2
  146. data/lib/rigor/environment/lockfile_resolver.rb +28 -12
  147. data/lib/rigor/environment/member_consistency/comparator.rb +18 -1
  148. data/lib/rigor/environment/member_consistency.rb +56 -7
  149. data/lib/rigor/environment/missing_gem_constant_index.rb +3 -2
  150. data/lib/rigor/environment/project_path_gems.rb +124 -0
  151. data/lib/rigor/environment/rbs_hierarchy.rb +6 -15
  152. data/lib/rigor/environment/rbs_loader.rb +480 -67
  153. data/lib/rigor/environment/reflection.rb +7 -7
  154. data/lib/rigor/environment/required_features.rb +0 -3
  155. data/lib/rigor/environment/ruby_library_roots.rb +1 -1
  156. data/lib/rigor/environment/vendored_signature_plan.rb +153 -0
  157. data/lib/rigor/environment.rb +41 -10
  158. data/lib/rigor/flow_contribution/merger.rb +4 -4
  159. data/lib/rigor/inference/acceptance.rb +93 -32
  160. data/lib/rigor/inference/block_parameter_binder.rb +2 -3
  161. data/lib/rigor/inference/block_run_count.rb +178 -0
  162. data/lib/rigor/inference/budget_trace.rb +8 -2
  163. data/lib/rigor/inference/builtins/exception_catalog.rb +2 -2
  164. data/lib/rigor/inference/builtins/numeric_catalog.rb +2 -5
  165. data/lib/rigor/inference/builtins/random_catalog.rb +2 -2
  166. data/lib/rigor/inference/callee_mutation_chain.rb +88 -77
  167. data/lib/rigor/inference/callee_write_census.rb +815 -0
  168. data/lib/rigor/inference/construction_escape.rb +217 -0
  169. data/lib/rigor/inference/declaration_walk/traversal.rb +1 -1
  170. data/lib/rigor/inference/deep_erasure.rb +39 -7
  171. data/lib/rigor/inference/def_handle.rb +1 -1
  172. data/lib/rigor/inference/def_node_resolver.rb +1 -1
  173. data/lib/rigor/inference/def_return_typer.rb +46 -4
  174. data/lib/rigor/inference/definer_resolution.rb +4 -2
  175. data/lib/rigor/inference/definers_index.rb +106 -0
  176. data/lib/rigor/inference/direct_ivar_writes.rb +300 -0
  177. data/lib/rigor/inference/element_read_widening.rb +49 -3
  178. data/lib/rigor/inference/expression_typer.rb +116 -49
  179. data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
  180. data/lib/rigor/inference/global_write_census.rb +76 -2
  181. data/lib/rigor/inference/global_writer_reach.rb +260 -0
  182. data/lib/rigor/inference/hkt_body.rb +1 -2
  183. data/lib/rigor/inference/hkt_body_parser.rb +3 -5
  184. data/lib/rigor/inference/hkt_reducer.rb +2 -2
  185. data/lib/rigor/inference/hkt_registry.rb +3 -5
  186. data/lib/rigor/inference/indexed_narrowing.rb +4 -7
  187. data/lib/rigor/inference/intervening_ivar_reset.rb +26 -4
  188. data/lib/rigor/inference/ivar_writer_census.rb +51 -0
  189. data/lib/rigor/inference/key_presence_guard.rb +2 -1
  190. data/lib/rigor/inference/loop_value_record.rb +8 -4
  191. data/lib/rigor/inference/match_rebinding/frame.rb +46 -0
  192. data/lib/rigor/inference/match_rebinding/yielding_match.rb +45 -0
  193. data/lib/rigor/inference/match_rebinding.rb +11 -1
  194. data/lib/rigor/inference/memo_audit.rb +55 -0
  195. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +1 -1
  196. data/lib/rigor/inference/method_dispatcher/argument_distribution.rb +40 -0
  197. data/lib/rigor/inference/method_dispatcher/block_folding.rb +1 -1
  198. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +3 -3
  199. data/lib/rigor/inference/method_dispatcher/contested_overloads.rb +89 -0
  200. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -1
  201. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +1 -1
  202. data/lib/rigor/inference/method_dispatcher/no_match_fallback.rb +54 -0
  203. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +39 -27
  204. data/lib/rigor/inference/method_dispatcher/random_folding.rb +1 -1
  205. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +203 -17
  206. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +1 -1
  207. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +2 -2
  208. data/lib/rigor/inference/method_dispatcher/self_substitute.rb +2 -2
  209. data/lib/rigor/inference/method_dispatcher/set_folding.rb +9 -7
  210. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +83 -20
  211. data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +0 -1
  212. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +50 -17
  213. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -5
  214. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +4 -6
  215. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  216. data/lib/rigor/inference/method_dispatcher.rb +64 -14
  217. data/lib/rigor/inference/module_function_state.rb +11 -1
  218. data/lib/rigor/inference/multi_target_binder.rb +1 -1
  219. data/lib/rigor/inference/mutation_rejoin.rb +240 -4
  220. data/lib/rigor/inference/mutation_widening.rb +157 -27
  221. data/lib/rigor/inference/narrowing.rb +47 -33
  222. data/lib/rigor/inference/optimistic_origin.rb +1 -1
  223. data/lib/rigor/inference/out_buffer_mutation.rb +72 -0
  224. data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
  225. data/lib/rigor/inference/pre_eval_constants.rb +1 -1
  226. data/lib/rigor/inference/project_method_ownership.rb +6 -3
  227. data/lib/rigor/inference/project_yielder.rb +11 -17
  228. data/lib/rigor/inference/receiver_alias.rb +20 -0
  229. data/lib/rigor/inference/refined_dispatch/constructed_class.rb +3 -1
  230. data/lib/rigor/inference/refined_dispatch/own_method.rb +2 -1
  231. data/lib/rigor/inference/remover_holder_scan.rb +233 -0
  232. data/lib/rigor/inference/remover_narrowing.rb +224 -0
  233. data/lib/rigor/inference/rewrite_mutation.rb +53 -2
  234. data/lib/rigor/inference/scope_indexer/certainty.rb +0 -1
  235. data/lib/rigor/inference/scope_indexer/constant_ivar_value.rb +93 -0
  236. data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +58 -15
  237. data/lib/rigor/inference/scope_indexer/derived_param_value.rb +112 -0
  238. data/lib/rigor/inference/scope_indexer/empty_seed.rb +99 -0
  239. data/lib/rigor/inference/scope_indexer/ivar_contributions.rb +135 -0
  240. data/lib/rigor/inference/scope_indexer.rb +137 -46
  241. data/lib/rigor/inference/self_call_ivar_writes.rb +84 -12
  242. data/lib/rigor/inference/set_mutation.rb +189 -0
  243. data/lib/rigor/inference/singleton_hook_decline.rb +1 -2
  244. data/lib/rigor/inference/statement_evaluator.rb +2328 -138
  245. data/lib/rigor/inference/struct_fold_safety.rb +4 -6
  246. data/lib/rigor/inference/synthetic_method_scanner.rb +5 -6
  247. data/lib/rigor/inference/toplevel_def_census.rb +59 -0
  248. data/lib/rigor/inference/unknown_store_widening.rb +103 -4
  249. data/lib/rigor/inference/unthreaded_rebinds.rb +2 -3
  250. data/lib/rigor/inference/void_tail_summary.rb +2 -1
  251. data/lib/rigor/language_server/buffer_table.rb +3 -3
  252. data/lib/rigor/language_server/completion_provider.rb +26 -20
  253. data/lib/rigor/language_server/config_warning_announcer.rb +28 -0
  254. data/lib/rigor/language_server/diagnostic_publisher.rb +8 -3
  255. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  256. data/lib/rigor/language_server/folding_range_provider.rb +1 -2
  257. data/lib/rigor/language_server/hover_provider.rb +1 -7
  258. data/lib/rigor/language_server/hover_renderer.rb +6 -8
  259. data/lib/rigor/language_server/project_context.rb +39 -1
  260. data/lib/rigor/language_server/publish_batcher.rb +3 -4
  261. data/lib/rigor/language_server/selection_range_provider.rb +2 -4
  262. data/lib/rigor/language_server/server.rb +7 -3
  263. data/lib/rigor/language_server/signature_help_provider.rb +4 -4
  264. data/lib/rigor/lens/plugins.rb +1 -1
  265. data/lib/rigor/mcp/server.rb +73 -2
  266. data/lib/rigor/mcp.rb +1 -1
  267. data/lib/rigor/plugin/access_denied_error.rb +1 -2
  268. data/lib/rigor/plugin/bundled_catalog.rb +1 -12
  269. data/lib/rigor/plugin/io_boundary.rb +1 -1
  270. data/lib/rigor/plugin/loader.rb +25 -8
  271. data/lib/rigor/plugin/macro/heredoc_template.rb +7 -8
  272. data/lib/rigor/plugin/macro/trait_registry.rb +6 -9
  273. data/lib/rigor/plugin/manifest.rb +9 -13
  274. data/lib/rigor/plugin/registry.rb +3 -3
  275. data/lib/rigor/plugin/services.rb +2 -3
  276. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -0
  277. data/lib/rigor/plugin/trust_policy.rb +2 -2
  278. data/lib/rigor/plugin/type_node_resolver.rb +2 -4
  279. data/lib/rigor/plugin.rb +6 -8
  280. data/lib/rigor/plugin_gap_advisory.rb +5 -1
  281. data/lib/rigor/project_environment.rb +16 -2
  282. data/lib/rigor/protection/analysis_guard.rb +1 -1
  283. data/lib/rigor/protection/discovery_seed.rb +44 -5
  284. data/lib/rigor/protection/mutator.rb +1 -1
  285. data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
  286. data/lib/rigor/rbs_extended/hkt_directives.rb +6 -10
  287. data/lib/rigor/rbs_extended/reporter.rb +27 -3
  288. data/lib/rigor/rbs_extended.rb +2 -3
  289. data/lib/rigor/reflection/constant_ancestors.rb +5 -3
  290. data/lib/rigor/rooted_glob.rb +15 -1
  291. data/lib/rigor/run_scoped_slots.rb +38 -0
  292. data/lib/rigor/runtime/jit.rb +1 -1
  293. data/lib/rigor/scope/discovery_index.rb +19 -6
  294. data/lib/rigor/scope/resolution_chain.rb +3 -1
  295. data/lib/rigor/scope.rb +365 -31
  296. data/lib/rigor/sig_gen/classification.rb +4 -0
  297. data/lib/rigor/sig_gen/effect_annotation.rb +3 -3
  298. data/lib/rigor/sig_gen/generator.rb +371 -58
  299. data/lib/rigor/sig_gen/generic_header_scan.rb +189 -0
  300. data/lib/rigor/sig_gen/inline_declarations.rb +2 -2
  301. data/lib/rigor/sig_gen/layout_index.rb +3 -3
  302. data/lib/rigor/sig_gen/method_candidate.rb +23 -4
  303. data/lib/rigor/sig_gen/observation_collector.rb +3 -5
  304. data/lib/rigor/sig_gen/observed_call.rb +1 -1
  305. data/lib/rigor/sig_gen/path_mapper.rb +6 -0
  306. data/lib/rigor/sig_gen/renderer.rb +15 -9
  307. data/lib/rigor/sig_gen/skip_reason_catalog.rb +15 -2
  308. data/lib/rigor/sig_gen/type_name_qualification.rb +69 -0
  309. data/lib/rigor/sig_gen/write_reporter.rb +63 -14
  310. data/lib/rigor/sig_gen/write_result.rb +23 -12
  311. data/lib/rigor/sig_gen/writer.rb +217 -37
  312. data/lib/rigor/type/acceptance_router.rb +2 -2
  313. data/lib/rigor/type/accepts_result.rb +1 -1
  314. data/lib/rigor/type/app.rb +4 -6
  315. data/lib/rigor/type/combinator.rb +9 -13
  316. data/lib/rigor/type/constant.rb +4 -5
  317. data/lib/rigor/type/hash_shape.rb +21 -40
  318. data/lib/rigor/type/union.rb +1 -1
  319. data/lib/rigor/type_node/identifier.rb +1 -2
  320. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  321. data/lib/rigor/type_node/string_literal.rb +1 -2
  322. data/lib/rigor/type_node/symbol_literal.rb +1 -2
  323. data/lib/rigor/type_node/union.rb +3 -6
  324. data/lib/rigor/value_semantics.rb +1 -10
  325. data/lib/rigor/version.rb +1 -1
  326. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +2 -2
  327. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +2 -3
  328. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -1
  329. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +38 -36
  330. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +4 -2
  331. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +6 -4
  332. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +1 -1
  333. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -5
  334. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +0 -1
  335. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +2 -1
  336. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +1 -1
  337. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +66 -19
  338. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
  339. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +4 -3
  340. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +3 -2
  341. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +2 -2
  342. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +21 -31
  343. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +10 -16
  344. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +2 -3
  345. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +0 -1
  346. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +5 -4
  347. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +3 -4
  348. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +1 -1
  349. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +7 -12
  350. data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +1 -1
  351. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +1 -1
  352. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/symbolize_keys.rb +102 -0
  353. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +19 -6
  354. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +26 -13
  355. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +3 -3
  356. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +1 -3
  357. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +6 -6
  358. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +5 -6
  359. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +1 -1
  360. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/params_shape.rb +2 -3
  361. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -8
  362. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +0 -1
  363. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
  364. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +10 -18
  365. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +0 -1
  366. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +0 -2
  367. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +4 -5
  368. data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +0 -2
  369. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +6 -7
  370. data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +2 -4
  371. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +2 -5
  372. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -2
  373. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +6 -6
  374. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
  375. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +3 -8
  376. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +2 -3
  377. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -8
  378. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -3
  379. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -3
  380. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +1 -1
  381. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +4 -11
  382. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -1
  383. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +2 -1
  384. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +1 -1
  385. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +2 -2
  386. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +3 -4
  387. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +6 -8
  388. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +3 -2
  389. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +4 -5
  390. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +6 -7
  391. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +1 -1
  392. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +2 -2
  393. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +17 -21
  394. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +2 -3
  395. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +3 -2
  396. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +4 -4
  397. data/sig/rigor/analysis/baseline.rbs +0 -3
  398. data/sig/rigor/analysis/fact_store.rbs +1 -0
  399. data/sig/rigor/analysis/reachability/scan_cache.rbs +1 -1
  400. data/sig/rigor/cli/diff_command.rbs +1 -1
  401. data/sig/rigor/cli/explain_command.rbs +1 -1
  402. data/sig/rigor/effects/registry.rbs +2 -2
  403. data/sig/rigor/inference.rbs +4 -1
  404. data/sig/rigor/plugin/additional_initializer.rbs +0 -5
  405. data/sig/rigor/plugin/base.rbs +1 -0
  406. data/sig/rigor/plugin/effect_attribution.rbs +3 -3
  407. data/sig/rigor/plugin/effect_edge.rbs +1 -1
  408. data/sig/rigor/plugin/effect_entry_points.rbs +3 -3
  409. data/sig/rigor/plugin/io_boundary.rbs +2 -1
  410. data/sig/rigor/plugin/manifest.rbs +9 -9
  411. data/sig/rigor/plugin/protocol_contract.rbs +3 -7
  412. data/sig/rigor/scope.rbs +23 -3
  413. data/skills/rigor-plugin-author/SKILL.md +4 -4
  414. data/skills/rigor-plugin-author/references/02-walker-and-types.md +5 -5
  415. data/skills/rigor-plugin-review/SKILL.md +3 -3
  416. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +12 -1
  417. metadata +36 -3
@@ -5,6 +5,7 @@ require "prism"
5
5
  require "tmpdir"
6
6
 
7
7
  require_relative "../environment"
8
+ require_relative "../run_scoped_slots"
8
9
  require_relative "../scope"
9
10
  require_relative "../cache/store"
10
11
  require_relative "../cache/rbs_descriptor"
@@ -87,11 +88,10 @@ module Rigor
87
88
  # forbids Rigor demanding in its place) and the HKT-scan outcome (whose producer is an environment a
88
89
  # nothing-changed recheck otherwise has no reason to build). {IncrementalSession} persists what a full
89
90
  # run answers here and hands it back to the next narrowed run.
91
+ #
92
+ # Which rows those are is declared, not listed: every {RunLevelFacts} fact marked `replay`.
90
93
  def run_level_rows
91
- Cache::IncrementalSnapshot::RunLevelRows.new(
92
- definition_build_failures: @snapshots.definition_build_failures,
93
- hkt_scan_failure: @snapshots.hkt_scan_failure
94
- )
94
+ Cache::IncrementalSnapshot::RunLevelRows.new(**@snapshots.to_h.slice(*RunLevelFacts.replayable_names))
95
95
  end
96
96
 
97
97
  # ADR-46 — the per-file cross-file read records this run captured (empty unless
@@ -280,8 +280,7 @@ module Rigor
280
280
  # @param explain — surface fail-soft fallback events as `:info` diagnostics.
281
281
  # @param cache_store — the persistent cache the runner exposes to producers
282
282
  # (`RbsConstantTable` and successors). Pass `nil` to disable caching for this run; the CLI's
283
- # `--no-cache` flag wires `nil` through. v0.0.9 group A slice 1 introduces the surface; later
284
- # slices route real producers through it.
283
+ # `--no-cache` flag wires `nil` through.
285
284
  # @param workers — ADR-15 Phase 4b — when greater than zero, per-file analysis dispatches
286
285
  # across a pool of N workers. Default `0` keeps the sequential code path bit-for-bit unchanged.
287
286
  # Controlled via the `RIGOR_RACTOR_WORKERS` env var or `.rigor.yml` `parallel.workers:` (Phase 4c,
@@ -352,8 +351,8 @@ module Rigor
352
351
  @prebuilt = prebuilt
353
352
  @environment_override = environment
354
353
  # ADR-46 slice 1 — opt-in cross-file dependency recording. Off by default; when true,
355
- # `analyze_file` records each file's cross-file reads into `file_dependencies` (the incremental
356
- # cache, a later slice, consumes them).
354
+ # `analyze_file` records each file's cross-file reads into `file_dependencies`; the incremental
355
+ # cache consumes them.
357
356
  @record_dependencies = record_dependencies
358
357
  # ADR-24 slice 4a — opt-in unresolved-implicit-self-call recording. Off by default; when true,
359
358
  # `analyze_file` activates the engine choke-point recorder and collects each file's misses into
@@ -513,6 +512,9 @@ module Rigor
513
512
  # entry across a run boundary (LSP re-check, ADR-62 warm loop); nil until the first run so
514
513
  # runner-less probes keep the per-file fallback.
515
514
  @run_generation = nil
515
+ # The return memo bucket the last `run` filled, kept for {#return_summaries} once the run has released the
516
+ # thread's slot; nil before the first run.
517
+ @return_memo = nil
516
518
  build_collaborators
517
519
  end
518
520
 
@@ -535,6 +537,11 @@ module Rigor
535
537
  # freshness path for this run; the `auto` default resolves strict in CI (#190), stat-first elsewhere.
536
538
  Cache::FileDigest.with_run(strict: @configuration.cache_validation_strict?) do
537
539
  Inference::DefNodeResolver.with_run { run_analysis(paths) }
540
+ ensure
541
+ # ADR-89 WD2 — {#return_summaries} reads the return memo after the run, so the runner keeps the bucket the
542
+ # run filled before the thread's run-scoped slots are released (issues #2412, #2399).
543
+ @return_memo = Inference::ExpressionTyper.current_return_memo
544
+ RunScopedSlots.release
538
545
  end
539
546
  end
540
547
 
@@ -548,6 +555,21 @@ module Rigor
548
555
  end
549
556
  private :reset_analysis_attribution
550
557
 
558
+ # Issue #2392 — the sequential half of the per-run reset, beside {#reset_analysis_attribution}: the
559
+ # dependency graph and per-file effect collections a `--workers 0` run records, and the effect-slot state a
560
+ # served run leaves behind. The loader is the one that matters: a served run's collections loader would
561
+ # otherwise feed the next analysing run the previous run's collections. Runs before any `adopt_*`, which the
562
+ # served lanes call later in the same run, so an adopted slot is this run's own.
563
+ def reset_sequential_run_tables
564
+ @file_dependencies = {}
565
+ @file_effects = {}
566
+ @cached_effect_sources = nil
567
+ @cached_effect_ancestry = nil
568
+ @cached_collections_loader = nil
569
+ @effects_served_from_cache = false
570
+ end
571
+ private :reset_sequential_run_tables
572
+
551
573
  def run_analysis(paths)
552
574
  Inference::MethodDispatcher::FileFolding.fold_platform_specific_paths =
553
575
  @configuration.fold_platform_specific_paths
@@ -561,6 +583,10 @@ module Rigor
561
583
  @project_discovery_expansion = project_discovery_expansion(paths, expansion)
562
584
  @discovery_widened = widen_discovery_to_project?(paths)
563
585
  @snapshots.reset_for_run
586
+ # Issue #2392 — the coordinator's run-accumulated tables (disclosures, trust refusals, the dependency and
587
+ # effect graphs) are per run too: a reused Runner must not re-emit or sum the previous run's rows.
588
+ @pool_coordinator.reset_for_run
589
+ reset_sequential_run_tables
564
590
  # Per-run reset of the deferred-discovery memo (see `#ensure_project_discovery`).
565
591
  @project_discovery_done = false
566
592
  # #392 — re-derive the template units each run: their source files can change between two runs of
@@ -768,9 +794,10 @@ module Rigor
768
794
  # arg-flooring surface). Only meaningful right after a run populated the memo (baseline / recheck),
769
795
  # before the bucket rolls. Keys are held live here; the session Marshals them for the snapshot.
770
796
  #
771
- # @return `{ [path, "Class#method" | "Class.method"] => { keys:, returns:, effects: } }`.
797
+ # @return the summaries keyed by `[path, "Class#method"]` (or `"Class.method"`), each holding `keys:`,
798
+ # `returns:` and `effects:`.
772
799
  def return_summaries
773
- memo = Inference::ExpressionTyper.harvest_return_memo
800
+ memo = Inference::ExpressionTyper.harvest_return_memo(@return_memo)
774
801
  return {} if memo.empty?
775
802
 
776
803
  result = {}
@@ -828,13 +855,15 @@ module Rigor
828
855
  #
829
856
  # @param paths — analysis roots (nil → the configuration's `paths:`).
830
857
  # @param specs — each `{ class_name:, method_name:, singleton:, keys: [[receiver, args], …] }`.
831
- # @return `{ [class_name, method_name, singleton] => [return_descriptor_or_nil, …] }`.
858
+ # @return the re-evaluated return descriptors, one per key, keyed by `[class_name, method_name, singleton]`.
832
859
  def evaluate_return_types(paths, specs)
833
860
  return {} if specs.empty?
834
861
 
835
862
  Cache::FileDigest.with_run(strict: @configuration.cache_validation_strict?) do
836
863
  Inference::DefNodeResolver.with_run { evaluate_return_types_setup(paths, specs) }
837
864
  end
865
+ ensure
866
+ RunScopedSlots.release
838
867
  end
839
868
 
840
869
  # Builds the discovery index + environment (a real run's prologue, minus per-file analysis) and
@@ -1021,17 +1050,12 @@ module Rigor
1021
1050
  nil
1022
1051
  end
1023
1052
 
1024
- # The write half, run only after a miss, so the block never recomputes anything: it hands over the
1025
- # collections the analysis just produced — and the table the analysis already closed over them, so
1026
- # a warm hit re-runs neither the merge nor the fixpoint — validated against the same post-run
1027
- # dependency descriptor the diagnostics slot records. Fail-soft — a collection that will not
1028
- # Marshal (which nothing in {Effects::FileCollection} should be, and the fork pool already proves
1029
- # per file) costs the next run its warm start and nothing else.
1030
- # The write half, run only after a miss, so neither block recomputes anything. Two entries under one
1053
+ # The write half, run only after a miss, so nothing here recomputes anything. Two entries under one
1031
1054
  # key descriptor and one dependency descriptor (#482): the summary a warm run serves from, and the
1032
1055
  # collections the incremental and fail-soft paths read. The summary is written LAST, so a run
1033
1056
  # interrupted between the two leaves a collections entry that the next run's summary miss simply
1034
- # re-collects — never a summary whose collections are absent.
1057
+ # re-collects — never a summary whose collections are absent. A collection that will not Marshal
1058
+ # costs the next run its warm start and nothing else.
1035
1059
  def store_effect_collections(descriptor, expansion, rbs_descriptor)
1036
1060
  return if descriptor.nil?
1037
1061
 
@@ -1233,40 +1257,13 @@ module Rigor
1233
1257
  end
1234
1258
  private :template_unit_failure_diagnostics
1235
1259
 
1236
- # The rbs-coverage build-failure ladder plus every remaining post-analysis stream
1237
- # `#assemble_run_diagnostics` appends, in their fixed contract order (see the comments at each call
1238
- # below and at the streams' own definitions). Split out of `#assemble_run_diagnostics` so that
1239
- # method's `Metrics/AbcSize` stays under budget — every stream here is a plain `DiagnosticAggregator`
1240
- # reader with no branching of its own, so the split changes nothing about WHEN or in WHAT order they
1241
- # run.
1260
+ # Every remaining post-analysis stream `#assemble_run_diagnostics` appends — the `rbs.coverage.*`
1261
+ # build-failure ladder (widest consequence first: quarantine, env build, definition build #696, HKT scan
1262
+ # #784), its notices, the conformance rows, the reporter streams, the plugin run disclosures (#1051)
1263
+ # and, last, the trust-refusal envelope (#959) — in the contract order ADR-116 WD3's
1264
+ # {RunLevelFacts} declares. Read after `analyze_targets`, so every worker payload has been merged.
1242
1265
  def post_analysis_diagnostic_streams
1243
- diagnostics = @diagnostic_aggregator.rbs_quarantined_signature_diagnostics
1244
- diagnostics += @diagnostic_aggregator.rbs_environment_build_failed_diagnostics
1245
- # Issue #696 — after its env-wide twin and before the synthesized-namespace notice: the four
1246
- # `rbs.coverage.*` build conditions surface widest-consequence first, and their relative order is
1247
- # the diagnostic output contract.
1248
- diagnostics += @diagnostic_aggregator.rbs_definition_build_failed_diagnostics
1249
- # Issue #784 — last of the four, deliberately: it loses only the IMPLICIT HKT registrations a
1250
- # `type` alias would have contributed, never a class's own declared method surface, so it is the
1251
- # narrowest-consequence rung on the same ladder.
1252
- diagnostics += @diagnostic_aggregator.rbs_hkt_scan_failed_diagnostics
1253
- # The synthesized-namespace notice, then (#610) the plugin-signature stand-down: the outcome that
1254
- # AVOIDED a definition-build failure, so the quietest row on that ladder.
1255
- diagnostics += @diagnostic_aggregator.rbs_coverage_notice_diagnostics
1256
- diagnostics += @diagnostic_aggregator.conforms_to_diagnostics
1257
- diagnostics += @diagnostic_aggregator.rbs_extended_reporter_diagnostics
1258
- diagnostics += @diagnostic_aggregator.boundary_cross_diagnostics
1259
- diagnostics += @diagnostic_aggregator.source_rbs_synthesis_diagnostics
1260
- # Issue #1051 — read after `analyze_targets` for the same reason the refusal stream below is: a
1261
- # plugin may register a disclosure from `#prepare` (the usual case) or while analysing a file, and
1262
- # both have to be in before the table is harvested. Ordered before the refusals so the run-level
1263
- # plugin rows read plugin-authored notice first, engine-authored envelope second.
1264
- diagnostics += @diagnostic_aggregator.plugin_run_disclosure_diagnostics
1265
- # Issue #959 — read LAST (after `analyze_targets`, which already ran before this method is called),
1266
- # so every per-file plugin call has had its chance to record a refusal on its (per-plugin-instance
1267
- # memoised) `IoBoundary`, alongside the prepare-time refusals the same boundary instance may
1268
- # already carry.
1269
- diagnostics + @diagnostic_aggregator.plugin_trust_refusal_diagnostics
1266
+ @diagnostic_aggregator.run_level_diagnostics
1270
1267
  end
1271
1268
  private :post_analysis_diagnostic_streams
1272
1269
 
@@ -1583,7 +1580,9 @@ module Rigor
1583
1580
  # change time it cannot read, and passes over a `pinned` one.
1584
1581
  BaselineRows = Data.define(:owned, :pinned)
1585
1582
 
1586
- SignatureRoots = Struct.new(:signature_paths)
1583
+ # A loader's two readers {Cache::RbsDescriptor} takes signature roots from. `core_root` (issue #2517) is the
1584
+ # selected core, which the slot key names by path only, so its files are `owned` rows like the project's own.
1585
+ SignatureRoots = Struct.new(:signature_paths, :core_root)
1587
1586
  private_constant :SignatureRoots
1588
1587
 
1589
1588
  # @param files — the run's analysed-path set.
@@ -1604,8 +1603,9 @@ module Rigor
1604
1603
 
1605
1604
  # The signature tree's rows, the project's own and the rest.
1606
1605
  def signature_rows
1607
- roots = SignatureRoots.new(@snapshots.signature_paths)
1608
- own = signature_root_candidates.map { |root| File.join(File.absolute_path(root.to_s), "") }
1606
+ roots = SignatureRoots.new(@snapshots.signature_paths, @configuration.core_root)
1607
+ own = (signature_root_candidates + [@configuration.core_root].compact)
1608
+ .map { |root| File.join(File.absolute_path(root.to_s), "") }
1609
1609
  own_files, other_files = Cache::RbsDescriptor.file_entries(roots, comparator: :stat)
1610
1610
  .partition { |row| within?(row.path, own) }
1611
1611
  own_globs, other_globs = Cache::RbsDescriptor.glob_entries(roots).partition { |row| within?(row.root, own) }
@@ -2056,8 +2056,7 @@ module Rigor
2056
2056
  # Editor mode § "Scope choice — option A". Under `buffer:` non-nil the per-file analysis emits
2057
2057
  # diagnostics ONLY for the buffer's logical path; the rest of `paths:` is consumed by the
2058
2058
  # project-wide pre-passes (synthetic methods, project-patched methods, plugin facts) but contributes
2059
- # no per-file diagnostics. This is the v1 cut before a per-file diagnostic cache exists; option B
2060
- # (full project + incremental cache) is queued.
2059
+ # no per-file diagnostics. Option B (#146) narrows to the closure instead; see {#target_files}.
2061
2060
  #
2062
2061
  # The buffer's logical path is added to the file list even if it's not under `paths:` — per design §
2063
2062
  # "Failure envelope": "--instead-of=Y with Y not under any paths: directory → treated as a valid
@@ -2090,7 +2089,13 @@ module Rigor
2090
2089
  # The logical path joins even when it is not on disk under `paths:` (design § "Failure envelope"),
2091
2090
  # the same allowance option A makes below.
2092
2091
  files = files.select { |path| @analyze_only.include?(path) }
2093
- files |= [@buffer.logical_path] if @buffer && @analyze_only.include?(@buffer.logical_path)
2092
+ # Issue #2233 — a buffer that is a claimed template joins as its unit, appended by
2093
+ # {#template_unit_targets}, never as a `.rb` file: the logical path in its absolute spelling would
2094
+ # otherwise analyse the same template twice. The registry is loaded by now, which is why the test is here
2095
+ # and not in the incremental session, where it is not.
2096
+ if @buffer && @analyze_only.include?(@buffer.logical_path) && !template_path?(@buffer.logical_path)
2097
+ files |= [@buffer.logical_path]
2098
+ end
2094
2099
  return template_unit_targets(files)
2095
2100
  end
2096
2101
  return template_unit_targets(files) if @buffer.nil?
@@ -2126,6 +2131,14 @@ module Rigor
2126
2131
  end
2127
2132
  public :template_unit_paths
2128
2133
 
2134
+ # Issue #2233 — whether `path` is a template of this run: a claimed glob matches it, or it names a unit. The
2135
+ # runner keeps the buffer's logical path out of the targets when this is true, so a template buffer is
2136
+ # analysed once, as a unit ({#template_unit_targets} appends it), and never also as a plain `.rb` file.
2137
+ def template_path?(path)
2138
+ template_units.claims?(path) ||
2139
+ template_unit_paths.include?(Analysis::TemplateUnitPaths.relative(path, Dir.pwd))
2140
+ end
2141
+
2129
2142
  # Editor mode (`buffer:` non-nil) auto-flips the cache store to `read_only: true` so multiple
2130
2143
  # debounced editor invocations against the same project don't churn the on-disk cache or race on
2131
2144
  # schema-version writes. Cache reads continue unchanged; misses still run the producer block but the
@@ -2179,14 +2192,7 @@ module Rigor
2179
2192
  pool_mode: -> { pool_mode? },
2180
2193
  cached_plugin_prepare_diagnostics: -> { @cached_plugin_prepare_diagnostics },
2181
2194
  pre_eval_diagnostics_from_scanner: -> { @pre_eval_diagnostics_from_scanner },
2182
- synthesized_namespaces_snapshot: -> { @snapshots.synthesized_namespaces },
2183
- quarantined_signatures_snapshot: -> { @snapshots.quarantined_signatures },
2184
- signature_standdowns_snapshot: -> { @snapshots.signature_standdowns },
2185
- vendored_standdowns_snapshot: -> { @snapshots.vendored_standdowns },
2186
- env_build_failure_snapshot: -> { @snapshots.env_build_failure },
2187
- definition_build_failures_snapshot: -> { @snapshots.definition_build_failures },
2188
- hkt_scan_failure_snapshot: -> { @snapshots.hkt_scan_failure },
2189
- conformance_results_snapshot: -> { @snapshots.conformance_results },
2195
+ snapshots: @snapshots,
2190
2196
  pooled_run_disclosures: -> { @pool_coordinator.collected_run_disclosures },
2191
2197
  served_run_disclosures: -> { @served_run_disclosures },
2192
2198
  pooled_trust_refusals: -> { @pool_coordinator.collected_trust_refusals }
@@ -2520,14 +2526,14 @@ module Rigor
2520
2526
  @discovery_seed ? @discovery_seed.dup : {}
2521
2527
  end
2522
2528
 
2523
- # Seeds the two OPT-IN pre-pass tables — the ones an ordinary run leaves empty, so they are absent from
2524
- # the seed entirely unless the project asked for them. Both ride this seed so a pooled `WorkerSession`
2525
- # scope resolves identically to the sequential path (ADR-15 sequential equivalence).
2529
+ # Seeds the parameter-inference and constant-publication tables, so a pooled `WorkerSession` scope resolves
2530
+ # identically to the sequential path (ADR-15 sequential equivalence). The parameter-inference table is absent
2531
+ # unless its opt-in ran; the constant tables are seeded whenever the run has project constants.
2526
2532
  #
2527
2533
  # - ADR-67 WD6a `param_inferred_types`: the call-site parameter-inference table, populated only when the
2528
2534
  # `parameter_inference:` gate ran the pre-pass.
2529
- # - Issue #352 `in_source_constants`: the `pre_eval:` constant publication, populated only when the
2530
- # project lists `pre_eval:` files that declare publishable constants.
2535
+ # - Issue #352 `in_source_constants`: the `pre_eval:` constant publication, merged with the project's
2536
+ # literal constant table (#644, see {#merged_seed_constants}).
2531
2537
  #
2532
2538
  # Extracted to keep {#project_scope_seed_tables} under the complexity budget.
2533
2539
  def seed_opt_in_pre_pass_tables(tables)
@@ -18,7 +18,7 @@ module Rigor
18
18
  # recompute" lesson applied to self-calls.
19
19
  #
20
20
  # ADR-24 slice 4 (`call.self-undefined-method`) consumes the recorded misses behind a confidently-closed-class
21
- # gate (see `CheckRules` L775). The rule ships `:off` by default — {active?} is false on a normal run, so
21
+ # gate (see `CheckRules`). The rule ships `:off` by default — {active?} is false on a normal run, so
22
22
  # the instrumented choke-point pays a single integer read and records nothing. Recording is purely
23
23
  # observational; it never changes a diagnostic.
24
24
  #
@@ -173,9 +173,8 @@ module Rigor
173
173
  # copies one tag body once, and the second `@author` is the same copy seen from the other end, not a
174
174
  # second reading. Only a node OUTSIDE the winning run's own compiled span counts, which is what keeps
175
175
  # the busy lines of a real view answerable while the cross-tag repeat (`<%= v %> <%= v.to_s %>`, or
176
- # `<%= v %><%= "v" %>` probed at the STRING's `v`) still declines — the family a plugin-exported tag
177
- # span would answer instead
178
- # (see the PR), and the follow-up this rule is deliberately conservative ahead of.
176
+ # `<%= v %><%= "v" %>` probed at the STRING's `v`) still declines. That family is what a plugin-exported
177
+ # tag span would answer instead, and it is the follow-up this rule is deliberately conservative ahead of.
179
178
  #
180
179
  # `spill` is the nearest NON-BLANK compiled line above this template line's own, where a hoisted
181
180
  # leading-text literal lives. Only a string literal the run does NOT contain counts there. A code
@@ -189,6 +189,15 @@ module Rigor
189
189
  @entries.keys.sort
190
190
  end
191
191
 
192
+ # Issue #2233 — whether a plugin claims `path` by one of its `template_globs:`, in any spelling a caller holds it
193
+ # (absolute, `./`-prefixed, relative). A claimed template is analysed as a unit, never as a plain `.rb` file, so
194
+ # the runner keeps the buffer's logical path out of the `.rb` targets, so it is not analysed twice.
195
+ def claims?(path)
196
+ return false if @claimed_globs.empty?
197
+
198
+ TemplateUnitPaths.claims?(@claimed_globs, TemplateUnitPaths.relative(path, @root))
199
+ end
200
+
192
201
  def [](path)
193
202
  entry_for(path)
194
203
  end
@@ -293,7 +302,6 @@ module Rigor
293
302
  [entry.locals.keys.map(&:to_sym)]
294
303
  end
295
304
 
296
- # The `view:<logical_name>` effect-unit key for a path, or nil.
297
305
  # The file whose bytes a unit's path was READ from: the editor's buffer when one is bound to it, the
298
306
  # project file otherwise. Read by the run's dependency descriptor, so it digests what the run read.
299
307
  def physical_path(path, buffer)
@@ -25,6 +25,7 @@ require_relative "dependency_source_inference"
25
25
  require_relative "diagnostic"
26
26
  require_relative "erb_template_detector"
27
27
  require_relative "template_units"
28
+ require_relative "runner/run_level_facts"
28
29
 
29
30
  module Rigor
30
31
  module Analysis
@@ -39,8 +40,8 @@ module Rigor
39
40
  # - `configuration` — Phase 2a ({Rigor::Configuration} is `Ractor.shareable?`).
40
41
  # - `cache_store` — the fork backend passes the parent runner's pre-built Store (`cache_store:
41
42
  # @cache_store` in PoolCoordinator); workers share it rather than building their own at `cache_root`.
42
- # - `plugin_blueprints` — Phase 3a (`Array<Plugin::Blueprint>` is `Ractor.shareable?`).
43
- # - `explain` — Boolean.
43
+ # - `plugin_blueprints` — Phase 3a (a list of `Plugin::Blueprint` is `Ractor.shareable?`).
44
+ # - `explain` — a flag.
44
45
  # - `synthetic_method_index` / `project_patched_methods` / `project_scope_seed` — optional (default `nil`
45
46
  # / `{}`). NOT `Ractor.shareable?` (the seed tables carry Prism def nodes), so the Ractor pool path
46
47
  # leaves them unset; the fork backend (ADR-15 Amendment), which builds the session pre-fork on the
@@ -109,7 +110,7 @@ module Rigor
109
110
  # above: a worker and the parent agree without a flag to keep in sync.
110
111
  @effect_attribution = Effects::Attribution.build(configuration.effects_attribution)
111
112
  @file_effects = {}
112
- # ADR-32 WD4 — full project file list (frozen Array<String>) for env-build-time invocation of any
113
+ # ADR-32 WD4 — full project file list (frozen) for env-build-time invocation of any
113
114
  # loaded plugin's `source_rbs_synthesizer` callable.
114
115
  @source_files = source_files
115
116
  # #392 — the run's template units, synthesised on the PARENT before the fork and inherited here as
@@ -162,7 +163,7 @@ module Rigor
162
163
  end
163
164
 
164
165
  # Equivalent of {Rigor::Analysis::Runner#analyze_file} + `plugin_emitted_diagnostics` +
165
- # `explain_diagnostics`. Returns a flat `Array<Diagnostic>` for the file. Severity profile re-stamping
166
+ # `explain_diagnostics`. Returns a flat list of diagnostics for the file. Severity profile re-stamping
166
167
  # is intentionally NOT applied — that is a per-run aggregate concern handled by the caller. When
167
168
  # `record_dependencies:` was set, wraps the analysis in a {DependencyRecorder} window (ADR-46) so the
168
169
  # worker captures the file's cross-file reads for {#drain_dependencies}; the recorder's own disabled
@@ -271,7 +272,7 @@ module Rigor
271
272
  # definition and never enters the failing build; a diagnostic wired off the parent's loader would
272
273
  # appear at `--workers=0` and vanish at `--workers=N`, which is the same "reports less depending on
273
274
  # how you ran it" defect the diagnostic exists to end. Draining it out of the workers is what makes
274
- # the two paths say the same thing. The payload is `[String, String, String, Array<String>]` tuples —
275
+ # the two paths say the same thing. The payload is a list of plain tuples —
275
276
  # Marshal-clean for the fork backend and shareable for the Ractor one.
276
277
  #
277
278
  # Issue #784 — `hkt_scan_failure` rides the same channel for the same reason: the PARENT's own
@@ -284,33 +285,25 @@ module Rigor
284
285
  # project whose `sig/` produced a single such event killed each worker HERE — after its files were
285
286
  # analysed — and the run degraded to in-process re-analysis with a `pool-degraded` row. All three
286
287
  # streams now carry `(path, line, column)` primitives.
288
+ #
289
+ # ADR-116 WD3 (#1195) — both halves are declared rather than listed here: each reporter snapshots its
290
+ # own streams ({Runner::RunLevelFacts::REPORTERS}), and the `:analysis` facts come from
291
+ # {Runner::RunLevelFacts.drain}, the same read the coordinator makes of an environment it analysed on.
292
+ # The facts are read first: their HKT demand (#784, made once per worker so a worker whose share of
293
+ # files demanded nothing still reports the run's outcome) is what fills the hkt-directive stream (#785).
294
+ # On a worker that analysed a file the demand is a memoised read; the fork backend never hands a worker
295
+ # an empty slice, so no idle worker pays the env load.
287
296
  def drain_reporters
288
- # Issue #784 — demand the registry once per worker before reading the slot, so a worker whose share
289
- # of files happened to contain no `Klass.method` call still reports the run's outcome (the same
290
- # reason `PoolCoordinator#hkt_scan_outcome` demands it on the sequential path). The Environment
291
- # object is built in the constructor, but its RBS env is lazy: on a worker that analysed a file this
292
- # is a memoised read; on one that demanded nothing it is the same cache-served env load that file
293
- # would have paid. The fork backend never hands a worker an empty slice, so no idle worker pays it.
294
- @environment&.hkt_registry
295
- {
296
- rbs_extended: {
297
- unresolved_payloads: @rbs_extended_reporter.unresolved_payloads,
298
- lossy_projections: @rbs_extended_reporter.lossy_projections,
299
- # Issue #785 — the HKT directive stream rides the same channel for the same reason
300
- # `hkt_scan_failure` does: the directives are read by the registry scan, and under the pool the
301
- # PARENT never demands that scan, so a diagnostic wired off the parent's reporter would appear
302
- # at `--workers=0` and vanish at `--workers=N`. The demand above is what fills this.
303
- hkt_directive_errors: @rbs_extended_reporter.hkt_directive_errors,
304
- # ADR-109 WD3 — the deprecated-form stream rides the same channel: the payload is read where
305
- # the annotated method is dispatched, which under the pool is only ever inside a worker.
306
- deprecated_forms: @rbs_extended_reporter.deprecated_forms
307
- },
308
- boundary_cross: @boundary_cross_reporter.entries,
309
- source_rbs_synthesis: @source_rbs_synthesis_reporter.entries,
310
- definition_build_failures: @environment&.rbs_loader&.definition_build_failures || [],
311
- hkt_scan_failure: @environment&.hkt_scan_failure
312
- }
297
+ facts = Runner::RunLevelFacts.drain(@environment)
298
+ Runner::RunLevelFacts.snapshot_reporters(reporters).merge(facts)
299
+ end
300
+
301
+ # The per-worker reporter accumulators, by {Runner::RunLevelFacts::REPORTERS} key.
302
+ def reporters
303
+ { rbs_extended: @rbs_extended_reporter, boundary_cross: @boundary_cross_reporter,
304
+ source_rbs_synthesis: @source_rbs_synthesis_reporter }
313
305
  end
306
+ private :reporters
314
307
 
315
308
  # ADR-46 — the per-file {DependencyRecorder::Record}s captured by this session's `#analyze` calls (empty
316
309
  # unless `record_dependencies:` was set). Marshal-clean (Data of frozen Sets / String Hashes), so the
@@ -457,7 +450,7 @@ module Rigor
457
450
  return nil if gem_name.nil? || gem_name.empty?
458
451
 
459
452
  spec = Gem.loaded_specs[gem_name]
460
- spec&.full_gem_path # rigor:disable undefined-method
453
+ spec&.full_gem_path
461
454
  rescue StandardError
462
455
  nil
463
456
  end
@@ -9,11 +9,9 @@ module Rigor
9
9
  # *today's* rules are) and is versioned with the gem, NOT a user-supplied file: the
10
10
  # inspectable counterpart to PHPStan's `bleedingEdge` include.
11
11
  #
12
- # The WD2 foundation slice wired the surface end-to-end (`bleeding_edge:` config, the
13
- # `rigor show-bleedingedge` command, the severity-composition hook in
14
- # {Configuration::SeverityProfile.resolve}), so a *severity* discipline lands as a single
15
- # {FEATURES} entry with no engine plumbing — as the first one,
16
- # `reject-unparseable-signatures`, does.
12
+ # The surface is wired end-to-end (`bleeding_edge:` config, `rigor show-bleedingedge`, the
13
+ # severity-composition hook in {Configuration::SeverityProfile.resolve}), so a *severity*
14
+ # discipline is a single {FEATURES} entry with no engine plumbing.
17
15
  #
18
16
  # A queued change whose effect is not a severity move — a measurement, an algorithm, or a
19
17
  # default that changes while every rule keeps its severity — is a `:behaviour` feature
@@ -40,7 +38,6 @@ module Rigor
40
38
  # {Configuration#bleeding_edge_active?}.
41
39
  KINDS = %i[severity behaviour].freeze
42
40
 
43
- # The severity map a `:behaviour` feature carries: none.
44
41
  NO_SEVERITY_OVERRIDES = {}.freeze
45
42
 
46
43
  # One queued change.
@@ -113,9 +113,8 @@ module Rigor
113
113
  },
114
114
  # ADR-13 § "Canonical type-function additions" — five shape-projection type functions
115
115
  # that the `rigor-typescript-utility-types` plugin (and any other plugin that ships a
116
- # shape-projection vocabulary) maps onto. Phase A handles `HashShape` carriers;
117
- # non-shape inputs return the input unchanged (the lossy-projection diagnostic lands
118
- # in slice 5).
116
+ # shape-projection vocabulary) maps onto. Non-shape inputs return the input unchanged;
117
+ # the Resolver records `dynamic.shape.lossy-projection` for them.
119
118
  "pick_of" => lambda { |args|
120
119
  return nil unless args.size == 2
121
120
 
@@ -148,7 +147,7 @@ module Rigor
148
147
  # `Class[range]` — a numeric class head bounded by a Ruby range literal (ADR-109). The
149
148
  # builder receives the `Range` value and returns the carrier, or `nil` when the range is
150
149
  # empty or its endpoints are not literals of the head class; `nil` surfaces as the ordinary
151
- # unresolved-payload diagnostic. `Float` joins this table with the `FloatRange` carrier.
150
+ # unresolved-payload diagnostic. `Float` builds a `FloatRange` carrier.
152
151
  RANGE_HEAD_BUILDERS = {
153
152
  "Integer" => lambda { |range|
154
153
  lo = range.begin
@@ -186,8 +185,7 @@ module Rigor
186
185
  # `Array<Integer>` and returns a `Rigor::Type` (or `nil`). Bounds are signed integer
187
186
  # literals. A reversed pair (`min` > `max`) is declined as `nil` like every other shape
188
187
  # mismatch, so the caller surfaces `dynamic.rbs-extended.unresolved` at the annotation;
189
- # `Type::IntegerRange` raises on it instead, and that `ArgumentError` used to escape as an
190
- # `internal analyzer error` that abandoned the whole Ruby file.
188
+ # `Type::IntegerRange` would raise on it instead, which must not escape the analyzer.
191
189
  PARAMETERISED_INT_BUILDERS = {
192
190
  "int" => lambda { |bounds|
193
191
  return nil unless bounds.size == 2
@@ -276,14 +274,14 @@ module Rigor
276
274
  # grammar:
277
275
  #
278
276
  # type := simple_name | parametric
279
- # simple_name := /[a-z][a-z0-9-]*/
277
+ # simple_name := /[a-z][a-z0-9_-]*/
280
278
  # parametric := simple_name '[' type_arg_list ']'
281
279
  # | simple_name '<' int_bound_list '>'
282
280
  # type_arg_list := type_arg (',' type_arg)*
283
281
  # type_arg := range_literal | type | class_name
284
282
  # class_name := /[A-Z][A-Za-z0-9_]*(?:::[A-Z][A-Za-z0-9_]*)*/
285
283
  # range_literal := endpoint? ('..' | '...') endpoint? (at least one side written)
286
- # endpoint := signed_int | 'nil'
284
+ # endpoint := signed_int | float_literal | 'nil' | 'Float::INFINITY' | 'Float::MAX'
287
285
  # int_bound_list := signed_int (',' signed_int)*
288
286
  # signed_int := /-?\d+/
289
287
  #
@@ -62,7 +62,7 @@ module Rigor
62
62
  # `RBS::VERSION` while Rigor checks itself, rubygems and Bundler internals while it checks
63
63
  # CRuby's `lib/` — and that is worse than no answer: identical source would type differently
64
64
  # depending on how the analyzer was installed. Removing an ad hoc `Object.const_get` from an
65
- # analysis path is the same move ADR-4 § 330 already made for predicate narrowing.
65
+ # analysis path is the same move ADR-4 already made for predicate narrowing.
66
66
  #
67
67
  # This module is consulted by `Environment#constant_for_name` BEFORE the RBS
68
68
  # constant-type table (widest types) but AFTER in-source constant writes (the user's own
@@ -8,13 +8,11 @@ module Rigor
8
8
  # carriers Rigor already ships (`decimal-int-string`, `hex-int-string`,
9
9
  # `octal-int-string`, `lowercase-string`, `uppercase-string`, `numeric-string`). See
10
10
  # `docs/type-specification/imported-built-in-types.md` for the registry the refinements
11
- # come from and `docs/ROADMAP.md` § "v0.1.1 — Planned" Track 1 slice 1 for the binding
12
- # scope of this recogniser.
11
+ # come from.
13
12
  #
14
- # The intended consumer is `Inference::Narrowing.analyse_match_write`: given `if
15
- # /(?<year>\d+)/ =~ str; year; end`, the v0.1.0 baseline narrows `year` to plain `String`;
16
- # v0.1.1 introspects the regex source and narrows further to `decimal-int-string`
17
- # whenever the named-capture body matches one of the rows in {RULES}.
13
+ # The consumer is `Inference::Narrowing.analyse_match_write`: given `if /(?<year>\d+)/ =~ str;
14
+ # year; end`, `year` narrows to `decimal-int-string` when the named-capture body matches a row
15
+ # in {RULES}, and stays plain `String` otherwise.
18
16
  #
19
17
  # Recognised body shapes (each row admits the `+` quantifier and the bounded `{n}` /
20
18
  # `{n,m}` forms with `n >= 1`):
@@ -10,7 +10,8 @@ module Rigor
10
10
  module Cache
11
11
  # Cache invalidation descriptor — the typed-slot schema fixed by
12
12
  # [`docs/design/20260505-cache-slice-taxonomy.md`](../../../docs/design/20260505-cache-slice-taxonomy.md).
13
- # Pure value object: no I/O, no global state, fully immutable after construction. The storage layer
13
+ # Value object, fully immutable after construction. Filesystem reads happen only in the `FileEntry.stat`,
14
+ # `FileEntry.kind` and `GlobEntry.compute` builders and in {#fresh?}. The storage layer
14
15
  # ([`Rigor::Cache::Store`](store.rb), v0.0.8 slice 2) consumes descriptors but does not extend them.
15
16
  #
16
17
  # The descriptor has six slots (`files`, `gems`, `plugins`, `configs`, `dependencies`, `globs`); every slot is
@@ -462,10 +463,10 @@ module Rigor
462
463
 
463
464
  # ADR-45 — re-validates this descriptor's recorded {FileEntry}s against the current filesystem. Used by
464
465
  # the record-and-validate run-result cache: a value cached alongside its dependency descriptor is fresh
465
- # iff every recorded file still matches. Only `files` are checked — non-file inputs (config / gems /
466
+ # iff every recorded file still matches. Only `files` and `globs` are checked — non-file inputs (config / gems /
466
467
  # version) belong in the cache *key*, not the validated dependency set — so a descriptor carrying any
467
468
  # non-file slot is never considered fresh (it was built wrong for this use). ADR-60 WD3 adds `globs`
468
- # alongside `files` as a re-validatable slot: a {GlobEntry} is fresh when re-globbing + re-digesting
469
+ # alongside `files` as a re-validatable slot: a {GlobEntry} is fresh when re-globbing
469
470
  # reproduces its recorded value. ADR-45 WD1 (#577): an absence row ({FileEntry.absent}) is fresh
470
471
  # while its path is still missing — the negative half of the dependency set.
471
472
  def fresh?
@@ -581,8 +582,8 @@ module Rigor
581
582
  false
582
583
  end
583
584
 
584
- # ADR-60 WD3 / ADR-87 WD2 — stat-validates the entry's per-file table against the live tree, re-hashing
585
- # only stat-moved files. Any failure reads as stale (recompute), never a crash.
585
+ # ADR-60 WD3 / ADR-87 WD2 — recomputes the entry's signature over the live tree and compares it. Any
586
+ # failure reads as stale (recompute), never a crash.
586
587
  def glob_entry_fresh?(entry)
587
588
  GlobEntry.fresh?(entry)
588
589
  end