rigortype 0.4.6 → 0.4.8

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 (550) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -2
  3. data/data/core_overlay/bigdecimal.rbs +33 -0
  4. data/data/core_overlay/resolv.rbs +30 -0
  5. data/data/gem_overlay/activesupport/core_ext.rbs +45 -13
  6. data/data/gem_overlay/addressable/addressable.rbs +40 -0
  7. data/data/vendored_gem_sigs/bundler/bundler.rbs +1 -1
  8. data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +4 -3
  9. data/docs/handbook/01-getting-started.md +6 -3
  10. data/docs/handbook/11-sig-gen.md +85 -3
  11. data/docs/handbook/appendix-phpstan.md +4 -4
  12. data/docs/install.md +20 -0
  13. data/docs/manual/01-installation.md +67 -26
  14. data/docs/manual/02-cli-reference.md +455 -113
  15. data/docs/manual/03-configuration.md +76 -5
  16. data/docs/manual/04-diagnostics.md +5 -4
  17. data/docs/manual/06-baseline.md +6 -4
  18. data/docs/manual/08-skills.md +11 -0
  19. data/docs/manual/09-editor-integration.md +21 -2
  20. data/docs/manual/10-mcp-server.md +38 -2
  21. data/docs/manual/12-caching.md +4 -3
  22. data/docs/manual/14-rails-quickstart.md +1 -1
  23. data/docs/manual/19-effect-labels.md +3 -4
  24. data/docs/manual/plugins/rigor-actionpack.md +32 -15
  25. data/docs/manual/plugins/rigor-activesupport-core-ext.md +23 -1
  26. data/docs/manual/plugins/rigor-sorbet.md +17 -3
  27. data/lib/rigor/analysis/baseline.rb +24 -27
  28. data/lib/rigor/analysis/check_rules/always_raises_shield.rb +147 -0
  29. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +5 -6
  30. data/lib/rigor/analysis/check_rules/call_facts.rb +58 -0
  31. data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +2 -2
  32. data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +12 -4
  33. data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +2 -2
  34. data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +85 -13
  35. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +16 -4
  36. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +6 -1
  37. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -8
  38. data/lib/rigor/analysis/check_rules/plugin_api_surface.rb +195 -0
  39. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +41 -12
  40. data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +27 -1
  41. data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +3 -3
  42. data/lib/rigor/analysis/check_rules/replaced_singleton_defs.rb +96 -0
  43. data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
  44. data/lib/rigor/analysis/check_rules/rule_ids.rb +9 -2
  45. data/lib/rigor/analysis/check_rules/rule_walk.rb +9 -7
  46. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +3 -4
  47. data/lib/rigor/analysis/check_rules/special_global_setters.rb +3 -3
  48. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
  49. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +2 -2
  50. data/lib/rigor/analysis/check_rules.rb +720 -141
  51. data/lib/rigor/analysis/crash_signature.rb +45 -3
  52. data/lib/rigor/analysis/dependency_recorder.rb +237 -80
  53. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +14 -3
  54. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +1 -1
  55. data/lib/rigor/analysis/dependency_source_inference/index.rb +6 -8
  56. data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
  57. data/lib/rigor/analysis/edit_replay.rb +93 -6
  58. data/lib/rigor/analysis/fact_store.rb +3 -3
  59. data/lib/rigor/analysis/incremental.rb +11 -1
  60. data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +4 -3
  61. data/lib/rigor/analysis/incremental_run_slot.rb +2 -2
  62. data/lib/rigor/analysis/incremental_session.rb +306 -53
  63. data/lib/rigor/analysis/reachability/plugin_roots.rb +6 -3
  64. data/lib/rigor/analysis/rule_catalog.rb +39 -3
  65. data/lib/rigor/analysis/run_cache_key.rb +27 -3
  66. data/lib/rigor/analysis/run_disclosure_credit.rb +26 -6
  67. data/lib/rigor/analysis/run_stats.rb +2 -2
  68. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -2
  69. data/lib/rigor/analysis/runner/declaration_position.rb +8 -8
  70. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +264 -112
  71. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +4 -9
  72. data/lib/rigor/analysis/runner/pool_coordinator.rb +173 -328
  73. data/lib/rigor/analysis/runner/project_pre_passes.rb +4 -37
  74. data/lib/rigor/analysis/runner/run_level_facts.rb +304 -0
  75. data/lib/rigor/analysis/runner/run_snapshots.rb +44 -54
  76. data/lib/rigor/analysis/runner/worker_payload.rb +36 -0
  77. data/lib/rigor/analysis/runner.rb +298 -109
  78. data/lib/rigor/analysis/self_call_resolution_recorder.rb +62 -23
  79. data/lib/rigor/analysis/template_unit_positions.rb +2 -3
  80. data/lib/rigor/analysis/template_units.rb +9 -1
  81. data/lib/rigor/analysis/worker_session.rb +65 -71
  82. data/lib/rigor/bleeding_edge.rb +22 -10
  83. data/lib/rigor/builtins/imported_refinements.rb +6 -8
  84. data/lib/rigor/builtins/predefined_constant_refinements.rb +1 -1
  85. data/lib/rigor/builtins/regex_refinement.rb +4 -6
  86. data/lib/rigor/cache/descriptor.rb +6 -5
  87. data/lib/rigor/cache/file_digest.rb +39 -31
  88. data/lib/rigor/cache/incremental_snapshot.rb +95 -17
  89. data/lib/rigor/cache/rbs_cache_producer.rb +2 -2
  90. data/lib/rigor/cache/rbs_class_ancestor_table.rb +1 -1
  91. data/lib/rigor/cache/rbs_class_type_param_names.rb +2 -2
  92. data/lib/rigor/cache/rbs_constant_table.rb +1 -1
  93. data/lib/rigor/cache/rbs_descriptor.rb +90 -4
  94. data/lib/rigor/cache/rbs_environment.rb +8 -1
  95. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +2 -2
  96. data/lib/rigor/cache/rbs_known_class_names.rb +1 -1
  97. data/lib/rigor/cache/store.rb +11 -10
  98. data/lib/rigor/ci_detector.rb +1 -1
  99. data/lib/rigor/cli/annotate_command.rb +24 -19
  100. data/lib/rigor/cli/baseline_command.rb +160 -57
  101. data/lib/rigor/cli/catalogue.rb +256 -0
  102. data/lib/rigor/cli/check_command.rb +112 -104
  103. data/lib/rigor/cli/check_runner_factory.rb +2 -2
  104. data/lib/rigor/cli/command.rb +15 -1
  105. data/lib/rigor/cli/coverage_command.rb +21 -8
  106. data/lib/rigor/cli/coverage_mutation.rb +5 -7
  107. data/lib/rigor/cli/coverage_renderer.rb +2 -1
  108. data/lib/rigor/cli/coverage_scan.rb +3 -5
  109. data/lib/rigor/cli/diff_command.rb +25 -20
  110. data/lib/rigor/cli/docs_command.rb +1 -2
  111. data/lib/rigor/cli/doctor_command.rb +12 -12
  112. data/lib/rigor/cli/effects_command.rb +50 -40
  113. data/lib/rigor/cli/effects_diff_renderer.rb +18 -17
  114. data/lib/rigor/cli/effects_explain_renderer.rb +13 -12
  115. data/lib/rigor/cli/effects_renderer.rb +2 -1
  116. data/lib/rigor/cli/effects_snapshot_command.rb +26 -18
  117. data/lib/rigor/cli/explain_command.rb +18 -8
  118. data/lib/rigor/cli/fused_protection_renderer.rb +2 -1
  119. data/lib/rigor/cli/json_document.rb +41 -0
  120. data/lib/rigor/cli/lens_command.rb +29 -10
  121. data/lib/rigor/cli/lsp_command.rb +250 -23
  122. data/lib/rigor/cli/mcp_command.rb +12 -7
  123. data/lib/rigor/cli/mutation_fork_scan.rb +1 -1
  124. data/lib/rigor/cli/mutation_protection_renderer.rb +3 -2
  125. data/lib/rigor/cli/options.rb +40 -1
  126. data/lib/rigor/cli/plugin_command.rb +93 -46
  127. data/lib/rigor/cli/plugins_command.rb +17 -18
  128. data/lib/rigor/cli/plugins_renderer.rb +10 -6
  129. data/lib/rigor/cli/protection_fork_scan.rb +2 -2
  130. data/lib/rigor/cli/protection_renderer.rb +2 -1
  131. data/lib/rigor/cli/show_bleedingedge_command.rb +21 -15
  132. data/lib/rigor/cli/sig_gen_command.rb +92 -27
  133. data/lib/rigor/cli/skill_command.rb +32 -3
  134. data/lib/rigor/cli/skill_deep_probe.rb +1 -1
  135. data/lib/rigor/cli/skill_describe.rb +2 -1
  136. data/lib/rigor/cli/trace_command.rb +14 -9
  137. data/lib/rigor/cli/triage_command.rb +10 -11
  138. data/lib/rigor/cli/triage_renderer.rb +2 -1
  139. data/lib/rigor/cli/type_of_command.rb +16 -11
  140. data/lib/rigor/cli/type_of_project_scope.rb +1 -4
  141. data/lib/rigor/cli/type_of_renderer.rb +4 -1
  142. data/lib/rigor/cli/type_scan_command.rb +14 -9
  143. data/lib/rigor/cli/type_scan_renderer.rb +2 -1
  144. data/lib/rigor/cli/unused_command.rb +27 -17
  145. data/lib/rigor/cli/upgrade_command.rb +5 -0
  146. data/lib/rigor/cli.rb +214 -56
  147. data/lib/rigor/config_audit.rb +33 -19
  148. data/lib/rigor/configuration/severity_profile.rb +14 -5
  149. data/lib/rigor/configuration.rb +104 -35
  150. data/lib/rigor/effects/collector.rb +53 -21
  151. data/lib/rigor/effects/definition_context.rb +0 -2
  152. data/lib/rigor/effects/envelope.rb +1 -1
  153. data/lib/rigor/effects/framework_units.rb +1 -1
  154. data/lib/rigor/effects/mutation_classifier.rb +4 -7
  155. data/lib/rigor/effects/propagator.rb +1 -1
  156. data/lib/rigor/effects/registry.rb +2 -2
  157. data/lib/rigor/effects/snapshot.rb +1 -1
  158. data/lib/rigor/effects/taint_cause.rb +0 -2
  159. data/lib/rigor/effects/unit_scan.rb +9 -13
  160. data/lib/rigor/environment/bundle_sig_discovery.rb +64 -33
  161. data/lib/rigor/environment/class_registry.rb +3 -3
  162. data/lib/rigor/environment/constant_type_cache_holder.rb +5 -0
  163. data/lib/rigor/environment/core_root.rb +77 -0
  164. data/lib/rigor/environment/default_libraries.rb +18 -1
  165. data/lib/rigor/environment/deferred_signature_plan.rb +381 -0
  166. data/lib/rigor/environment/gated_signature_guard.rb +1 -1
  167. data/lib/rigor/environment/gated_signature_plan.rb +5 -2
  168. data/lib/rigor/environment/hkt_registry_holder.rb +6 -0
  169. data/lib/rigor/environment/lockfile_resolver.rb +28 -12
  170. data/lib/rigor/environment/member_consistency/comparator.rb +18 -1
  171. data/lib/rigor/environment/member_consistency.rb +56 -7
  172. data/lib/rigor/environment/missing_gem_constant_index.rb +3 -2
  173. data/lib/rigor/environment/project_path_gems.rb +124 -0
  174. data/lib/rigor/environment/rbs_collection_discovery.rb +123 -8
  175. data/lib/rigor/environment/rbs_coverage_report.rb +12 -11
  176. data/lib/rigor/environment/rbs_hierarchy.rb +12 -15
  177. data/lib/rigor/environment/rbs_loader.rb +772 -110
  178. data/lib/rigor/environment/reflection.rb +7 -7
  179. data/lib/rigor/environment/required_features.rb +0 -3
  180. data/lib/rigor/environment/ruby_library_roots.rb +1 -1
  181. data/lib/rigor/environment/vendored_signature_plan.rb +153 -0
  182. data/lib/rigor/environment.rb +169 -42
  183. data/lib/rigor/flow_contribution/merger.rb +4 -4
  184. data/lib/rigor/inference/acceptance.rb +136 -40
  185. data/lib/rigor/inference/alias_mutation_floor/foreign_locals.rb +117 -0
  186. data/lib/rigor/inference/alias_mutation_floor/memos.rb +85 -0
  187. data/lib/rigor/inference/alias_mutation_floor/reach.rb +119 -0
  188. data/lib/rigor/inference/alias_mutation_floor/site_collector.rb +252 -0
  189. data/lib/rigor/inference/alias_mutation_floor.rb +339 -0
  190. data/lib/rigor/inference/anonymous_meta_class.rb +18 -0
  191. data/lib/rigor/inference/block_auto_splat.rb +1 -1
  192. data/lib/rigor/inference/block_parameter_binder.rb +2 -3
  193. data/lib/rigor/inference/block_run_count.rb +185 -0
  194. data/lib/rigor/inference/budget_trace.rb +8 -2
  195. data/lib/rigor/inference/builtins/array_catalog.rb +2 -2
  196. data/lib/rigor/inference/builtins/exception_catalog.rb +2 -2
  197. data/lib/rigor/inference/builtins/numeric_catalog.rb +2 -5
  198. data/lib/rigor/inference/builtins/random_catalog.rb +2 -2
  199. data/lib/rigor/inference/callee_mutation_chain.rb +88 -77
  200. data/lib/rigor/inference/callee_write_census.rb +815 -0
  201. data/lib/rigor/inference/captured_locals.rb +2 -0
  202. data/lib/rigor/inference/census_foreign_block.rb +354 -0
  203. data/lib/rigor/inference/clause_widening.rb +1 -1
  204. data/lib/rigor/inference/closure_escape_analyzer.rb +11 -4
  205. data/lib/rigor/inference/construction_escape.rb +258 -0
  206. data/lib/rigor/inference/declaration_walk/traversal.rb +1 -1
  207. data/lib/rigor/inference/declaration_walk.rb +4 -0
  208. data/lib/rigor/inference/deep_erasure.rb +39 -7
  209. data/lib/rigor/inference/def_handle.rb +1 -1
  210. data/lib/rigor/inference/def_node_resolver.rb +33 -15
  211. data/lib/rigor/inference/def_return_typer.rb +47 -5
  212. data/lib/rigor/inference/definer_resolution.rb +19 -9
  213. data/lib/rigor/inference/definers_index.rb +162 -0
  214. data/lib/rigor/inference/direct_ivar_writes.rb +300 -0
  215. data/lib/rigor/inference/element_read_widening.rb +49 -3
  216. data/lib/rigor/inference/error_info.rb +16 -0
  217. data/lib/rigor/inference/expression_typer.rb +1559 -320
  218. data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
  219. data/lib/rigor/inference/flow_tracer.rb +31 -14
  220. data/lib/rigor/inference/fork_map.rb +3 -0
  221. data/lib/rigor/inference/fresh_frame_blocks.rb +52 -1
  222. data/lib/rigor/inference/global_write_census.rb +81 -5
  223. data/lib/rigor/inference/global_writer_reach.rb +260 -0
  224. data/lib/rigor/inference/hkt_body.rb +1 -2
  225. data/lib/rigor/inference/hkt_body_parser.rb +3 -5
  226. data/lib/rigor/inference/hkt_reducer.rb +2 -2
  227. data/lib/rigor/inference/hkt_registry.rb +3 -5
  228. data/lib/rigor/inference/in_effect_refinements.rb +7 -0
  229. data/lib/rigor/inference/indexed_narrowing.rb +61 -15
  230. data/lib/rigor/inference/intervening_ivar_reset.rb +26 -4
  231. data/lib/rigor/inference/ivar_writer_census.rb +154 -8
  232. data/lib/rigor/inference/key_presence_guard.rb +51 -24
  233. data/lib/rigor/inference/last_line/self_evidence.rb +30 -0
  234. data/lib/rigor/inference/last_line.rb +5 -2
  235. data/lib/rigor/inference/loop_value_record.rb +66 -14
  236. data/lib/rigor/inference/match_rebinding/frame.rb +57 -0
  237. data/lib/rigor/inference/match_rebinding/yielding_match.rb +101 -0
  238. data/lib/rigor/inference/match_rebinding.rb +28 -5
  239. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +1 -1
  240. data/lib/rigor/inference/method_dispatcher/argument_distribution.rb +40 -0
  241. data/lib/rigor/inference/method_dispatcher/block_folding.rb +44 -30
  242. data/lib/rigor/inference/method_dispatcher/class_keyed_block_params.rb +65 -0
  243. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +3 -3
  244. data/lib/rigor/inference/method_dispatcher/contested_overloads.rb +89 -0
  245. data/lib/rigor/inference/method_dispatcher/core_enumerable_receiver.rb +44 -0
  246. data/lib/rigor/inference/method_dispatcher/declared_constructor.rb +151 -0
  247. data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +1 -1
  248. data/lib/rigor/inference/method_dispatcher/imprecise_argument.rb +2 -2
  249. data/lib/rigor/inference/method_dispatcher/interface_shape.rb +181 -0
  250. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -1
  251. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +1 -1
  252. data/lib/rigor/inference/method_dispatcher/no_match_fallback.rb +54 -0
  253. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +51 -27
  254. data/lib/rigor/inference/method_dispatcher/random_folding.rb +1 -1
  255. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +606 -78
  256. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +1 -1
  257. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +2 -2
  258. data/lib/rigor/inference/method_dispatcher/self_substitute.rb +3 -3
  259. data/lib/rigor/inference/method_dispatcher/set_folding.rb +9 -7
  260. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +467 -84
  261. data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +0 -1
  262. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +50 -17
  263. data/lib/rigor/inference/method_dispatcher/singleton_type_params.rb +104 -0
  264. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -5
  265. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +4 -6
  266. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  267. data/lib/rigor/inference/method_dispatcher.rb +123 -25
  268. data/lib/rigor/inference/method_parameter_binder.rb +136 -56
  269. data/lib/rigor/inference/module_function_state/spellings.rb +2 -2
  270. data/lib/rigor/inference/module_function_state.rb +24 -3
  271. data/lib/rigor/inference/multi_target_binder.rb +2 -2
  272. data/lib/rigor/inference/mutation_rejoin.rb +240 -4
  273. data/lib/rigor/inference/mutation_widening.rb +199 -29
  274. data/lib/rigor/inference/narrowing.rb +238 -50
  275. data/lib/rigor/inference/operand_effects.rb +43 -2
  276. data/lib/rigor/inference/optimistic_origin.rb +134 -9
  277. data/lib/rigor/inference/origin_lookup.rb +1 -1
  278. data/lib/rigor/inference/out_buffer_mutation.rb +72 -0
  279. data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
  280. data/lib/rigor/inference/pre_eval_constants.rb +1 -1
  281. data/lib/rigor/inference/precision_scanner.rb +1 -1
  282. data/lib/rigor/inference/project_method_ownership.rb +30 -9
  283. data/lib/rigor/inference/project_patched_methods.rb +65 -13
  284. data/lib/rigor/inference/project_patched_scanner.rb +120 -25
  285. data/lib/rigor/inference/project_patched_visibility.rb +39 -3
  286. data/lib/rigor/inference/project_yielder.rb +11 -17
  287. data/lib/rigor/inference/rbs_type_translator.rb +68 -15
  288. data/lib/rigor/inference/receiver_alias.rb +20 -0
  289. data/lib/rigor/inference/receiver_writer_call.rb +67 -0
  290. data/lib/rigor/inference/recursion_rebinding.rb +157 -0
  291. data/lib/rigor/inference/refined_dispatch/constructed_class.rb +57 -17
  292. data/lib/rigor/inference/refined_dispatch/own_method.rb +51 -16
  293. data/lib/rigor/inference/refined_dispatch.rb +13 -0
  294. data/lib/rigor/inference/reflective_ivar_write.rb +19 -11
  295. data/lib/rigor/inference/remover_holder_scan.rb +287 -0
  296. data/lib/rigor/inference/remover_narrowing.rb +224 -0
  297. data/lib/rigor/inference/rewrite_mutation.rb +53 -2
  298. data/lib/rigor/inference/scope_indexer/certainty.rb +0 -1
  299. data/lib/rigor/inference/scope_indexer/class_cvars_collector.rb +13 -6
  300. data/lib/rigor/inference/scope_indexer/constant_ivar_value.rb +93 -0
  301. data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +70 -26
  302. data/lib/rigor/inference/scope_indexer/derived_param_value.rb +112 -0
  303. data/lib/rigor/inference/scope_indexer/empty_seed.rb +99 -0
  304. data/lib/rigor/inference/scope_indexer/ivar_contributions.rb +295 -0
  305. data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +136 -9
  306. data/lib/rigor/inference/scope_indexer.rb +2213 -226
  307. data/lib/rigor/inference/self_call_ivar_writes.rb +103 -12
  308. data/lib/rigor/inference/self_preserving_block.rb +87 -0
  309. data/lib/rigor/inference/set_mutation.rb +189 -0
  310. data/lib/rigor/inference/singleton_hook_decline.rb +42 -8
  311. data/lib/rigor/inference/singleton_object_constant.rb +32 -2
  312. data/lib/rigor/inference/statement_evaluator.rb +3268 -358
  313. data/lib/rigor/inference/string_mutation.rb +14 -0
  314. data/lib/rigor/inference/struct_fold_safety.rb +4 -6
  315. data/lib/rigor/inference/synthetic_method_scanner.rb +40 -17
  316. data/lib/rigor/inference/toplevel_def_census.rb +67 -0
  317. data/lib/rigor/inference/unknown_store_widening.rb +103 -4
  318. data/lib/rigor/inference/unthreaded_rebinds.rb +2 -3
  319. data/lib/rigor/inference/untyped_rejoin.rb +1 -1
  320. data/lib/rigor/inference/void_tail_summary.rb +29 -9
  321. data/lib/rigor/language_server/buffer_table.rb +3 -3
  322. data/lib/rigor/language_server/completion_provider.rb +26 -20
  323. data/lib/rigor/language_server/config_warning_announcer.rb +28 -0
  324. data/lib/rigor/language_server/diagnostic_publisher.rb +8 -3
  325. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  326. data/lib/rigor/language_server/folding_range_provider.rb +1 -2
  327. data/lib/rigor/language_server/hover_provider.rb +1 -7
  328. data/lib/rigor/language_server/hover_renderer.rb +6 -8
  329. data/lib/rigor/language_server/project_context.rb +39 -1
  330. data/lib/rigor/language_server/publish_batcher.rb +3 -4
  331. data/lib/rigor/language_server/selection_range_provider.rb +2 -4
  332. data/lib/rigor/language_server/server.rb +7 -3
  333. data/lib/rigor/language_server/signature_help_provider.rb +4 -4
  334. data/lib/rigor/lens/claims/comparison.rb +78 -0
  335. data/lib/rigor/lens/claims/request.rb +131 -0
  336. data/lib/rigor/lens/claims/type_reader.rb +216 -0
  337. data/lib/rigor/lens/claims.rb +208 -0
  338. data/lib/rigor/lens/contract_reader.rb +5 -0
  339. data/lib/rigor/lens/document.rb +1 -1
  340. data/lib/rigor/lens/file_reader.rb +21 -8
  341. data/lib/rigor/lens/plugins.rb +1 -1
  342. data/lib/rigor/lens/run.rb +8 -0
  343. data/lib/rigor/lens.rb +1 -1
  344. data/lib/rigor/library_probe.rb +68 -0
  345. data/lib/rigor/mcp/server.rb +73 -2
  346. data/lib/rigor/mcp.rb +1 -1
  347. data/lib/rigor/memo_audit.rb +282 -0
  348. data/lib/rigor/plugin/access_denied_error.rb +1 -2
  349. data/lib/rigor/plugin/base.rb +15 -2
  350. data/lib/rigor/plugin/bundled_catalog.rb +1 -12
  351. data/lib/rigor/plugin/io_boundary.rb +26 -4
  352. data/lib/rigor/plugin/loader.rb +25 -8
  353. data/lib/rigor/plugin/macro/heredoc_template.rb +7 -8
  354. data/lib/rigor/plugin/macro/trait_registry.rb +6 -9
  355. data/lib/rigor/plugin/manifest.rb +16 -16
  356. data/lib/rigor/plugin/registry.rb +16 -5
  357. data/lib/rigor/plugin/services.rb +2 -3
  358. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -0
  359. data/lib/rigor/plugin/trust_policy.rb +48 -2
  360. data/lib/rigor/plugin/type_node_resolver.rb +2 -4
  361. data/lib/rigor/plugin.rb +6 -8
  362. data/lib/rigor/plugin_gap_advisory.rb +5 -1
  363. data/lib/rigor/project_environment.rb +16 -2
  364. data/lib/rigor/protection/analysis_guard.rb +1 -1
  365. data/lib/rigor/protection/discovery_seed.rb +44 -5
  366. data/lib/rigor/protection/mutation_cache.rb +4 -1
  367. data/lib/rigor/protection/mutator.rb +1 -1
  368. data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
  369. data/lib/rigor/rbs_extended/hkt_directives.rb +6 -10
  370. data/lib/rigor/rbs_extended/reporter.rb +27 -3
  371. data/lib/rigor/rbs_extended.rb +2 -3
  372. data/lib/rigor/reflection/constant_ancestors.rb +81 -22
  373. data/lib/rigor/reflection/declaration_collectors.rb +55 -0
  374. data/lib/rigor/reflection/declarations.rb +58 -0
  375. data/lib/rigor/reflection/project_method.rb +177 -0
  376. data/lib/rigor/reflection.rb +2 -0
  377. data/lib/rigor/rooted_glob.rb +32 -1
  378. data/lib/rigor/run_scoped_slots.rb +38 -0
  379. data/lib/rigor/runtime/jit.rb +1 -1
  380. data/lib/rigor/scope/discovery_index.rb +168 -8
  381. data/lib/rigor/scope/resolution_chain/relevance.rb +14 -1
  382. data/lib/rigor/scope/resolution_chain.rb +901 -54
  383. data/lib/rigor/scope.rb +644 -37
  384. data/lib/rigor/sig_gen/bot_proposal_guard.rb +306 -0
  385. data/lib/rigor/sig_gen/classification.rb +18 -1
  386. data/lib/rigor/sig_gen/effect_annotation.rb +3 -3
  387. data/lib/rigor/sig_gen/generator.rb +588 -66
  388. data/lib/rigor/sig_gen/generic_header_scan.rb +189 -0
  389. data/lib/rigor/sig_gen/inline_declarations.rb +2 -2
  390. data/lib/rigor/sig_gen/layout_index.rb +3 -3
  391. data/lib/rigor/sig_gen/method_candidate.rb +37 -4
  392. data/lib/rigor/sig_gen/observation_collector.rb +14 -7
  393. data/lib/rigor/sig_gen/observed_call.rb +1 -1
  394. data/lib/rigor/sig_gen/path_mapper.rb +6 -0
  395. data/lib/rigor/sig_gen/platform_guarded_files.rb +184 -0
  396. data/lib/rigor/sig_gen/renderer.rb +15 -9
  397. data/lib/rigor/sig_gen/skip_reason_catalog.rb +62 -2
  398. data/lib/rigor/sig_gen/type_name_qualification.rb +69 -0
  399. data/lib/rigor/sig_gen/write_reporter.rb +63 -14
  400. data/lib/rigor/sig_gen/write_result.rb +23 -12
  401. data/lib/rigor/sig_gen/writer.rb +238 -37
  402. data/lib/rigor/sig_gen.rb +1 -0
  403. data/lib/rigor/source/forwardable_delegators.rb +122 -0
  404. data/lib/rigor/source/node_children.rb +13 -0
  405. data/lib/rigor/type/acceptance_router.rb +2 -2
  406. data/lib/rigor/type/accepts_result.rb +1 -1
  407. data/lib/rigor/type/app.rb +4 -6
  408. data/lib/rigor/type/combinator.rb +173 -21
  409. data/lib/rigor/type/constant.rb +4 -5
  410. data/lib/rigor/type/hash_shape.rb +21 -40
  411. data/lib/rigor/type/interface.rb +60 -0
  412. data/lib/rigor/type/union.rb +1 -1
  413. data/lib/rigor/type.rb +1 -0
  414. data/lib/rigor/type_node/identifier.rb +1 -2
  415. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  416. data/lib/rigor/type_node/string_literal.rb +1 -2
  417. data/lib/rigor/type_node/symbol_literal.rb +1 -2
  418. data/lib/rigor/type_node/union.rb +3 -6
  419. data/lib/rigor/type_of_memo/serving.rb +99 -0
  420. data/lib/rigor/type_of_memo/stats.rb +43 -0
  421. data/lib/rigor/type_of_memo.rb +337 -0
  422. data/lib/rigor/typing_state.rb +1094 -0
  423. data/lib/rigor/value_semantics.rb +1 -10
  424. data/lib/rigor/version.rb +1 -1
  425. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +2 -2
  426. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +2 -3
  427. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -1
  428. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +38 -36
  429. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +4 -2
  430. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +6 -4
  431. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +1 -1
  432. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -5
  433. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +0 -1
  434. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +2 -1
  435. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +3 -3
  436. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +115 -36
  437. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +3 -6
  438. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +25 -1
  439. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
  440. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +16 -6
  441. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +4 -2
  442. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +4 -3
  443. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +2 -1
  444. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +3 -2
  445. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +2 -2
  446. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +21 -31
  447. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +10 -16
  448. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +132 -33
  449. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +20 -6
  450. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +7 -4
  451. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +54 -17
  452. data/plugins/rigor-activerecord/sig/active_record/relation.rbs +19 -5
  453. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +3 -4
  454. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +1 -1
  455. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +11 -14
  456. data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +1 -1
  457. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +1 -1
  458. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/symbolize_keys.rb +102 -0
  459. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +30 -9
  460. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +55 -13
  461. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +15 -21
  462. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +1 -6
  463. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +3 -3
  464. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +1 -3
  465. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +6 -6
  466. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +5 -6
  467. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +1 -1
  468. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/params_shape.rb +2 -3
  469. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -8
  470. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +0 -1
  471. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
  472. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +10 -18
  473. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +0 -1
  474. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +0 -2
  475. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +8 -7
  476. data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +0 -2
  477. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +6 -7
  478. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +12 -2
  479. data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +2 -4
  480. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +2 -5
  481. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +8 -2
  482. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +6 -6
  483. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
  484. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +3 -8
  485. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +2 -3
  486. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -8
  487. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +200 -4
  488. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -3
  489. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +1 -1
  490. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +4 -11
  491. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +4 -3
  492. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +2 -1
  493. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +5 -5
  494. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +2 -2
  495. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +3 -4
  496. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +6 -8
  497. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +3 -2
  498. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +4 -5
  499. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +6 -7
  500. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +210 -3
  501. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +146 -7
  502. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +2 -2
  503. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +52 -34
  504. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +97 -24
  505. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +2 -3
  506. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +16 -16
  507. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +4 -8
  508. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +4 -4
  509. data/sig/rigor/analysis/baseline.rbs +0 -3
  510. data/sig/rigor/analysis/check_rules/rule_walk.rbs +6 -0
  511. data/sig/rigor/analysis/fact_store.rbs +2 -2
  512. data/sig/rigor/analysis/reachability/scan_cache.rbs +1 -1
  513. data/sig/rigor/cache/descriptor.rbs +14 -0
  514. data/sig/rigor/cli/diff_command.rbs +1 -1
  515. data/sig/rigor/cli/explain_command.rbs +1 -1
  516. data/sig/rigor/effects/registry.rbs +2 -2
  517. data/sig/rigor/environment.rbs +4 -1
  518. data/sig/rigor/inference.rbs +4 -1
  519. data/sig/rigor/plugin/additional_initializer.rbs +0 -5
  520. data/sig/rigor/plugin/base.rbs +53 -18
  521. data/sig/rigor/plugin/effect_attribution.rbs +3 -3
  522. data/sig/rigor/plugin/effect_edge.rbs +1 -1
  523. data/sig/rigor/plugin/effect_entry_points.rbs +3 -3
  524. data/sig/rigor/plugin/interfaces.rbs +51 -0
  525. data/sig/rigor/plugin/io_boundary.rbs +3 -3
  526. data/sig/rigor/plugin/manifest.rbs +9 -9
  527. data/sig/rigor/plugin/node_context.rbs +21 -0
  528. data/sig/rigor/plugin/protocol_contract.rbs +3 -7
  529. data/sig/rigor/reflection.rbs +37 -0
  530. data/sig/rigor/scope.rbs +43 -4
  531. data/sig/rigor/trinary.rbs +1 -0
  532. data/sig/rigor/type.rbs +9 -3
  533. data/sig/rigor.rbs +3 -2
  534. data/skills/rigor-ask/SKILL.md +1 -0
  535. data/skills/rigor-plugin-author/SKILL.md +4 -4
  536. data/skills/rigor-plugin-author/references/02-walker-and-types.md +5 -5
  537. data/skills/rigor-plugin-review/SKILL.md +3 -3
  538. data/skills/rigor-project-init/references/01-detect.md +1 -0
  539. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +17 -2
  540. data/skills/rigor-project-init/references/06-agent-contract.md +4 -4
  541. data/skills/rigor-rbs-contract/SKILL.md +147 -0
  542. data/skills/rigor-rbs-contract/references/01-finding-candidates.md +212 -0
  543. data/skills/rigor-rbs-contract/references/02-contract-rules.md +157 -0
  544. data/skills/rigor-rbs-contract/references/03-target-own-code.md +90 -0
  545. data/skills/rigor-rbs-contract/references/04-target-gem-rbs-collection.md +210 -0
  546. data/skills/rigor-rbs-contract/references/05-declaration-mutants.md +182 -0
  547. data/skills/rigor-rbs-setup/SKILL.md +3 -1
  548. data/skills/rigor-type-oracle/SKILL.md +26 -1
  549. data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +4 -3
  550. metadata +82 -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"
@@ -63,8 +64,10 @@ module Rigor
63
64
 
64
65
  # ADR-89 WD2 — bounds on the persisted return summaries (per-def observed call keys, and total defs
65
66
  # summarised) so the incremental snapshot stays small. A def observed under more keys than the per-def
66
- # cap keeps only the first; a def past the total cap is dropped (its symbol dependents then always
67
- # re-check — the conservative direction).
67
+ # cap keeps none of them (issue #3116): the keys past the cap are unknown, so equal returns at the first
68
+ # eight prove nothing about the ninth key's reader. A def past the total cap keeps none either. Both are
69
+ # harvested as nil ({#return_summaries}), which the session stores as no summary at all, so the def's symbol
70
+ # dependents always re-check — the conservative direction.
68
71
  RETURN_SUMMARY_KEYS_PER_DEF = 8
69
72
  RETURN_SUMMARY_TOTAL_CAP = 4000
70
73
 
@@ -87,11 +90,10 @@ module Rigor
87
90
  # forbids Rigor demanding in its place) and the HKT-scan outcome (whose producer is an environment a
88
91
  # nothing-changed recheck otherwise has no reason to build). {IncrementalSession} persists what a full
89
92
  # run answers here and hands it back to the next narrowed run.
93
+ #
94
+ # Which rows those are is declared, not listed: every {RunLevelFacts} fact marked `replay`.
90
95
  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
- )
96
+ Cache::IncrementalSnapshot::RunLevelRows.new(**@snapshots.to_h.slice(*RunLevelFacts.replayable_names))
95
97
  end
96
98
 
97
99
  # ADR-46 — the per-file cross-file read records this run captured (empty unless
@@ -280,8 +282,7 @@ module Rigor
280
282
  # @param explain — surface fail-soft fallback events as `:info` diagnostics.
281
283
  # @param cache_store — the persistent cache the runner exposes to producers
282
284
  # (`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.
285
+ # `--no-cache` flag wires `nil` through.
285
286
  # @param workers — ADR-15 Phase 4b — when greater than zero, per-file analysis dispatches
286
287
  # across a pool of N workers. Default `0` keeps the sequential code path bit-for-bit unchanged.
287
288
  # Controlled via the `RIGOR_RACTOR_WORKERS` env var or `.rigor.yml` `parallel.workers:` (Phase 4c,
@@ -352,8 +353,8 @@ module Rigor
352
353
  @prebuilt = prebuilt
353
354
  @environment_override = environment
354
355
  # 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).
356
+ # `analyze_file` records each file's cross-file reads into `file_dependencies`; the incremental
357
+ # cache consumes them.
357
358
  @record_dependencies = record_dependencies
358
359
  # ADR-24 slice 4a — opt-in unresolved-implicit-self-call recording. Off by default; when true,
359
360
  # `analyze_file` activates the engine choke-point recorder and collects each file's misses into
@@ -513,6 +514,13 @@ module Rigor
513
514
  # entry across a run boundary (LSP re-check, ADR-62 warm loop); nil until the first run so
514
515
  # runner-less probes keep the per-file fallback.
515
516
  @run_generation = nil
517
+ # The return memo bucket the last `run` filled, kept for {#return_summaries} once the run has released the
518
+ # thread's slot; nil before the first run.
519
+ @return_memo = nil
520
+ # The `[path, node_id] => Prism::DefNode` table the last `run`'s {Inference::DefNodeResolver} filled, kept for
521
+ # the same reader.
522
+ @resolved_def_nodes = nil
523
+ @probe_reads = {}.freeze
516
524
  build_collaborators
517
525
  end
518
526
 
@@ -526,6 +534,9 @@ module Rigor
526
534
  # errors. The Environment is built once at run start through `Environment.for_project` so all files
527
535
  # share the same RBS load.
528
536
  def run(paths = @configuration.paths)
537
+ # ADR-129 WD5 (#2785) — a mismatch left on the thread by typing after an earlier run (its return summaries, a
538
+ # language-server hover) fails here rather than reaching this run's first file as a crash row.
539
+ Inference::MemoAudit.raise_undrained!
529
540
  # One per-run file-digest memo spans the whole run, so a path is SHA-256'd at most once across the
530
541
  # run-diagnostics dependency descriptor, its `fresh?` validation, the RBS signature tree, and every
531
542
  # plugin producer's watched-glob validation (they overlap heavily on the warm path). The nested ADR-85
@@ -534,7 +545,17 @@ module Rigor
534
545
  # ADR-87 WD1 — `cache.validation` (or the RIGOR_STRICT_VALIDATION env, which wins) selects the
535
546
  # freshness path for this run; the `auto` default resolves strict in CI (#190), stat-first elsewhere.
536
547
  Cache::FileDigest.with_run(strict: @configuration.cache_validation_strict?) do
537
- Inference::DefNodeResolver.with_run { run_analysis(paths) }
548
+ Inference::DefNodeResolver.with_run do
549
+ run_analysis(paths)
550
+ ensure
551
+ # Issue #3160 — the memo keys an unchanged file's def on the node the resolver minted for its handle.
552
+ @resolved_def_nodes = Inference::DefNodeResolver.resolved_nodes
553
+ end
554
+ ensure
555
+ # ADR-89 WD2 — {#return_summaries} reads the return memo after the run, so the runner keeps the bucket the
556
+ # run filled before the thread's run-scoped slots are released (issues #2412, #2399).
557
+ @return_memo = Inference::ExpressionTyper.current_return_memo
558
+ RunScopedSlots.release
538
559
  end
539
560
  end
540
561
 
@@ -548,6 +569,21 @@ module Rigor
548
569
  end
549
570
  private :reset_analysis_attribution
550
571
 
572
+ # Issue #2392 — the sequential half of the per-run reset, beside {#reset_analysis_attribution}: the
573
+ # dependency graph and per-file effect collections a `--workers 0` run records, and the effect-slot state a
574
+ # served run leaves behind. The loader is the one that matters: a served run's collections loader would
575
+ # otherwise feed the next analysing run the previous run's collections. Runs before any `adopt_*`, which the
576
+ # served lanes call later in the same run, so an adopted slot is this run's own.
577
+ def reset_sequential_run_tables
578
+ @file_dependencies = {}
579
+ @file_effects = {}
580
+ @cached_effect_sources = nil
581
+ @cached_effect_ancestry = nil
582
+ @cached_collections_loader = nil
583
+ @effects_served_from_cache = false
584
+ end
585
+ private :reset_sequential_run_tables
586
+
551
587
  def run_analysis(paths)
552
588
  Inference::MethodDispatcher::FileFolding.fold_platform_specific_paths =
553
589
  @configuration.fold_platform_specific_paths
@@ -561,6 +597,10 @@ module Rigor
561
597
  @project_discovery_expansion = project_discovery_expansion(paths, expansion)
562
598
  @discovery_widened = widen_discovery_to_project?(paths)
563
599
  @snapshots.reset_for_run
600
+ # Issue #2392 — the coordinator's run-accumulated tables (disclosures, trust refusals, the dependency and
601
+ # effect graphs) are per run too: a reused Runner must not re-emit or sum the previous run's rows.
602
+ @pool_coordinator.reset_for_run
603
+ reset_sequential_run_tables
564
604
  # Per-run reset of the deferred-discovery memo (see `#ensure_project_discovery`).
565
605
  @project_discovery_done = false
566
606
  # #392 — re-derive the template units each run: their source files can change between two runs of
@@ -762,15 +802,29 @@ module Rigor
762
802
  end
763
803
 
764
804
  # ADR-89 WD2 — the per-method observed-key return summaries the run's ADR-84 return memo just captured.
765
- # For each project method with live memo entries, a bounded `{ keys:, returns:, effects: }` summary the
766
- # incremental session persists: `keys` the observed `[receiver, arg_types]` type tuples, `returns` their
805
+ # For each project method the run observed, a bounded `{ keys:, returns:, effects:, guard_constants: }` summary
806
+ # the incremental session persists: `keys` the observed `[receiver, arg_types]` type tuples, `returns` their
767
807
  # `describe(:short)` descriptors, `effects` the content-mutated parameter positions (a caller-visible
768
- # arg-flooring surface). Only meaningful right after a run populated the memo (baseline / recheck),
808
+ # arg-flooring surface), `guard_constants` the published-constant spellings its body reads ({#guard_constants}).
809
+ # Only meaningful right after a run populated the memo (baseline / recheck),
769
810
  # before the bucket rolls. Keys are held live here; the session Marshals them for the snapshot.
770
811
  #
771
- # @return `{ [path, "Class#method" | "Class.method"] => { keys:, returns:, effects: } }`.
812
+ # Issue #3160 — the session reads the harvest as EVERY key the files this run analysed observed each def
813
+ # under, so whatever cannot be that says so instead of looking like a short key list:
814
+ #
815
+ # - a def observed under keys this run cannot list maps to nil: more keys than
816
+ # {RETURN_SUMMARY_KEYS_PER_DEF} (issue #3116), a call whose result the memo refused to store, or a def
817
+ # past {RETURN_SUMMARY_TOTAL_CAP};
818
+ # - a def absent from the result was observed by no analysed file, wherever it is defined: an unchanged
819
+ # file's def is found through the node its {Inference::DefHandle} resolved to;
820
+ # - the whole result is nil for a pool run, whose workers typed the calls in memos this process never sees.
821
+ #
822
+ # @return the summaries keyed by `[path, "Class#method"]` (or `"Class.method"`), each holding `keys:`,
823
+ # `returns:` and `effects:`, or nil; nil for a pool run.
772
824
  def return_summaries
773
- memo = Inference::ExpressionTyper.harvest_return_memo
825
+ return nil if pool_mode?
826
+
827
+ memo = Inference::ExpressionTyper.harvest_return_memo(@return_memo)
774
828
  return {} if memo.empty?
775
829
 
776
830
  result = {}
@@ -779,37 +833,89 @@ module Rigor
779
833
  "#", memo, effects_evaluator)
780
834
  collect_return_summaries(result, @project_discovered_singleton_def_nodes,
781
835
  @project_discovered_singleton_def_sources, ".", memo, effects_evaluator)
836
+ # ADR-129 WD5 (#2785) — typing after the run, under `content_effects`' rescue.
837
+ Inference::MemoAudit.raise_undrained!
782
838
  result
783
839
  end
784
840
 
785
841
  # Folds one def-node/def-source table pair's memo entries into the return-summary result under
786
- # `separator`. A live def node (a cold / re-walked file) is the identity every cross-file caller
787
- # resolved through, so `memo[node]` holds those callers' observed keys; a {DefHandle} (an unchanged
788
- # file on the warm path) never keyed a memo entry, so it is skipped (its summary is carried over from
789
- # the snapshot). Bounded per def and in total so the snapshot stays small.
842
+ # `separator`. The node the memo keyed is the identity every cross-file caller resolved through: the live
843
+ # def node of a cold / re-walked file, or the one the resolver minted for an unchanged file's
844
+ # {Inference::DefHandle} ({#observed_def_node}).
790
845
  def collect_return_summaries(result, nodes, sources, separator, memo, effects_evaluator)
846
+ summarised = result.count { |_pair, summary| summary }
791
847
  nodes.each do |class_name, methods|
792
- methods.each do |method_name, node|
793
- next if node.is_a?(Inference::DefHandle)
794
-
795
- entries = memo[node]
796
- next if entries.nil? || entries.empty?
848
+ methods.each do |method_name, slot|
849
+ node = observed_def_node(slot)
850
+ next unless memo.key?(node)
797
851
 
798
852
  path_line = sources.dig(class_name, method_name)
799
853
  next if path_line.nil?
800
854
 
801
- break if result.size >= RETURN_SUMMARY_TOTAL_CAP
802
-
803
- capped = entries.first(RETURN_SUMMARY_KEYS_PER_DEF)
804
- result[[path_line.split(":", 2).first, "#{class_name}#{separator}#{method_name}"]] = {
805
- keys: capped.map { |entry| [entry.receiver, entry.arg_types] },
806
- returns: capped.map { |entry| entry.result.describe(:short) },
807
- effects: content_effects(effects_evaluator, node)
808
- }
855
+ summary = summarised < RETURN_SUMMARY_TOTAL_CAP ? return_summary(memo[node], effects_evaluator, node) : nil
856
+ summarised += 1 if summary
857
+ result[[path_line.split(":", 2).first, "#{class_name}#{separator}#{method_name}"]] = summary
809
858
  end
810
859
  end
811
860
  end
812
861
 
862
+ # The node the run's return memo keyed `slot`'s def on, or nil when no call resolved it: the live node, or the
863
+ # one the run's resolver minted for a {Inference::DefHandle}.
864
+ def observed_def_node(slot)
865
+ return slot unless slot.is_a?(Inference::DefHandle)
866
+
867
+ @resolved_def_nodes&.[]([slot.path, slot.node_id])
868
+ end
869
+
870
+ # One def's summary, or nil when `entries` are not every key it was observed under: the memo refused to store
871
+ # a call of it (so `entries` may be empty), or there are more than {RETURN_SUMMARY_KEYS_PER_DEF} (issue
872
+ # #3116). A splat call's key (issue #1237) is an entry like any other and counts toward the cap.
873
+ def return_summary(entries, effects_evaluator, node)
874
+ return nil if entries.empty? || entries.size > RETURN_SUMMARY_KEYS_PER_DEF
875
+ return nil if Inference::ExpressionTyper.return_memo_refused?(@return_memo, node)
876
+
877
+ { keys: entries.map { |entry| return_summary_key(entry) },
878
+ returns: entries.map { |entry| entry.result.describe(:short) },
879
+ effects: content_effects(effects_evaluator, node),
880
+ guard_constants: guard_constants(node) }
881
+ end
882
+
883
+ # Issue #3186 — the constant spellings in `node`'s body that the truthiness rule's one interprocedural hop
884
+ # ({CheckRules::PublishedConstantGuard}) could answer true for, or nil when they cannot be listed. A reader's
885
+ # `flow.always-truthy-condition` withholds when a def it calls reads a constant another file published,
886
+ # whatever the def returns, so the ADR-89 WD2 gate compares these as well as the returns; nil never compares
887
+ # equal, so it keeps the def's readers.
888
+ #
889
+ # The hop asks `Scope#published_constant?`, which answers true only for a spelling whose last segment the
890
+ # project publishes or the reader's own file assigns from a published constant, and each of those is a segment
891
+ # some project file writes. So the spellings whose last segment no file writes (a class name, as a rule) are
892
+ # left out: they answer false for every reader, and keeping them would fail the gate on every edit that adds a
893
+ # callee. The filter is this run's write census, so a census that moved fails the comparison.
894
+ def guard_constants(node)
895
+ written = constant_write_segments
896
+ CheckRules::PublishedConstantGuard.body_constant_spellings(node.body).select do |spelling|
897
+ written.include?(spelling.split("::").last)
898
+ end
899
+ rescue StandardError
900
+ nil
901
+ end
902
+
903
+ # The last segments of every constant name the run's write census holds, a wildcard key's included.
904
+ def constant_write_segments
905
+ unless @constant_write_segments_of.equal?(@project_constant_writes)
906
+ @constant_write_segments_of = @project_constant_writes
907
+ @constant_write_segments = @project_constant_writes.each_key.to_set { |name| name.split("::").last }
908
+ end
909
+ @constant_write_segments
910
+ end
911
+
912
+ # One observed call key: `[receiver, arg_types]`, or, for an entry a splat call stored (issue #1237),
913
+ # `[receiver, arg_types, true]`, which {#evaluate_spec_returns} re-drives with no positional correspondence.
914
+ def return_summary_key(entry)
915
+ key = [entry.receiver, entry.arg_types]
916
+ entry.positionals_unknown ? key << true : key
917
+ end
918
+
813
919
  # The content-mutated parameter positions of `node`, or `[]` if the computation raises (defensive: the
814
920
  # effects surface only routes attention, never soundness — a missing set reads as "no floor").
815
921
  def content_effects(effects_evaluator, node)
@@ -828,15 +934,49 @@ module Rigor
828
934
  #
829
935
  # @param paths — analysis roots (nil → the configuration's `paths:`).
830
936
  # @param specs — each `{ class_name:, method_name:, singleton:, keys: [[receiver, args], …] }`.
831
- # @return `{ [class_name, method_name, singleton] => [return_descriptor_or_nil, …] }`.
937
+ # @return the re-evaluated return descriptors, one per key, keyed by `[class_name, method_name, singleton]`.
832
938
  def evaluate_return_types(paths, specs)
833
939
  return {} if specs.empty?
834
940
 
941
+ @probe_reads = {}
942
+ @probe_plugin_reads = {}
943
+ @probe_disclosures = {}
944
+ @probe_guard_constants = {}
835
945
  Cache::FileDigest.with_run(strict: @configuration.cache_validation_strict?) do
836
- Inference::DefNodeResolver.with_run { evaluate_return_types_setup(paths, specs) }
946
+ Inference::DefNodeResolver.with_run do
947
+ evaluate_return_types_setup(paths, specs)
948
+ ensure
949
+ @resolved_def_nodes = Inference::DefNodeResolver.resolved_nodes
950
+ end
837
951
  end
952
+ ensure
953
+ # Issue #3180 — as {#run} does: {#return_summaries} then answers for what the probe observed.
954
+ @return_memo = Inference::ExpressionTyper.current_return_memo
955
+ RunScopedSlots.release
838
956
  end
839
957
 
958
+ # Issue #3180 — what the last {#evaluate_return_types} read while re-evaluating each spec's def at its keys: the
959
+ # spec triple => a {DependencyRecorder::Record} of every def, class and name the evaluation read, the def's own
960
+ # file included (nothing is filtered as a self-read). The incremental session gives these edges to the readers
961
+ # a passing gate skips, since they now reach whatever the edited def reaches. Empty before the first probe.
962
+ attr_reader :probe_reads
963
+
964
+ # Issue #3187 — what the plugins did while the last {#evaluate_return_types} re-evaluated each spec's def, as
965
+ # {#analysis_reads} and {#analysis_disclosures} hold for an analysed file: the spec triple => the boundary rows
966
+ # its evaluation read ({Plugin::IoBoundary.attributing}), and => the run-scoped rows it registered
967
+ # ({RunDisclosureCredit}). A triple whose evaluation read or registered nothing has no key. The incremental
968
+ # session credits the reads to the readers a passing gate skips, as it does {#probe_reads}; a registration
969
+ # keeps the def's readers instead, since no skipped reader could be credited with it, or have it retracted.
970
+ attr_reader :probe_plugin_reads, :probe_disclosures
971
+
972
+ # Issue #3186 — the spec triple => {#guard_constants} of the def the last {#evaluate_return_types} re-evaluated,
973
+ # under that probe's write census. A triple whose def the probe did not find has no key.
974
+ attr_reader :probe_guard_constants
975
+
976
+ # The consumer {#evaluate_spec_returns} records under. It is no file's path, so no read is dropped as a self-read.
977
+ PROBE_CONSUMER = ""
978
+ private_constant :PROBE_CONSUMER
979
+
840
980
  # Builds the discovery index + environment (a real run's prologue, minus per-file analysis) and
841
981
  # re-evaluates each spec's def. Extracted so {#evaluate_return_types}'s `with_run` wrappers stay thin.
842
982
  def evaluate_return_types_setup(paths, specs)
@@ -872,10 +1012,43 @@ module Rigor
872
1012
  return spec[:keys].map { nil } if node.nil?
873
1013
 
874
1014
  scope = seed_project_scope(Scope.empty(environment: environment, source_path: spec[:path]))
875
- spec[:keys].map do |(receiver, arg_types)|
876
- result = scope.user_method_return(node, receiver, arg_types)
877
- result&.describe(:short)
1015
+ triple = [spec[:class_name], spec[:method_name], spec[:singleton]]
1016
+ @probe_guard_constants[triple] = guard_constants(node)
1017
+ returns = nil
1018
+ attributed_probe(triple) do
1019
+ record_guard_spelling_edges(node)
1020
+ returns = spec[:keys].map do |(receiver, arg_types, positionals_unknown)|
1021
+ result = scope.user_method_return(node, receiver, arg_types,
1022
+ positionals_unknown: positionals_unknown || false)
1023
+ result&.describe(:short)
1024
+ end
1025
+ end
1026
+ returns
1027
+ end
1028
+
1029
+ # Issue #3199 — the `constant:` edge for every spelling the truthiness guard's hop would scan in `node`'s body,
1030
+ # recorded into the probe's reads, which every reader the gate skips receives. {#guard_constants} leaves out a
1031
+ # spelling whose last segment no file writes, so an edit that starts probing one (`defined?(B::X)` before any
1032
+ # file assigns `B::X`) passes the gate; the skipped reader's guard never asks about it, and without this edge a
1033
+ # file that later publishes the constant would re-check nobody.
1034
+ def record_guard_spelling_edges(node)
1035
+ CheckRules::PublishedConstantGuard.body_constant_spellings(node.body).each do |spelling|
1036
+ DependencyRecorder.read_last_segment(:constant, spelling)
1037
+ end
1038
+ end
1039
+
1040
+ # Runs one spec's evaluation with its dependency reads recorded ({#probe_reads}) and its plugin reads and
1041
+ # run-scoped registrations attributed to it ({#probe_plugin_reads}, {#probe_disclosures}), as
1042
+ # {#analyze_file_attributed} does for a file.
1043
+ def attributed_probe(triple, &)
1044
+ reads = []
1045
+ disclosures = RunDisclosureCredit.collect do
1046
+ @probe_reads[triple] = Plugin::IoBoundary.attributing(reads) do
1047
+ DependencyRecorder.record_for(PROBE_CONSUMER, &)
1048
+ end
878
1049
  end
1050
+ @probe_plugin_reads[triple] = reads.uniq.freeze unless reads.empty?
1051
+ @probe_disclosures[triple] = disclosures unless disclosures.empty?
879
1052
  end
880
1053
 
881
1054
  # ADR-45 — unchanged-project fast path. Serves the whole run's (pre-severity-profile) diagnostics
@@ -891,7 +1064,10 @@ module Rigor
891
1064
  # #392 — `source_files:` is what a plugin's `source_rbs_synthesizer` is offered at env-build time,
892
1065
  # and a template unit is not Ruby the synthesiser can read (rbs-inline would be handed `.rbx`
893
1066
  # bytes). The `.rb` expansion is the right set here; the units join the ANALYSED set only.
894
- environment = @pool_coordinator.resolve_sequential_environment(source_files: expansion.fetch(:files))
1067
+ # Issue #2883 — not eager: a warm result-cache HIT never types, so it must not build the RBS environment;
1068
+ # {#assemble_run_diagnostics} builds it on the analysis path.
1069
+ environment = @pool_coordinator.resolve_sequential_environment(source_files: expansion.fetch(:files),
1070
+ eager: false)
895
1071
  # Lazy-files descriptor: the cache KEY reads only `gems` + `configs`; the RBS signature-tree `files`
896
1072
  # are digested solely by `run_dependency_descriptor` on a MISS, so a warm HIT never walks the tree.
897
1073
  rbs_descriptor = if environment&.rbs_loader
@@ -1021,17 +1197,12 @@ module Rigor
1021
1197
  nil
1022
1198
  end
1023
1199
 
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
1200
+ # The write half, run only after a miss, so nothing here recomputes anything. Two entries under one
1031
1201
  # key descriptor and one dependency descriptor (#482): the summary a warm run serves from, and the
1032
1202
  # collections the incremental and fail-soft paths read. The summary is written LAST, so a run
1033
1203
  # 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.
1204
+ # re-collects — never a summary whose collections are absent. A collection that will not Marshal
1205
+ # costs the next run its warm start and nothing else.
1035
1206
  def store_effect_collections(descriptor, expansion, rbs_descriptor)
1036
1207
  return if descriptor.nil?
1037
1208
 
@@ -1170,6 +1341,8 @@ module Rigor
1170
1341
  end
1171
1342
 
1172
1343
  def assemble_run_diagnostics(expansion, environment: nil)
1344
+ # Issue #2883 — the analysis path builds the RBS environment before anything types (ADR-129 G1').
1345
+ environment&.prepare_for_typing!
1173
1346
  # Force the deferred cross-file discovery pre-pass on the analysis (miss) path. Memoised, so the
1174
1347
  # eager force in `#run` (recording / subset modes) makes this a no-op. A warm cache HIT never calls
1175
1348
  # `assemble_run_diagnostics`, so it never runs the two whole-project parse passes. Runs over the FULL
@@ -1233,40 +1406,13 @@ module Rigor
1233
1406
  end
1234
1407
  private :template_unit_failure_diagnostics
1235
1408
 
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.
1409
+ # Every remaining post-analysis stream `#assemble_run_diagnostics` appends — the `rbs.coverage.*`
1410
+ # build-failure ladder (widest consequence first: quarantine, env build, definition build #696, HKT scan
1411
+ # #784), its notices, the conformance rows, the reporter streams, the plugin run disclosures (#1051)
1412
+ # and, last, the trust-refusal envelope (#959) — in the contract order ADR-116 WD3's
1413
+ # {RunLevelFacts} declares. Read after `analyze_targets`, so every worker payload has been merged.
1242
1414
  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
1415
+ @diagnostic_aggregator.run_level_diagnostics
1270
1416
  end
1271
1417
  private :post_analysis_diagnostic_streams
1272
1418
 
@@ -1445,7 +1591,7 @@ module Rigor
1445
1591
  # #979 — the signature ROOTS' listings, one glob row each. `rbs_descriptor.files` covers only the
1446
1592
  # `.rbs` files that existed while the run read them, so without these a signature file written after
1447
1593
  # the run left the slot validating fresh and the warm run answered without it.
1448
- globs = rbs_descriptor.globs + template_unit_glob_entries + discovery_glob_entries
1594
+ globs = rbs_descriptor.globs + template_unit_glob_entries + discovery_glob_entries + bundle_sig_glob_entries
1449
1595
  boundary = plugin_boundary_rows
1450
1596
  entries.concat(boundary.files)
1451
1597
  globs.concat(boundary.globs).concat(producer_watch_glob_entries)
@@ -1583,7 +1729,9 @@ module Rigor
1583
1729
  # change time it cannot read, and passes over a `pinned` one.
1584
1730
  BaselineRows = Data.define(:owned, :pinned)
1585
1731
 
1586
- SignatureRoots = Struct.new(:signature_paths)
1732
+ # A loader's two readers {Cache::RbsDescriptor} takes signature roots from. `core_root` (issue #2517) is the
1733
+ # selected core, which the slot key names by path only, so its files are `owned` rows like the project's own.
1734
+ SignatureRoots = Struct.new(:signature_paths, :core_root)
1587
1735
  private_constant :SignatureRoots
1588
1736
 
1589
1737
  # @param files — the run's analysed-path set.
@@ -1591,6 +1739,9 @@ module Rigor
1591
1739
  return nil if pool_mode? || !@collect_stats || @analyzed_files.empty?
1592
1740
 
1593
1741
  own, pinned = signature_rows
1742
+ # Issue #2987 — the bundle's `sig/` listing is identified by the gems' directory, outside the project, so a
1743
+ # `pinned` row: the write guard passes over it where it would refuse an `owned` one it cannot stat.
1744
+ pinned = Cache::Descriptor.new(files: pinned.files, globs: (pinned.globs + bundle_sig_glob_entries).uniq)
1594
1745
  BaselineRows.new(
1595
1746
  owned: Cache::Descriptor.new(
1596
1747
  files: own.files + kind_entries(signature_root_candidates) +
@@ -1604,8 +1755,9 @@ module Rigor
1604
1755
 
1605
1756
  # The signature tree's rows, the project's own and the rest.
1606
1757
  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), "") }
1758
+ roots = SignatureRoots.new(@snapshots.signature_paths, @configuration.core_root)
1759
+ own = (signature_root_candidates + [@configuration.core_root].compact)
1760
+ .map { |root| File.join(File.absolute_path(root.to_s), "") }
1609
1761
  own_files, other_files = Cache::RbsDescriptor.file_entries(roots, comparator: :stat)
1610
1762
  .partition { |row| within?(row.path, own) }
1611
1763
  own_globs, other_globs = Cache::RbsDescriptor.glob_entries(roots).partition { |row| within?(row.root, own) }
@@ -1618,12 +1770,12 @@ module Rigor
1618
1770
  roots.any? { |root| directory.start_with?(root) }
1619
1771
  end
1620
1772
 
1621
- # The project's own signature roots, whether or not they exist: the configured `signature_paths:`, or the
1622
- # `sig` directory `Environment.default_signature_paths` auto-detects when none are configured. The
1623
- # signature rows' listing rows cover a file appearing under a root that existed; only a row for the root
1624
- # itself covers it appearing.
1773
+ # The project's own signature roots, whether or not they exist:
1774
+ # {Environment.project_signature_root_candidates} — the configured `signature_paths:`, or the `sig`
1775
+ # directory it defaults to when none are configured (issue #1619). The signature rows' listing rows
1776
+ # cover a file appearing under a root that existed; only a row for the root itself covers it appearing.
1625
1777
  def signature_root_candidates
1626
- @configuration.signature_paths || ["sig"]
1778
+ Environment.project_signature_root_candidates(@configuration.signature_paths)
1627
1779
  end
1628
1780
 
1629
1781
  # One names-mode listing row per directory the discovery widening globbed, so a file appearing there is seen
@@ -1638,6 +1790,22 @@ module Rigor
1638
1790
  end
1639
1791
  end
1640
1792
 
1793
+ # Issue #2987 — one names-mode listing row per bundle `sig/` layout ({Environment::BundleSigDiscovery.discover}'s
1794
+ # two globs). The signature rows list only the directories the loader read, so a gem that GAINS a `sig/`
1795
+ # (or loses it) while its version and lock entry stand still sits in none of them, and the run-result
1796
+ # slot kept serving the environment built without (or with) it.
1797
+ def bundle_sig_glob_entries
1798
+ root = Environment::BundleSigDiscovery.resolve_bundle_path(
1799
+ bundle_path: @configuration.bundler_bundle_path, project_root: Dir.pwd,
1800
+ auto_detect: @configuration.bundler_auto_detect
1801
+ )
1802
+ return [] if root.nil?
1803
+
1804
+ Environment::BundleSigDiscovery::SIG_GLOBS.map do |pattern|
1805
+ Cache::Descriptor::GlobEntry.compute(root: root.to_s, pattern: pattern, mode: :names)
1806
+ end
1807
+ end
1808
+
1641
1809
  def unanalysed_pre_eval_entries(files)
1642
1810
  analysed = files.to_set { |path| File.absolute_path(path) }
1643
1811
  outside = @configuration.pre_eval.reject { |path| analysed.include?(File.absolute_path(path)) }
@@ -1658,6 +1826,7 @@ module Rigor
1658
1826
  end
1659
1827
  private :plugin_boundary_rows, :producer_watch_glob_entries, :kind_entries, :root_presence_entries,
1660
1828
  :analysis_reads_attributed?, :signature_root_candidates, :discovery_glob_entries,
1829
+ :bundle_sig_glob_entries,
1661
1830
  :unanalysed_pre_eval_entries, :signature_rows, :within?
1662
1831
 
1663
1832
  # Issue #684 — the project files this run DISCOVERED but did not analyse. A widened run's diagnostics
@@ -2031,7 +2200,7 @@ module Rigor
2031
2200
  rescue ArgumentError => e
2032
2201
  floor = self.class.prism_supported_floor
2033
2202
  Diagnostic.new(
2034
- path: ".rigor.yml", line: 1, column: 1,
2203
+ path: @configuration.config_display_path, line: 1, column: 1,
2035
2204
  message: "target_ruby #{@configuration.target_ruby.inspect} is not supported by this Rigor build " \
2036
2205
  "(Prism accepts #{floor} and newer). Set target_ruby to your project's Ruby version " \
2037
2206
  "(>= #{floor}) — read it from Gemfile.lock's `RUBY VERSION` or .ruby-version. " \
@@ -2056,8 +2225,7 @@ module Rigor
2056
2225
  # Editor mode § "Scope choice — option A". Under `buffer:` non-nil the per-file analysis emits
2057
2226
  # diagnostics ONLY for the buffer's logical path; the rest of `paths:` is consumed by the
2058
2227
  # 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.
2228
+ # no per-file diagnostics. Option B (#146) narrows to the closure instead; see {#target_files}.
2061
2229
  #
2062
2230
  # The buffer's logical path is added to the file list even if it's not under `paths:` — per design §
2063
2231
  # "Failure envelope": "--instead-of=Y with Y not under any paths: directory → treated as a valid
@@ -2090,7 +2258,13 @@ module Rigor
2090
2258
  # The logical path joins even when it is not on disk under `paths:` (design § "Failure envelope"),
2091
2259
  # the same allowance option A makes below.
2092
2260
  files = files.select { |path| @analyze_only.include?(path) }
2093
- files |= [@buffer.logical_path] if @buffer && @analyze_only.include?(@buffer.logical_path)
2261
+ # Issue #2233 — a buffer that is a claimed template joins as its unit, appended by
2262
+ # {#template_unit_targets}, never as a `.rb` file: the logical path in its absolute spelling would
2263
+ # otherwise analyse the same template twice. The registry is loaded by now, which is why the test is here
2264
+ # and not in the incremental session, where it is not.
2265
+ if @buffer && @analyze_only.include?(@buffer.logical_path) && !template_path?(@buffer.logical_path)
2266
+ files |= [@buffer.logical_path]
2267
+ end
2094
2268
  return template_unit_targets(files)
2095
2269
  end
2096
2270
  return template_unit_targets(files) if @buffer.nil?
@@ -2126,6 +2300,14 @@ module Rigor
2126
2300
  end
2127
2301
  public :template_unit_paths
2128
2302
 
2303
+ # Issue #2233 — whether `path` is a template of this run: a claimed glob matches it, or it names a unit. The
2304
+ # runner keeps the buffer's logical path out of the targets when this is true, so a template buffer is
2305
+ # analysed once, as a unit ({#template_unit_targets} appends it), and never also as a plain `.rb` file.
2306
+ def template_path?(path)
2307
+ template_units.claims?(path) ||
2308
+ template_unit_paths.include?(Analysis::TemplateUnitPaths.relative(path, Dir.pwd))
2309
+ end
2310
+
2129
2311
  # Editor mode (`buffer:` non-nil) auto-flips the cache store to `read_only: true` so multiple
2130
2312
  # debounced editor invocations against the same project don't churn the on-disk cache or race on
2131
2313
  # schema-version writes. Cache reads continue unchanged; misses still run the producer block but the
@@ -2179,14 +2361,7 @@ module Rigor
2179
2361
  pool_mode: -> { pool_mode? },
2180
2362
  cached_plugin_prepare_diagnostics: -> { @cached_plugin_prepare_diagnostics },
2181
2363
  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 },
2364
+ snapshots: @snapshots,
2190
2365
  pooled_run_disclosures: -> { @pool_coordinator.collected_run_disclosures },
2191
2366
  served_run_disclosures: -> { @served_run_disclosures },
2192
2367
  pooled_trust_refusals: -> { @pool_coordinator.collected_trust_refusals }
@@ -2520,14 +2695,14 @@ module Rigor
2520
2695
  @discovery_seed ? @discovery_seed.dup : {}
2521
2696
  end
2522
2697
 
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).
2698
+ # Seeds the parameter-inference and constant-publication tables, so a pooled `WorkerSession` scope resolves
2699
+ # identically to the sequential path (ADR-15 sequential equivalence). The parameter-inference table is absent
2700
+ # unless its opt-in ran; the constant tables are seeded whenever the run has project constants.
2526
2701
  #
2527
2702
  # - ADR-67 WD6a `param_inferred_types`: the call-site parameter-inference table, populated only when the
2528
2703
  # `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.
2704
+ # - Issue #352 `in_source_constants`: the `pre_eval:` constant publication, merged with the project's
2705
+ # literal constant table (#644, see {#merged_seed_constants}).
2531
2706
  #
2532
2707
  # Extracted to keep {#project_scope_seed_tables} under the complexity budget.
2533
2708
  def seed_opt_in_pre_pass_tables(tables)
@@ -2655,6 +2830,14 @@ module Rigor
2655
2830
  # `DependencyRecorder.active? == false`. Recording is observational, so diagnostics are byte-identical
2656
2831
  # either way.
2657
2832
  def analyze_file(path, environment)
2833
+ diagnostics = analyze_file_attributed(path, environment)
2834
+ return diagnostics unless Inference::MemoAudit.enabled?
2835
+
2836
+ # ADR-129 WD5 (#2785) — the audit's mismatches this file raised, a swallowed one included.
2837
+ CrashSignature.with_swallowed(path, diagnostics, Inference::MemoAudit.drain)
2838
+ end
2839
+
2840
+ def analyze_file_attributed(path, environment)
2658
2841
  return analyze_with_effects(path, environment) unless @record_dependencies
2659
2842
 
2660
2843
  # ADR-45 WD2 (#1507) — every plugin read this file's analysis makes is attributed to it, so the
@@ -2735,9 +2918,11 @@ module Rigor
2735
2918
  # `self_call_misses` feeds the `call.self-undefined-method` collector; the recorder is inert unless
2736
2919
  # the rule is active or `record_self_calls:` opted in.
2737
2920
  index = nil
2921
+ TypeOfMemo.begin_file
2738
2922
  self_call_record = with_self_call_recording(path) do
2739
2923
  index = Inference::ScopeIndexer.index(parse_result.value, default_scope: scope, widen_clauses: true)
2740
2924
  end
2925
+ TypeOfMemo.phase!(:rule)
2741
2926
  self_call_misses = self_call_record ? self_call_record.calls : []
2742
2927
  diagnostics = rule_and_plugin_diagnostics(path, parse_result, scope, index, self_call_misses)
2743
2928
  notify_file_indexed(path, parse_result, scope, index)
@@ -2752,16 +2937,18 @@ module Rigor
2752
2937
  severity: :error
2753
2938
  )
2754
2939
  ]
2755
- rescue StandardError => e
2940
+ rescue StandardError, SystemStackError => e
2756
2941
  [
2757
2942
  Diagnostic.new(
2758
2943
  path: path,
2759
2944
  line: 1,
2760
2945
  column: 1,
2761
- message: CrashSignature.check_rule_message(e),
2946
+ message: CrashSignature.contained_message!(e, parse_result),
2762
2947
  severity: :error
2763
2948
  )
2764
2949
  ]
2950
+ ensure
2951
+ TypeOfMemo.end_file
2765
2952
  end
2766
2953
 
2767
2954
  # ADR-113 WD2 — the `on_file_indexed` observer. It reads; an error inside it never changes this file's
@@ -2778,7 +2965,9 @@ module Rigor
2778
2965
  # output and the plugin diagnostics are then built from that single walk's results.
2779
2966
  def rule_and_plugin_diagnostics(path, parse_result, scope, index, self_call_misses)
2780
2967
  root = parse_result.value
2781
- node_collectors = CheckRules.build_node_collectors(path, index, root)
2968
+ node_collectors = CheckRules.build_node_collectors(
2969
+ path, index, root, plugin_api_surface: CheckRules::PluginApiSurface.active?(@configuration)
2970
+ )
2782
2971
  node_results = node_rule_results_by_plugin(path, root, scope, node_collectors, index)
2783
2972
  diagnostics = CheckRules.diagnose(
2784
2973
  path: path,