rigortype 0.4.5 → 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 (452) 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 +61 -5
  8. data/docs/handbook/appendix-phpstan.md +4 -4
  9. data/docs/manual/02-cli-reference.md +192 -27
  10. data/docs/manual/03-configuration.md +38 -4
  11. data/docs/manual/04-diagnostics.md +1 -1
  12. data/docs/manual/06-baseline.md +6 -4
  13. data/docs/manual/09-editor-integration.md +13 -2
  14. data/docs/manual/10-mcp-server.md +47 -3
  15. data/docs/manual/12-caching.md +9 -1
  16. data/docs/manual/plugins/rigor-actionmailer.md +30 -0
  17. data/docs/manual/plugins/rigor-actionpack.md +49 -11
  18. data/docs/manual/plugins/rigor-activesupport-core-ext.md +20 -1
  19. data/docs/manual/plugins/rigor-devise.md +15 -0
  20. data/docs/manual/plugins/rigor-rspec.md +2 -1
  21. data/lib/rigor/analysis/baseline.rb +24 -27
  22. data/lib/rigor/analysis/buffer_binding.rb +14 -0
  23. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +5 -6
  24. data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +2 -2
  25. data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +4 -4
  26. data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +2 -2
  27. data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +105 -12
  28. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +143 -15
  29. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +4 -1
  30. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -8
  31. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +1 -1
  32. data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +66 -3
  33. data/lib/rigor/analysis/check_rules/rebinding_block_callees/dispatch.rb +112 -0
  34. data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +394 -0
  35. data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
  36. data/lib/rigor/analysis/check_rules/ruby_deprecations.rb +7 -0
  37. data/lib/rigor/analysis/check_rules/rule_ids.rb +1 -1
  38. data/lib/rigor/analysis/check_rules/rule_walk.rb +9 -7
  39. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +3 -4
  40. data/lib/rigor/analysis/check_rules/special_global_setters.rb +3 -3
  41. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +53 -2
  42. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +1 -1
  43. data/lib/rigor/analysis/check_rules.rb +217 -52
  44. data/lib/rigor/analysis/crash_signature.rb +2 -2
  45. data/lib/rigor/analysis/dependency_recorder.rb +15 -4
  46. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +14 -3
  47. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +1 -1
  48. data/lib/rigor/analysis/dependency_source_inference/index.rb +6 -8
  49. data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
  50. data/lib/rigor/analysis/edit_replay.rb +154 -0
  51. data/lib/rigor/analysis/fact_store.rb +3 -3
  52. data/lib/rigor/analysis/incremental.rb +37 -0
  53. data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +2 -2
  54. data/lib/rigor/analysis/incremental_run_slot.rb +2 -2
  55. data/lib/rigor/analysis/incremental_session.rb +202 -43
  56. data/lib/rigor/analysis/path_expansion.rb +41 -0
  57. data/lib/rigor/analysis/require_relative_closure.rb +100 -0
  58. data/lib/rigor/analysis/rule_catalog.rb +7 -3
  59. data/lib/rigor/analysis/run_disclosure_credit.rb +80 -0
  60. data/lib/rigor/analysis/run_disclosure_ledger.rb +155 -0
  61. data/lib/rigor/analysis/run_stats.rb +2 -2
  62. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +8 -4
  63. data/lib/rigor/analysis/runner/declaration_position.rb +1 -1
  64. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +204 -96
  65. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +1 -1
  66. data/lib/rigor/analysis/runner/pool_coordinator.rb +157 -320
  67. data/lib/rigor/analysis/runner/project_pre_passes.rb +48 -6
  68. data/lib/rigor/analysis/runner/run_level_facts.rb +304 -0
  69. data/lib/rigor/analysis/runner/run_snapshots.rb +44 -54
  70. data/lib/rigor/analysis/runner/scan_environment.rb +51 -0
  71. data/lib/rigor/analysis/runner/worker_payload.rb +36 -0
  72. data/lib/rigor/analysis/runner.rb +233 -120
  73. data/lib/rigor/analysis/self_call_resolution_recorder.rb +1 -1
  74. data/lib/rigor/analysis/template_unit_positions.rb +2 -3
  75. data/lib/rigor/analysis/template_units.rb +9 -1
  76. data/lib/rigor/analysis/worker_session.rb +38 -33
  77. data/lib/rigor/bleeding_edge.rb +3 -6
  78. data/lib/rigor/builtins/imported_refinements.rb +6 -8
  79. data/lib/rigor/builtins/predefined_constant_refinements.rb +1 -1
  80. data/lib/rigor/builtins/regex_refinement.rb +4 -6
  81. data/lib/rigor/cache/descriptor.rb +6 -5
  82. data/lib/rigor/cache/incremental_snapshot.rb +60 -18
  83. data/lib/rigor/cache/rbs_cache_producer.rb +2 -2
  84. data/lib/rigor/cache/rbs_class_ancestor_table.rb +1 -1
  85. data/lib/rigor/cache/rbs_class_type_param_names.rb +2 -2
  86. data/lib/rigor/cache/rbs_constant_table.rb +1 -1
  87. data/lib/rigor/cache/rbs_descriptor.rb +54 -3
  88. data/lib/rigor/cache/rbs_environment.rb +4 -1
  89. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +2 -2
  90. data/lib/rigor/cache/rbs_known_class_names.rb +1 -1
  91. data/lib/rigor/cache/store.rb +11 -10
  92. data/lib/rigor/ci_detector.rb +1 -1
  93. data/lib/rigor/cli/annotate_command.rb +24 -19
  94. data/lib/rigor/cli/baseline_command.rb +145 -55
  95. data/lib/rigor/cli/catalogue.rb +256 -0
  96. data/lib/rigor/cli/check_command.rb +188 -93
  97. data/lib/rigor/cli/check_runner_factory.rb +2 -2
  98. data/lib/rigor/cli/command.rb +15 -1
  99. data/lib/rigor/cli/coverage_command.rb +21 -8
  100. data/lib/rigor/cli/coverage_mutation.rb +5 -7
  101. data/lib/rigor/cli/coverage_renderer.rb +2 -1
  102. data/lib/rigor/cli/coverage_scan.rb +3 -5
  103. data/lib/rigor/cli/diff_command.rb +27 -21
  104. data/lib/rigor/cli/docs_command.rb +1 -2
  105. data/lib/rigor/cli/doctor_command.rb +12 -12
  106. data/lib/rigor/cli/effects_command.rb +50 -40
  107. data/lib/rigor/cli/effects_diff_renderer.rb +18 -17
  108. data/lib/rigor/cli/effects_explain_renderer.rb +13 -12
  109. data/lib/rigor/cli/effects_renderer.rb +2 -1
  110. data/lib/rigor/cli/effects_snapshot_command.rb +18 -17
  111. data/lib/rigor/cli/explain_command.rb +18 -8
  112. data/lib/rigor/cli/fused_protection_renderer.rb +2 -1
  113. data/lib/rigor/cli/json_document.rb +41 -0
  114. data/lib/rigor/cli/lens_command.rb +17 -13
  115. data/lib/rigor/cli/lsp_command.rb +250 -23
  116. data/lib/rigor/cli/mcp_command.rb +14 -8
  117. data/lib/rigor/cli/mutation_fork_scan.rb +1 -1
  118. data/lib/rigor/cli/mutation_protection_renderer.rb +3 -2
  119. data/lib/rigor/cli/options.rb +60 -4
  120. data/lib/rigor/cli/plugin_command.rb +93 -46
  121. data/lib/rigor/cli/plugins_command.rb +22 -18
  122. data/lib/rigor/cli/plugins_renderer.rb +34 -8
  123. data/lib/rigor/cli/protection_fork_scan.rb +2 -2
  124. data/lib/rigor/cli/protection_renderer.rb +2 -1
  125. data/lib/rigor/cli/show_bleedingedge_command.rb +23 -16
  126. data/lib/rigor/cli/sig_gen_command.rb +130 -34
  127. data/lib/rigor/cli/skill_command.rb +32 -3
  128. data/lib/rigor/cli/skill_deep_probe.rb +1 -1
  129. data/lib/rigor/cli/skill_describe.rb +1 -1
  130. data/lib/rigor/cli/trace_command.rb +14 -9
  131. data/lib/rigor/cli/triage_command.rb +10 -11
  132. data/lib/rigor/cli/triage_renderer.rb +2 -1
  133. data/lib/rigor/cli/type_of_command.rb +18 -12
  134. data/lib/rigor/cli/type_of_project_scope.rb +24 -0
  135. data/lib/rigor/cli/type_of_renderer.rb +4 -1
  136. data/lib/rigor/cli/type_scan_command.rb +14 -9
  137. data/lib/rigor/cli/type_scan_renderer.rb +2 -1
  138. data/lib/rigor/cli/unused_command.rb +27 -17
  139. data/lib/rigor/cli/upgrade_command.rb +5 -0
  140. data/lib/rigor/cli.rb +209 -54
  141. data/lib/rigor/config_audit.rb +17 -6
  142. data/lib/rigor/configuration/severity_profile.rb +5 -5
  143. data/lib/rigor/configuration.rb +86 -34
  144. data/lib/rigor/effects/definition_context.rb +0 -2
  145. data/lib/rigor/effects/envelope.rb +1 -1
  146. data/lib/rigor/effects/framework_units.rb +1 -1
  147. data/lib/rigor/effects/mutation_classifier.rb +4 -7
  148. data/lib/rigor/effects/propagator.rb +1 -1
  149. data/lib/rigor/effects/registry.rb +2 -2
  150. data/lib/rigor/effects/snapshot.rb +1 -1
  151. data/lib/rigor/effects/taint_cause.rb +0 -2
  152. data/lib/rigor/effects/unit_scan.rb +9 -13
  153. data/lib/rigor/environment/bundle_sig_discovery.rb +5 -9
  154. data/lib/rigor/environment/class_registry.rb +3 -3
  155. data/lib/rigor/environment/core_root.rb +77 -0
  156. data/lib/rigor/environment/default_libraries.rb +1 -1
  157. data/lib/rigor/environment/deferred_signature_plan.rb +283 -0
  158. data/lib/rigor/environment/gated_signature_guard.rb +1 -1
  159. data/lib/rigor/environment/gated_signature_plan.rb +5 -2
  160. data/lib/rigor/environment/lockfile_resolver.rb +28 -12
  161. data/lib/rigor/environment/member_consistency/comparator.rb +18 -1
  162. data/lib/rigor/environment/member_consistency.rb +56 -7
  163. data/lib/rigor/environment/missing_gem_constant_index.rb +3 -2
  164. data/lib/rigor/environment/project_path_gems.rb +124 -0
  165. data/lib/rigor/environment/rbs_hierarchy.rb +6 -15
  166. data/lib/rigor/environment/rbs_loader.rb +592 -70
  167. data/lib/rigor/environment/reflection.rb +7 -7
  168. data/lib/rigor/environment/required_features.rb +136 -32
  169. data/lib/rigor/environment/ruby_library_roots.rb +122 -0
  170. data/lib/rigor/environment/vendored_signature_plan.rb +153 -0
  171. data/lib/rigor/environment.rb +44 -11
  172. data/lib/rigor/flow_contribution/merger.rb +4 -4
  173. data/lib/rigor/inference/acceptance.rb +93 -32
  174. data/lib/rigor/inference/block_parameter_binder.rb +2 -3
  175. data/lib/rigor/inference/block_run_count.rb +178 -0
  176. data/lib/rigor/inference/budget_trace.rb +8 -2
  177. data/lib/rigor/inference/builtins/exception_catalog.rb +2 -2
  178. data/lib/rigor/inference/builtins/numeric_catalog.rb +2 -5
  179. data/lib/rigor/inference/builtins/random_catalog.rb +2 -2
  180. data/lib/rigor/inference/callee_mutation_chain.rb +126 -0
  181. data/lib/rigor/inference/callee_write_census.rb +815 -0
  182. data/lib/rigor/inference/construction_escape.rb +217 -0
  183. data/lib/rigor/inference/declaration_walk/traversal.rb +1 -1
  184. data/lib/rigor/inference/deep_erasure.rb +170 -0
  185. data/lib/rigor/inference/def_handle.rb +1 -1
  186. data/lib/rigor/inference/def_node_resolver.rb +1 -1
  187. data/lib/rigor/inference/def_return_typer.rb +204 -12
  188. data/lib/rigor/inference/definer_resolution.rb +21 -5
  189. data/lib/rigor/inference/definers_index.rb +106 -0
  190. data/lib/rigor/inference/direct_ivar_writes.rb +300 -0
  191. data/lib/rigor/inference/element_read_widening.rb +49 -3
  192. data/lib/rigor/inference/error_info.rb +17 -3
  193. data/lib/rigor/inference/expression_typer.rb +173 -57
  194. data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
  195. data/lib/rigor/inference/global_write_census.rb +110 -1
  196. data/lib/rigor/inference/global_writer_reach.rb +260 -0
  197. data/lib/rigor/inference/hkt_body.rb +1 -2
  198. data/lib/rigor/inference/hkt_body_parser.rb +3 -5
  199. data/lib/rigor/inference/hkt_reducer.rb +2 -2
  200. data/lib/rigor/inference/hkt_registry.rb +3 -5
  201. data/lib/rigor/inference/indexed_narrowing.rb +4 -7
  202. data/lib/rigor/inference/intervening_ivar_reset.rb +26 -4
  203. data/lib/rigor/inference/ivar_writer_census.rb +246 -0
  204. data/lib/rigor/inference/key_presence_guard.rb +2 -1
  205. data/lib/rigor/inference/loop_value_record.rb +57 -0
  206. data/lib/rigor/inference/match_rebinding/frame.rb +65 -0
  207. data/lib/rigor/inference/match_rebinding/yielding_match.rb +45 -0
  208. data/lib/rigor/inference/match_rebinding.rb +11 -1
  209. data/lib/rigor/inference/memo_audit.rb +55 -0
  210. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +1 -1
  211. data/lib/rigor/inference/method_dispatcher/argument_distribution.rb +40 -0
  212. data/lib/rigor/inference/method_dispatcher/block_folding.rb +1 -1
  213. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +113 -12
  214. data/lib/rigor/inference/method_dispatcher/contested_overloads.rb +89 -0
  215. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -1
  216. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -3
  217. data/lib/rigor/inference/method_dispatcher/no_match_fallback.rb +54 -0
  218. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +39 -27
  219. data/lib/rigor/inference/method_dispatcher/random_folding.rb +1 -1
  220. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +203 -17
  221. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +1 -1
  222. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +2 -2
  223. data/lib/rigor/inference/method_dispatcher/self_substitute.rb +2 -2
  224. data/lib/rigor/inference/method_dispatcher/set_folding.rb +59 -24
  225. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +83 -20
  226. data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +0 -1
  227. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +50 -17
  228. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -5
  229. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +4 -6
  230. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  231. data/lib/rigor/inference/method_dispatcher.rb +109 -29
  232. data/lib/rigor/inference/module_function_state/spellings.rb +64 -0
  233. data/lib/rigor/inference/module_function_state.rb +23 -1
  234. data/lib/rigor/inference/multi_target_binder.rb +1 -1
  235. data/lib/rigor/inference/mutation_rejoin.rb +240 -4
  236. data/lib/rigor/inference/mutation_widening.rb +162 -29
  237. data/lib/rigor/inference/narrowing.rb +113 -38
  238. data/lib/rigor/inference/object_mixins.rb +13 -1
  239. data/lib/rigor/inference/optimistic_origin.rb +1 -1
  240. data/lib/rigor/inference/out_buffer_mutation.rb +72 -0
  241. data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
  242. data/lib/rigor/inference/pre_eval_constants.rb +1 -1
  243. data/lib/rigor/inference/project_method_ownership.rb +6 -3
  244. data/lib/rigor/inference/project_seed_tables.rb +2 -1
  245. data/lib/rigor/inference/project_yielder.rb +122 -0
  246. data/lib/rigor/inference/receiver_alias.rb +20 -0
  247. data/lib/rigor/inference/refined_dispatch/constructed_class.rb +3 -1
  248. data/lib/rigor/inference/refined_dispatch/own_method.rb +2 -1
  249. data/lib/rigor/inference/remover_holder_scan.rb +233 -0
  250. data/lib/rigor/inference/remover_narrowing.rb +224 -0
  251. data/lib/rigor/inference/rewrite_mutation.rb +53 -2
  252. data/lib/rigor/inference/scope_indexer/certainty.rb +9 -1
  253. data/lib/rigor/inference/scope_indexer/constant_ivar_value.rb +93 -0
  254. data/lib/rigor/inference/scope_indexer/declared_ivar_types.rb +64 -0
  255. data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +205 -0
  256. data/lib/rigor/inference/scope_indexer/derived_param_value.rb +112 -0
  257. data/lib/rigor/inference/scope_indexer/empty_seed.rb +99 -0
  258. data/lib/rigor/inference/scope_indexer/ivar_contributions.rb +135 -0
  259. data/lib/rigor/inference/scope_indexer/unseen_ivar_writers.rb +133 -0
  260. data/lib/rigor/inference/scope_indexer.rb +614 -146
  261. data/lib/rigor/inference/self_call_ivar_writes.rb +101 -17
  262. data/lib/rigor/inference/set_mutation.rb +189 -0
  263. data/lib/rigor/inference/singleton_hook_decline.rb +1 -2
  264. data/lib/rigor/inference/statement_evaluator.rb +3036 -278
  265. data/lib/rigor/inference/struct_fold_safety.rb +4 -6
  266. data/lib/rigor/inference/synthetic_method_index.rb +23 -0
  267. data/lib/rigor/inference/synthetic_method_scanner/project_members.rb +130 -0
  268. data/lib/rigor/inference/synthetic_method_scanner.rb +73 -14
  269. data/lib/rigor/inference/toplevel_def_census.rb +59 -0
  270. data/lib/rigor/inference/unknown_store_widening.rb +103 -4
  271. data/lib/rigor/inference/unthreaded_rebinds.rb +2 -3
  272. data/lib/rigor/inference/void_tail_summary.rb +2 -1
  273. data/lib/rigor/language_server/buffer_table.rb +9 -3
  274. data/lib/rigor/language_server/completion_provider.rb +26 -20
  275. data/lib/rigor/language_server/config_warning_announcer.rb +28 -0
  276. data/lib/rigor/language_server/diagnostic_publisher.rb +28 -9
  277. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  278. data/lib/rigor/language_server/folding_range_provider.rb +1 -2
  279. data/lib/rigor/language_server/hover_provider.rb +1 -7
  280. data/lib/rigor/language_server/hover_renderer.rb +6 -8
  281. data/lib/rigor/language_server/project_context.rb +98 -4
  282. data/lib/rigor/language_server/publish_batcher.rb +3 -4
  283. data/lib/rigor/language_server/selection_range_provider.rb +2 -4
  284. data/lib/rigor/language_server/server.rb +12 -6
  285. data/lib/rigor/language_server/signature_help_provider.rb +4 -4
  286. data/lib/rigor/lens/plugins.rb +1 -1
  287. data/lib/rigor/mcp/server.rb +82 -3
  288. data/lib/rigor/mcp.rb +1 -1
  289. data/lib/rigor/plugin/access_denied_error.rb +1 -2
  290. data/lib/rigor/plugin/base.rb +26 -11
  291. data/lib/rigor/plugin/bundled_catalog.rb +1 -12
  292. data/lib/rigor/plugin/io_boundary.rb +1 -1
  293. data/lib/rigor/plugin/loader.rb +25 -8
  294. data/lib/rigor/plugin/macro/heredoc_template.rb +7 -8
  295. data/lib/rigor/plugin/macro/trait_registry.rb +6 -9
  296. data/lib/rigor/plugin/manifest.rb +38 -16
  297. data/lib/rigor/plugin/registry.rb +41 -3
  298. data/lib/rigor/plugin/services.rb +2 -3
  299. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -0
  300. data/lib/rigor/plugin/trust_policy.rb +2 -2
  301. data/lib/rigor/plugin/type_node_resolver.rb +2 -4
  302. data/lib/rigor/plugin.rb +6 -8
  303. data/lib/rigor/plugin_gap_advisory.rb +5 -1
  304. data/lib/rigor/project_environment.rb +16 -2
  305. data/lib/rigor/protection/analysis_guard.rb +1 -1
  306. data/lib/rigor/protection/discovery_seed.rb +96 -2
  307. data/lib/rigor/protection/mutator.rb +1 -1
  308. data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
  309. data/lib/rigor/rbs_extended/hkt_directives.rb +6 -10
  310. data/lib/rigor/rbs_extended/reporter.rb +27 -3
  311. data/lib/rigor/rbs_extended.rb +2 -3
  312. data/lib/rigor/reflection/constant_ancestors.rb +5 -3
  313. data/lib/rigor/rooted_glob.rb +25 -2
  314. data/lib/rigor/run_scoped_slots.rb +38 -0
  315. data/lib/rigor/runtime/jit.rb +1 -1
  316. data/lib/rigor/scope/discovery_index.rb +19 -6
  317. data/lib/rigor/scope/resolution_chain.rb +3 -1
  318. data/lib/rigor/scope.rb +379 -36
  319. data/lib/rigor/sig_gen/classification.rb +4 -0
  320. data/lib/rigor/sig_gen/effect_annotation.rb +3 -3
  321. data/lib/rigor/sig_gen/generator.rb +668 -84
  322. data/lib/rigor/sig_gen/generic_header_scan.rb +189 -0
  323. data/lib/rigor/sig_gen/inline_declarations.rb +2 -2
  324. data/lib/rigor/sig_gen/layout_index.rb +3 -3
  325. data/lib/rigor/sig_gen/method_candidate.rb +23 -4
  326. data/lib/rigor/sig_gen/observation_collector.rb +7 -6
  327. data/lib/rigor/sig_gen/observed_call.rb +1 -1
  328. data/lib/rigor/sig_gen/path_mapper.rb +6 -0
  329. data/lib/rigor/sig_gen/rbs_validity.rb +1 -1
  330. data/lib/rigor/sig_gen/renderer.rb +21 -12
  331. data/lib/rigor/sig_gen/skip_reason_catalog.rb +15 -2
  332. data/lib/rigor/sig_gen/type_elaborator.rb +21 -5
  333. data/lib/rigor/sig_gen/type_name_qualification.rb +69 -0
  334. data/lib/rigor/sig_gen/write_reporter.rb +63 -14
  335. data/lib/rigor/sig_gen/write_result.rb +23 -12
  336. data/lib/rigor/sig_gen/writer.rb +217 -37
  337. data/lib/rigor/type/acceptance_router.rb +2 -2
  338. data/lib/rigor/type/accepts_result.rb +1 -1
  339. data/lib/rigor/type/app.rb +4 -6
  340. data/lib/rigor/type/combinator.rb +13 -16
  341. data/lib/rigor/type/constant.rb +48 -7
  342. data/lib/rigor/type/hash_shape.rb +26 -40
  343. data/lib/rigor/type/struct_instance.rb +3 -2
  344. data/lib/rigor/type/union.rb +1 -1
  345. data/lib/rigor/type_node/identifier.rb +1 -2
  346. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  347. data/lib/rigor/type_node/string_literal.rb +1 -2
  348. data/lib/rigor/type_node/symbol_literal.rb +1 -2
  349. data/lib/rigor/type_node/union.rb +3 -6
  350. data/lib/rigor/value_semantics.rb +1 -10
  351. data/lib/rigor/version.rb +1 -1
  352. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +2 -2
  353. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +2 -3
  354. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -1
  355. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +89 -70
  356. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +4 -2
  357. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +25 -4
  358. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +1 -1
  359. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -5
  360. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +0 -1
  361. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +2 -1
  362. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +1 -1
  363. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +79 -19
  364. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
  365. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +6 -3
  366. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +3 -2
  367. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +2 -2
  368. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +21 -31
  369. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +10 -16
  370. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +2 -3
  371. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +0 -1
  372. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +5 -4
  373. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +3 -4
  374. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +1 -1
  375. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +7 -12
  376. data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +1 -1
  377. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +1 -1
  378. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/symbolize_keys.rb +102 -0
  379. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +19 -6
  380. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +26 -13
  381. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +3 -3
  382. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +5 -3
  383. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +1 -3
  384. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +6 -6
  385. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +5 -6
  386. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +7 -6
  387. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/params_shape.rb +2 -3
  388. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -8
  389. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +0 -1
  390. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
  391. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +10 -18
  392. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +0 -1
  393. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +0 -2
  394. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +4 -5
  395. data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +0 -2
  396. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +6 -7
  397. data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +2 -4
  398. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +2 -5
  399. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -2
  400. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +6 -6
  401. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
  402. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +3 -8
  403. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +2 -3
  404. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -8
  405. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -3
  406. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -3
  407. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +1 -1
  408. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +4 -11
  409. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -1
  410. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +2 -1
  411. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +102 -11
  412. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +2 -2
  413. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +3 -4
  414. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +6 -8
  415. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +5 -2
  416. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +4 -5
  417. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +6 -7
  418. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +1 -1
  419. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +2 -2
  420. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +17 -21
  421. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +2 -3
  422. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +3 -2
  423. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +4 -4
  424. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer.rb +1 -1
  425. data/sig/rigor/analysis/baseline.rbs +0 -2
  426. data/sig/rigor/analysis/fact_store.rbs +1 -0
  427. data/sig/rigor/analysis/reachability/scan_cache.rbs +1 -1
  428. data/sig/rigor/cli/diff_command.rbs +1 -1
  429. data/sig/rigor/cli/explain_command.rbs +1 -1
  430. data/sig/rigor/effects/file_collection.rbs +5 -6
  431. data/sig/rigor/effects/registry.rbs +8 -6
  432. data/sig/rigor/effects/summary.rbs +6 -5
  433. data/sig/rigor/environment.rbs +5 -3
  434. data/sig/rigor/inference/optimistic_origin.rbs +11 -0
  435. data/sig/rigor/inference.rbs +10 -1
  436. data/sig/rigor/plugin/additional_initializer.rbs +0 -5
  437. data/sig/rigor/plugin/base.rbs +1 -0
  438. data/sig/rigor/plugin/effect_ancestry.rbs +6 -4
  439. data/sig/rigor/plugin/effect_attribution.rbs +25 -10
  440. data/sig/rigor/plugin/effect_edge.rbs +12 -5
  441. data/sig/rigor/plugin/effect_entry_points.rbs +8 -3
  442. data/sig/rigor/plugin/io_boundary.rbs +6 -5
  443. data/sig/rigor/plugin/manifest.rbs +16 -18
  444. data/sig/rigor/plugin/protocol_contract.rbs +6 -9
  445. data/sig/rigor/plugin/registry.rbs +1 -1
  446. data/sig/rigor/scope.rbs +51 -50
  447. data/sig/rigor/type.rbs +0 -6
  448. data/skills/rigor-plugin-author/SKILL.md +4 -4
  449. data/skills/rigor-plugin-author/references/02-walker-and-types.md +5 -5
  450. data/skills/rigor-plugin-review/SKILL.md +3 -3
  451. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +12 -1
  452. metadata +55 -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"
@@ -12,6 +13,8 @@ require_relative "../cache/file_digest"
12
13
  require_relative "../cache/incremental_snapshot"
13
14
  require_relative "run_cache_key"
14
15
  require_relative "path_expansion"
16
+ require_relative "require_relative_closure"
17
+ require_relative "run_disclosure_credit"
15
18
  require_relative "../plugin"
16
19
  require_relative "../plugin/source_rbs_synthesis_reporter"
17
20
  require_relative "../rbs_extended/reporter"
@@ -75,18 +78,20 @@ module Rigor
75
78
  # this and serves reused files from it without re-stamping: a run-level row is regenerated
76
79
  # every run wherever it is positioned, and slicing the run's full stream by path cached the
77
80
  # file-positioned ones (`effect.annotations-unchecked`, `source-rbs-*`).
78
- :per_file_diagnostics
81
+ :per_file_diagnostics,
82
+ # PR #2286 review M1 — the ADR-16 index this run's pre-pass built; `IncrementalSession` digests it
83
+ # to tell when a class-body DSL edit moved what a served file's dispatch read.
84
+ :synthetic_method_index
79
85
 
80
86
  # Issues #796 / #794 — the two run-level rows a NARROWED run cannot re-derive from the files it
81
87
  # analyses: the `definition-build-failed` set (whose producer is the analysis's own demand, and #696
82
88
  # forbids Rigor demanding in its place) and the HKT-scan outcome (whose producer is an environment a
83
89
  # nothing-changed recheck otherwise has no reason to build). {IncrementalSession} persists what a full
84
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`.
85
93
  def run_level_rows
86
- Cache::IncrementalSnapshot::RunLevelRows.new(
87
- definition_build_failures: @snapshots.definition_build_failures,
88
- hkt_scan_failure: @snapshots.hkt_scan_failure
89
- )
94
+ Cache::IncrementalSnapshot::RunLevelRows.new(**@snapshots.to_h.slice(*RunLevelFacts.replayable_names))
90
95
  end
91
96
 
92
97
  # ADR-46 — the per-file cross-file read records this run captured (empty unless
@@ -109,6 +114,13 @@ module Rigor
109
114
  pooled.empty? ? @analysis_reads : @analysis_reads.merge(pooled)
110
115
  end
111
116
 
117
+ # Issue #2020 — `{path => [run-disclosure record]}`, the run-scoped rows each analysed file's plugin hooks
118
+ # registered ({RunDisclosureCredit}), sequential and pooled alike; empty unless `record_dependencies:`.
119
+ def analysis_disclosures
120
+ pooled = @pool_coordinator.collected_analysis_disclosures
121
+ pooled.empty? ? @analysis_disclosures : @analysis_disclosures.merge(pooled)
122
+ end
123
+
112
124
  # ADR-103 — the run's propagated effect graph, or {Effects::EffectTable.empty} when collection did
113
125
  # not run. Reconciled from the sequential and pooled halves exactly as {#file_dependencies} is, then
114
126
  # closed by the post-pool fixpoint in {#assemble_run_diagnostics}.
@@ -268,8 +280,7 @@ module Rigor
268
280
  # @param explain — surface fail-soft fallback events as `:info` diagnostics.
269
281
  # @param cache_store — the persistent cache the runner exposes to producers
270
282
  # (`RbsConstantTable` and successors). Pass `nil` to disable caching for this run; the CLI's
271
- # `--no-cache` flag wires `nil` through. v0.0.9 group A slice 1 introduces the surface; later
272
- # slices route real producers through it.
283
+ # `--no-cache` flag wires `nil` through.
273
284
  # @param workers — ADR-15 Phase 4b — when greater than zero, per-file analysis dispatches
274
285
  # across a pool of N workers. Default `0` keeps the sequential code path bit-for-bit unchanged.
275
286
  # Controlled via the `RIGOR_RACTOR_WORKERS` env var or `.rigor.yml` `parallel.workers:` (Phase 4c,
@@ -294,10 +305,11 @@ module Rigor
294
305
  # worker continues to build its own Environment.
295
306
  # @param discovery_seed — issue #260 — opt-in cross-file discovery tables, keyed by
296
307
  # {Scope::DiscoveryIndex} slot name, seeded onto every per-file scope through
297
- # `project_scope_seed_tables`. The ONE deliberate exception to "a `prebuilt:` runner carries no
298
- # discovery tables": {Protection::DiagnosticOracle} threads the table set Tier 2's site filter already
299
- # judges anchors against, so a site admitted because a sibling-file class resolved is also a site the
300
- # oracle can kill at. nil (the default) leaves the prebuilt/LSP contract byte-identical.
308
+ # `project_scope_seed_tables`. A `prebuilt:` runner computes no discovery tables of its own, so this is
309
+ # how one learns the project: {Protection::DiagnosticOracle} threads the table set Tier 2's site filter
310
+ # already judges anchors against, and (issue #2147, ADR-124) the LSP's per-buffer publish threads
311
+ # `LanguageServer::ProjectContext#discovery_seed_for`, so a class another file declares is not an
312
+ # external ancestor there. nil (the default) seeds nothing.
301
313
  # @param no_tolerated_effects — ADR-103 WD1 / #385 — `rigor check --no-tolerated-effects`.
302
314
  # Judges effect envelopes as if `effects.tolerated:` were empty. A judgment-time switch only: the
303
315
  # run, its collection and its cache identity are unchanged.
@@ -309,7 +321,7 @@ module Rigor
309
321
  seed_bundles: nil, collect_seed_bundles: false, param_inferred_types: nil,
310
322
  discovery_seed: nil, no_tolerated_effects: false,
311
323
  restored_run_level_rows: nil, served_per_file_diagnostics: nil, served_effect_collections: nil,
312
- on_file_indexed: nil)
324
+ served_run_disclosures: nil, on_file_indexed: nil)
313
325
  @configuration = configuration
314
326
  # ADR-113 WD2 — `rigor lens` reads a target's seeded index here: called with `(path, parse_result, scope,
315
327
  # index)` for every file this process indexes, after its rules ran and inside the run window, so the reader
@@ -327,6 +339,9 @@ module Rigor
327
339
  # effect graph this run closes (and the envelope pass judges) spans the whole project, not just the closure.
328
340
  # Never part of {#effect_collections_by_path}, which stays what THIS run collected. Empty on every other run.
329
341
  @served_effect_collections = served_effect_collections || {}
342
+ # Issue #2020 — the run-scoped plugin rows ({RunDisclosureCredit}) those unanalysed files registered when
343
+ # they were last analysed, emitted beside this run's own registrations. Empty on every other run.
344
+ @served_run_disclosures = served_run_disclosures || []
330
345
  @explain = explain
331
346
  @cache_store = enforce_read_only_cache(cache_store, buffer)
332
347
  @plugin_requirer = plugin_requirer
@@ -336,8 +351,8 @@ module Rigor
336
351
  @prebuilt = prebuilt
337
352
  @environment_override = environment
338
353
  # ADR-46 slice 1 — opt-in cross-file dependency recording. Off by default; when true,
339
- # `analyze_file` records each file's cross-file reads into `file_dependencies` (the incremental
340
- # 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.
341
356
  @record_dependencies = record_dependencies
342
357
  # ADR-24 slice 4a — opt-in unresolved-implicit-self-call recording. Off by default; when true,
343
358
  # `analyze_file` activates the engine choke-point recorder and collects each file's misses into
@@ -382,6 +397,7 @@ module Rigor
382
397
  # rows as they stood around the per-file loop; see {#incremental_slot_rows}. Recorded only by a
383
398
  # dependency-recording (incremental) run.
384
399
  @analysis_reads = {}
400
+ @analysis_disclosures = {}
385
401
  @pre_analysis_plugin_rows = nil
386
402
  @post_analysis_plugin_rows = nil
387
403
  # Memoised activation decision for the `call.self-undefined-method` rule (nil = not yet computed).
@@ -496,6 +512,9 @@ module Rigor
496
512
  # entry across a run boundary (LSP re-check, ADR-62 warm loop); nil until the first run so
497
513
  # runner-less probes keep the per-file fallback.
498
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
499
518
  build_collaborators
500
519
  end
501
520
 
@@ -518,9 +537,39 @@ module Rigor
518
537
  # freshness path for this run; the `auto` default resolves strict in CI (#190), stat-first elsewhere.
519
538
  Cache::FileDigest.with_run(strict: @configuration.cache_validation_strict?) do
520
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
521
545
  end
522
546
  end
523
547
 
548
+ # ADR-45 WD2 (#1507) / #2020 — the per-run reset of what each file's analysis is credited with: its plugin
549
+ # reads, its run-scoped plugin registrations, and the boundary rows around the per-file loop.
550
+ def reset_analysis_attribution
551
+ @analysis_reads = {}
552
+ @analysis_disclosures = {}
553
+ @pre_analysis_plugin_rows = nil
554
+ @post_analysis_plugin_rows = nil
555
+ end
556
+ private :reset_analysis_attribution
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
+
524
573
  def run_analysis(paths)
525
574
  Inference::MethodDispatcher::FileFolding.fold_platform_specific_paths =
526
575
  @configuration.fold_platform_specific_paths
@@ -534,6 +583,10 @@ module Rigor
534
583
  @project_discovery_expansion = project_discovery_expansion(paths, expansion)
535
584
  @discovery_widened = widen_discovery_to_project?(paths)
536
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
537
590
  # Per-run reset of the deferred-discovery memo (see `#ensure_project_discovery`).
538
591
  @project_discovery_done = false
539
592
  # #392 — re-derive the template units each run: their source files can change between two runs of
@@ -548,9 +601,7 @@ module Rigor
548
601
  # #788 — the per-file reader is assigned only on the analysis (miss) path; reset it here so a run the
549
602
  # ADR-45 result cache serves does not answer with the previous run's rows.
550
603
  @per_file_diagnostics = [].freeze
551
- @analysis_reads = {}
552
- @pre_analysis_plugin_rows = nil
553
- @post_analysis_plugin_rows = nil
604
+ reset_analysis_attribution
554
605
  # ADR-84 WD2 — roll the return-memo bucket: a fresh frozen token per run makes every per-file scope
555
606
  # of THIS run share one memo bucket while entries from any earlier run in this process (stale after
556
607
  # an edit) become unreachable.
@@ -743,9 +794,10 @@ module Rigor
743
794
  # arg-flooring surface). Only meaningful right after a run populated the memo (baseline / recheck),
744
795
  # before the bucket rolls. Keys are held live here; the session Marshals them for the snapshot.
745
796
  #
746
- # @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:`.
747
799
  def return_summaries
748
- memo = Inference::ExpressionTyper.harvest_return_memo
800
+ memo = Inference::ExpressionTyper.harvest_return_memo(@return_memo)
749
801
  return {} if memo.empty?
750
802
 
751
803
  result = {}
@@ -803,13 +855,15 @@ module Rigor
803
855
  #
804
856
  # @param paths — analysis roots (nil → the configuration's `paths:`).
805
857
  # @param specs — each `{ class_name:, method_name:, singleton:, keys: [[receiver, args], …] }`.
806
- # @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]`.
807
859
  def evaluate_return_types(paths, specs)
808
860
  return {} if specs.empty?
809
861
 
810
862
  Cache::FileDigest.with_run(strict: @configuration.cache_validation_strict?) do
811
863
  Inference::DefNodeResolver.with_run { evaluate_return_types_setup(paths, specs) }
812
864
  end
865
+ ensure
866
+ RunScopedSlots.release
813
867
  end
814
868
 
815
869
  # Builds the discovery index + environment (a real run's prologue, minus per-file analysis) and
@@ -996,17 +1050,12 @@ module Rigor
996
1050
  nil
997
1051
  end
998
1052
 
999
- # The write half, run only after a miss, so the block never recomputes anything: it hands over the
1000
- # collections the analysis just produced — and the table the analysis already closed over them, so
1001
- # a warm hit re-runs neither the merge nor the fixpoint — validated against the same post-run
1002
- # dependency descriptor the diagnostics slot records. Fail-soft — a collection that will not
1003
- # Marshal (which nothing in {Effects::FileCollection} should be, and the fork pool already proves
1004
- # per file) costs the next run its warm start and nothing else.
1005
- # 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
1006
1054
  # key descriptor and one dependency descriptor (#482): the summary a warm run serves from, and the
1007
1055
  # collections the incremental and fail-soft paths read. The summary is written LAST, so a run
1008
1056
  # interrupted between the two leaves a collections entry that the next run's summary miss simply
1009
- # 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.
1010
1059
  def store_effect_collections(descriptor, expansion, rbs_descriptor)
1011
1060
  return if descriptor.nil?
1012
1061
 
@@ -1208,40 +1257,13 @@ module Rigor
1208
1257
  end
1209
1258
  private :template_unit_failure_diagnostics
1210
1259
 
1211
- # The rbs-coverage build-failure ladder plus every remaining post-analysis stream
1212
- # `#assemble_run_diagnostics` appends, in their fixed contract order (see the comments at each call
1213
- # below and at the streams' own definitions). Split out of `#assemble_run_diagnostics` so that
1214
- # method's `Metrics/AbcSize` stays under budget — every stream here is a plain `DiagnosticAggregator`
1215
- # reader with no branching of its own, so the split changes nothing about WHEN or in WHAT order they
1216
- # 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.
1217
1265
  def post_analysis_diagnostic_streams
1218
- diagnostics = @diagnostic_aggregator.rbs_quarantined_signature_diagnostics
1219
- diagnostics += @diagnostic_aggregator.rbs_environment_build_failed_diagnostics
1220
- # Issue #696 — after its env-wide twin and before the synthesized-namespace notice: the four
1221
- # `rbs.coverage.*` build conditions surface widest-consequence first, and their relative order is
1222
- # the diagnostic output contract.
1223
- diagnostics += @diagnostic_aggregator.rbs_definition_build_failed_diagnostics
1224
- # Issue #784 — last of the four, deliberately: it loses only the IMPLICIT HKT registrations a
1225
- # `type` alias would have contributed, never a class's own declared method surface, so it is the
1226
- # narrowest-consequence rung on the same ladder.
1227
- diagnostics += @diagnostic_aggregator.rbs_hkt_scan_failed_diagnostics
1228
- # The synthesized-namespace notice, then (#610) the plugin-signature stand-down: the outcome that
1229
- # AVOIDED a definition-build failure, so the quietest row on that ladder.
1230
- diagnostics += @diagnostic_aggregator.rbs_coverage_notice_diagnostics
1231
- diagnostics += @diagnostic_aggregator.conforms_to_diagnostics
1232
- diagnostics += @diagnostic_aggregator.rbs_extended_reporter_diagnostics
1233
- diagnostics += @diagnostic_aggregator.boundary_cross_diagnostics
1234
- diagnostics += @diagnostic_aggregator.source_rbs_synthesis_diagnostics
1235
- # Issue #1051 — read after `analyze_targets` for the same reason the refusal stream below is: a
1236
- # plugin may register a disclosure from `#prepare` (the usual case) or while analysing a file, and
1237
- # both have to be in before the table is harvested. Ordered before the refusals so the run-level
1238
- # plugin rows read plugin-authored notice first, engine-authored envelope second.
1239
- diagnostics += @diagnostic_aggregator.plugin_run_disclosure_diagnostics
1240
- # Issue #959 — read LAST (after `analyze_targets`, which already ran before this method is called),
1241
- # so every per-file plugin call has had its chance to record a refusal on its (per-plugin-instance
1242
- # memoised) `IoBoundary`, alongside the prepare-time refusals the same boundary instance may
1243
- # already carry.
1244
- diagnostics + @diagnostic_aggregator.plugin_trust_refusal_diagnostics
1266
+ @diagnostic_aggregator.run_level_diagnostics
1245
1267
  end
1246
1268
  private :post_analysis_diagnostic_streams
1247
1269
 
@@ -1414,7 +1436,7 @@ module Rigor
1414
1436
  end
1415
1437
 
1416
1438
  def build_run_dependency_descriptor(expansion, rbs_descriptor)
1417
- entries = analyzed_file_entries(expansion) + discovery_file_entries(expansion) +
1439
+ entries = analyzed_file_entries(expansion) + discovery_file_entries(expansion) + required_missing_entries +
1418
1440
  pre_eval_file_entries + template_unit_file_entries + rbs_descriptor.files +
1419
1441
  root_presence_entries(expansion)
1420
1442
  # #979 — the signature ROOTS' listings, one glob row each. `rbs_descriptor.files` covers only the
@@ -1509,7 +1531,8 @@ module Rigor
1509
1531
  IncrementalSlotRows.new(
1510
1532
  observed: plugin_boundary_rows,
1511
1533
  derived: Cache::Descriptor.new(
1512
- files: template_unit_file_entries + kind_entries(Array(roots) + @configuration.pre_eval),
1534
+ files: template_unit_file_entries + kind_entries(Array(roots) + @configuration.pre_eval) +
1535
+ required_missing_entries,
1513
1536
  globs: template_unit_glob_entries
1514
1537
  ),
1515
1538
  by_file: @analysis_reads.transform_values do |reads|
@@ -1557,7 +1580,9 @@ module Rigor
1557
1580
  # change time it cannot read, and passes over a `pinned` one.
1558
1581
  BaselineRows = Data.define(:owned, :pinned)
1559
1582
 
1560
- 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)
1561
1586
  private_constant :SignatureRoots
1562
1587
 
1563
1588
  # @param files — the run's analysed-path set.
@@ -1568,7 +1593,8 @@ module Rigor
1568
1593
  BaselineRows.new(
1569
1594
  owned: Cache::Descriptor.new(
1570
1595
  files: own.files + kind_entries(signature_root_candidates) +
1571
- discovery_file_entries({ files: files }) + unanalysed_pre_eval_entries(files),
1596
+ discovery_file_entries({ files: files }) + required_missing_entries +
1597
+ unanalysed_pre_eval_entries(files),
1572
1598
  globs: (own.globs + discovery_glob_entries).uniq
1573
1599
  ),
1574
1600
  pinned: pinned
@@ -1577,8 +1603,9 @@ module Rigor
1577
1603
 
1578
1604
  # The signature tree's rows, the project's own and the rest.
1579
1605
  def signature_rows
1580
- roots = SignatureRoots.new(@snapshots.signature_paths)
1581
- 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), "") }
1582
1609
  own_files, other_files = Cache::RbsDescriptor.file_entries(roots, comparator: :stat)
1583
1610
  .partition { |row| within?(row.path, own) }
1584
1611
  own_globs, other_globs = Cache::RbsDescriptor.glob_entries(roots).partition { |row| within?(row.root, own) }
@@ -1808,20 +1835,19 @@ module Rigor
1808
1835
  end
1809
1836
 
1810
1837
  # Internal: builds the deferred cross-file discovery tables at most once per run and adopts them.
1811
- # Memoised on `@project_discovery_done` (reset at the start of `#run`). No-op under `@prebuilt` — the
1812
- # LSP path deliberately seeds an empty project scope from a snapshot that carries no discovery tables,
1813
- # so forcing a build there would change that contract. A caller that DOES want cross-file knowledge under
1814
- # `prebuilt:` supplies it explicitly through `discovery_seed:` (issue #260) rather than by re-walking the
1815
- # project here. Called eagerly from `#run` for the recording /
1816
- # subset (ADR-46) modes and lazily from `#assemble_run_diagnostics` on the analysis path, so a warm
1817
- # cache HIT (which never assembles) never pays the double parse.
1838
+ # Memoised on `@project_discovery_done` (reset at the start of `#run`). No-op under `@prebuilt`: the
1839
+ # snapshot carries no discovery tables, and a per-request re-walk of the project is what `prebuilt:` exists
1840
+ # to avoid. A caller that wants cross-file knowledge under `prebuilt:` supplies it through `discovery_seed:`
1841
+ # (issue #260), as the LSP publish does since issue #2147 (ADR-124). Called eagerly from `#run` for the
1842
+ # recording / subset (ADR-46) modes and lazily from `#assemble_run_diagnostics` on the analysis path, so a
1843
+ # warm cache HIT (which never assembles) never pays the double parse.
1818
1844
  def ensure_project_discovery(expansion)
1819
1845
  return if @prebuilt
1820
1846
  return if @project_discovery_done
1821
1847
 
1822
1848
  @project_discovery_done = true
1823
1849
  # Issue #684 — the cross-file index spans the PROJECT even when the analysis targets a file list.
1824
- expansion = @project_discovery_expansion || once_each(expansion)
1850
+ expansion = buffer_spelled(@project_discovery_expansion || once_each(expansion))
1825
1851
  if @collect_seed_bundles
1826
1852
  # ADR-85 WD2 — rebuild discovery from the prior run's bundles (re-walking only changed files) and
1827
1853
  # capture the refreshed bundle set for the session to persist.
@@ -1853,7 +1879,13 @@ module Rigor
1853
1879
  # on what else was on the command line.
1854
1880
  #
1855
1881
  # Excluded, each because its expansion is already the one it means: the LSP `prebuilt:` path (which
1856
- # seeds discovery from a snapshot), editor `buffer:` mode, and `run_source`'s in-memory single file.
1882
+ # seeds discovery from a snapshot, ADR-124) and `run_source`'s in-memory single file.
1883
+ #
1884
+ # Issue #2165 — editor `buffer:` mode (`rigor check --tmp-file T --instead-of F F`) widens like a plain run.
1885
+ # It was excluded on the reading that its expansion is already the one it means, but an editor passes the
1886
+ # buffer's own path, so discovery saw that file alone: every class another file declares read as an external
1887
+ # ancestor, and the run reported what `rigor check F` does not. The discovery pre-pass reads through the
1888
+ # binding, so the buffer's file is walked from the buffer's bytes.
1857
1889
  #
1858
1890
  # Issue #1583 — ADR-46's `analyze_only` subset mode widens like a full run. It was excluded on the
1859
1891
  # reading that it "already passes the full expansion", which holds only when the incremental session
@@ -1867,15 +1899,51 @@ module Rigor
1867
1899
  # the pre-pass then saw two writers for every constant a file declares and withheld its publication (#644),
1868
1900
  # and every file was a discovered-not-analysed dependency of the run's cache entry.
1869
1901
  #
1902
+ # Issue #2021 — an analysed file outside the configured paths has no project there, so the widened set also
1903
+ # holds what it loads with a literal `require_relative` ({#required_outside_paths}). A plugin file checked on
1904
+ # its own otherwise missed its sibling's nested `Effects` and resolved the project's `Rigor::Effects`.
1905
+ #
1870
1906
  # The widening decision itself compares the paths as spelled. `exclude:` patterns apply to both spellings
1871
1907
  # alike (#1576), so the configured absolute `lib` and the argument `lib` exclude the same files.
1872
1908
  def project_discovery_expansion(paths, expansion)
1873
1909
  return nil unless widen_discovery_to_project?(paths)
1874
1910
 
1875
1911
  analysed = expansion.fetch(:files)
1876
- files = discovery_files(expand_paths(@configuration.paths | paths).fetch(:files), analysed)
1912
+ project = expand_paths(@configuration.paths | paths).fetch(:files)
1913
+ walked = required_outside_paths(analysed)
1914
+ files = discovery_files(project + walked.found, analysed)
1877
1915
  # Overlapping arguments (`lib lib/user.rb`) name an analysed file twice; the widened set names it once.
1878
- files.size > analysed.uniq { |path| File.absolute_path(path) }.size ? { files: files, errors: [] } : nil
1916
+ return nil unless files.size > analysed.uniq { |path| File.absolute_path(path) }.size || walked.missing.any?
1917
+
1918
+ { files: files, errors: [], uncertain: walked.found.to_set.freeze, missing: walked.missing }
1919
+ end
1920
+
1921
+ # Issue #2021 — what the analysed files outside the configured `paths:` load with a literal `require_relative`,
1922
+ # through targets that are themselves outside them ({RequireRelativeClosure}). A file under a configured path
1923
+ # is discovered already, so the walk adds nothing for a run whose files all lie inside them. The comparison is
1924
+ # by real path, so a symlinked spelling of a configured file does not join discovery a second time (two
1925
+ # writers would withhold its constants, #644). `exclude:` drops a target from discovery as it drops a
1926
+ # configured file; the walk still follows what an excluded target requires.
1927
+ #
1928
+ # The found files are discovered as `uncertain` ({Analysis::Runner::ProjectPrePasses#uncertain_paths}): the walk
1929
+ # sees one entry point's load graph, and a file it does not reach may redefine what they define.
1930
+ def required_outside_paths(analysed)
1931
+ roots = @configuration.paths.map { |root| RequireRelativeClosure.real_path(root) }
1932
+ covered = ->(real) { roots.any? { |root| real == root || real.start_with?(File.join(root, "")) } }
1933
+ seeds = analysed.reject { |path| covered.call(RequireRelativeClosure.real_path(path)) }
1934
+ return RequireRelativeClosure::Result.new(found: [], missing: []) if seeds.empty?
1935
+
1936
+ read = ->(path) { File.read(@buffer ? @buffer.resolve(path) : path) }
1937
+ walked = RequireRelativeClosure.closure(seeds, covered: covered, read: read)
1938
+ walked.with(found: PathExpansion.reject_excluded(walked.found, @configuration.exclude_patterns,
1939
+ base: @configuration.exclude_base))
1940
+ end
1941
+
1942
+ # Issue #2021 — a kind row per `require_relative` target the walk named and found absent: a file appearing
1943
+ # there joins the discovery, so it moves the answer of the run-result cache and the `--incremental` slot.
1944
+ def required_missing_entries
1945
+ missing = @project_discovery_expansion&.fetch(:missing, nil)
1946
+ missing.nil? || missing.empty? ? [] : kind_entries(missing)
1879
1947
  end
1880
1948
 
1881
1949
  # The expansion with each file once, first spelling kept: overlapping arguments (`lib ./lib`) that do not
@@ -1887,6 +1955,23 @@ module Rigor
1887
1955
  unique.size == files.size ? expansion : expansion.merge(files: unique)
1888
1956
  end
1889
1957
 
1958
+ # Issue #2165 — the discovery set with the buffer's file under the binding's own spelling, once. The binding
1959
+ # substitutes bytes only for a path spelled exactly as its logical path, while the file can reach discovery as
1960
+ # the argument's spelling, the configured one, or through a symlink; each of those read it from disk, and a
1961
+ # second spelling made it a second declarer. Every member whose real path is the buffer's file is replaced by
1962
+ # the logical path, and the duplicates that leaves are dropped (`IncrementalSession#rebind_buffer` is option
1963
+ # B's counterpart).
1964
+ def buffer_spelled(expansion)
1965
+ return expansion if @buffer.nil?
1966
+
1967
+ target = Analysis::BufferBinding.real_path(@buffer.logical_path)
1968
+ files = expansion.fetch(:files)
1969
+ spelled = files.map do |path|
1970
+ Analysis::BufferBinding.real_path(path) == target ? @buffer.logical_path : path
1971
+ end.uniq
1972
+ spelled == files ? expansion : expansion.merge(files: spelled)
1973
+ end
1974
+
1890
1975
  # Each file once, in project order, under the spelling the analysis uses when it is analysed.
1891
1976
  def discovery_files(project_files, analysed_files)
1892
1977
  spelling = analysed_files.to_h { |path| [File.absolute_path(path), path] }
@@ -1898,7 +1983,7 @@ module Rigor
1898
1983
  end
1899
1984
 
1900
1985
  def widen_discovery_to_project?(paths)
1901
- @prebuilt.nil? && @buffer.nil? && @in_memory_sources.nil? && paths != @configuration.paths
1986
+ @prebuilt.nil? && @in_memory_sources.nil? && paths != @configuration.paths
1902
1987
  end
1903
1988
 
1904
1989
  # ADR-46 — the dependency-recording and subset-analysis modes read the discovery tables OUTSIDE the
@@ -1912,7 +1997,8 @@ module Rigor
1912
1997
 
1913
1998
  private :run_project_pre_passes, :adopt_prebuilt_project_scan, :apply_pre_passes_result,
1914
1999
  :apply_discovery_result, :ensure_project_discovery, :force_eager_discovery?,
1915
- :project_discovery_expansion, :discovery_files, :once_each, :widen_discovery_to_project?
2000
+ :project_discovery_expansion, :discovery_files, :once_each, :widen_discovery_to_project?,
2001
+ :required_outside_paths, :required_missing_entries
1916
2002
 
1917
2003
  # Ruby versions probed (ascending) to discover the lowest one this Prism build accepts for
1918
2004
  # `version:`. Prism exposes no version list, so the floor is found empirically — only when a
@@ -1970,8 +2056,7 @@ module Rigor
1970
2056
  # Editor mode § "Scope choice — option A". Under `buffer:` non-nil the per-file analysis emits
1971
2057
  # diagnostics ONLY for the buffer's logical path; the rest of `paths:` is consumed by the
1972
2058
  # project-wide pre-passes (synthetic methods, project-patched methods, plugin facts) but contributes
1973
- # no per-file diagnostics. This is the v1 cut before a per-file diagnostic cache exists; option B
1974
- # (full project + incremental cache) is queued.
2059
+ # no per-file diagnostics. Option B (#146) narrows to the closure instead; see {#target_files}.
1975
2060
  #
1976
2061
  # The buffer's logical path is added to the file list even if it's not under `paths:` — per design §
1977
2062
  # "Failure envelope": "--instead-of=Y with Y not under any paths: directory → treated as a valid
@@ -2004,7 +2089,13 @@ module Rigor
2004
2089
  # The logical path joins even when it is not on disk under `paths:` (design § "Failure envelope"),
2005
2090
  # the same allowance option A makes below.
2006
2091
  files = files.select { |path| @analyze_only.include?(path) }
2007
- 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
2008
2099
  return template_unit_targets(files)
2009
2100
  end
2010
2101
  return template_unit_targets(files) if @buffer.nil?
@@ -2040,6 +2131,14 @@ module Rigor
2040
2131
  end
2041
2132
  public :template_unit_paths
2042
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
+
2043
2142
  # Editor mode (`buffer:` non-nil) auto-flips the cache store to `read_only: true` so multiple
2044
2143
  # debounced editor invocations against the same project don't churn the on-disk cache or race on
2045
2144
  # schema-version writes. Cache reads continue unchanged; misses still run the producer block but the
@@ -2093,15 +2192,9 @@ module Rigor
2093
2192
  pool_mode: -> { pool_mode? },
2094
2193
  cached_plugin_prepare_diagnostics: -> { @cached_plugin_prepare_diagnostics },
2095
2194
  pre_eval_diagnostics_from_scanner: -> { @pre_eval_diagnostics_from_scanner },
2096
- synthesized_namespaces_snapshot: -> { @snapshots.synthesized_namespaces },
2097
- quarantined_signatures_snapshot: -> { @snapshots.quarantined_signatures },
2098
- signature_standdowns_snapshot: -> { @snapshots.signature_standdowns },
2099
- vendored_standdowns_snapshot: -> { @snapshots.vendored_standdowns },
2100
- env_build_failure_snapshot: -> { @snapshots.env_build_failure },
2101
- definition_build_failures_snapshot: -> { @snapshots.definition_build_failures },
2102
- hkt_scan_failure_snapshot: -> { @snapshots.hkt_scan_failure },
2103
- conformance_results_snapshot: -> { @snapshots.conformance_results },
2195
+ snapshots: @snapshots,
2104
2196
  pooled_run_disclosures: -> { @pool_coordinator.collected_run_disclosures },
2197
+ served_run_disclosures: -> { @served_run_disclosures },
2105
2198
  pooled_trust_refusals: -> { @pool_coordinator.collected_trust_refusals }
2106
2199
  )
2107
2200
  end
@@ -2249,10 +2342,8 @@ module Rigor
2249
2342
  # client editing a brand-new file.
2250
2343
  elsif accept_as_ruby_file?(path)
2251
2344
  files << path
2252
- elsif File.exist?(path)
2253
- bad << [path, "not a Ruby file (expected `.rb` or a directory)"]
2254
2345
  else
2255
- bad << [path, "no such file or directory"]
2346
+ bad << [path, PathExpansion.unusable_message(path)]
2256
2347
  end
2257
2348
  end
2258
2349
  { files: files, roots: Array(paths).map(&:to_s), errors: path_expansion_errors(bad, any_files: files.any?) }
@@ -2262,27 +2353,24 @@ module Rigor
2262
2353
  # app lib` with no `lib/` (the 20260620 field trial's strap case) analyses `app` instead of aborting
2263
2354
  # on exit 1. A bad path that leaves NOTHING to analyse stays an error so a lone typo (`rigor check
2264
2355
  # typo.rb`) is not silently masked (the regression the path-error check was added to catch in the
2265
- # first place).
2356
+ # first place). The wording and grading live in {PathExpansion.diagnostics}, shared with `sig-gen` (#1991).
2266
2357
  def path_expansion_errors(bad, any_files:)
2267
- severity = any_files ? :warning : :error
2268
- suffix = any_files ? " (skipped)" : ""
2269
- bad.map { |path, message| path_error(path, "#{message}#{suffix}", severity: severity) }
2358
+ PathExpansion.diagnostics(bad, any_files: any_files)
2270
2359
  end
2271
2360
 
2361
+ # #2191 — the buffer's logical path is accepted under any spelling that names the same file (relative, absolute,
2362
+ # or through a symlinked root), since the discovery set respells it to the logical path (`buffer_spelled`).
2272
2363
  def accept_as_ruby_file?(path)
2273
2364
  (File.file?(path) && path.end_with?(".rb")) ||
2274
- (@buffer && path == @buffer.logical_path) ||
2365
+ buffer_path?(path) ||
2275
2366
  @in_memory_sources&.key?(path)
2276
2367
  end
2277
2368
 
2278
- def path_error(path, message, severity: :error)
2279
- Diagnostic.new(
2280
- path: path,
2281
- line: 1,
2282
- column: 1,
2283
- message: message,
2284
- severity: severity
2285
- )
2369
+ def buffer_path?(path)
2370
+ return false if @buffer.nil?
2371
+
2372
+ path == @buffer.logical_path ||
2373
+ Analysis::BufferBinding.real_path(path) == Analysis::BufferBinding.real_path(@buffer.logical_path)
2286
2374
  end
2287
2375
 
2288
2376
  # Reads + parses the source at `path`. Under editor mode (`@buffer` set) reads bytes from
@@ -2438,14 +2526,14 @@ module Rigor
2438
2526
  @discovery_seed ? @discovery_seed.dup : {}
2439
2527
  end
2440
2528
 
2441
- # Seeds the two OPT-IN pre-pass tables — the ones an ordinary run leaves empty, so they are absent from
2442
- # the seed entirely unless the project asked for them. Both ride this seed so a pooled `WorkerSession`
2443
- # 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.
2444
2532
  #
2445
2533
  # - ADR-67 WD6a `param_inferred_types`: the call-site parameter-inference table, populated only when the
2446
2534
  # `parameter_inference:` gate ran the pre-pass.
2447
- # - Issue #352 `in_source_constants`: the `pre_eval:` constant publication, populated only when the
2448
- # 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}).
2449
2537
  #
2450
2538
  # Extracted to keep {#project_scope_seed_tables} under the complexity budget.
2451
2539
  def seed_opt_in_pre_pass_tables(tables)
@@ -2481,12 +2569,31 @@ module Rigor
2481
2569
  # widener erases it to the class, and listing a file under `pre_eval:` must never LOSE precision. The
2482
2570
  # per-file table still wins over both (`ScopeIndexer.index`'s merge) — same-file stays the most
2483
2571
  # specific authority.
2572
+ #
2573
+ # ADR-124 — a `prebuilt:` runner computes no literal table of its own; a `discovery_seed:` (the editor
2574
+ # publish, ADR-124) brings the project's, which then plays the same part.
2484
2575
  def merged_seed_constants
2485
2576
  pre_eval = publishable_pre_eval_constants
2486
- return @project_constant_values if pre_eval.empty?
2487
- return pre_eval if @project_constant_values.empty?
2577
+ literal = literal_seed_constants
2578
+ return literal if pre_eval.empty?
2579
+ return pre_eval if literal.empty?
2580
+
2581
+ pre_eval.merge(literal).freeze
2582
+ end
2583
+
2584
+ # The project's literal constant table: this run's, or the one a `discovery_seed:` carries (ADR-124).
2585
+ def literal_seed_constants
2586
+ return @project_constant_values unless @project_constant_values.empty? && @discovery_seed
2587
+
2588
+ @discovery_seed.fetch(:in_source_constants, @project_constant_values)
2589
+ end
2590
+
2591
+ # The per-name writer paths the #663 retraction reads: this run's census, or the one a `discovery_seed:` carries
2592
+ # (ADR-124).
2593
+ def constant_writer_sources
2594
+ return @project_constant_sources unless @project_constant_sources.empty? && @discovery_seed
2488
2595
 
2489
- pre_eval.merge(@project_constant_values).freeze
2596
+ @discovery_seed.fetch(:constant_sources, @project_constant_sources)
2490
2597
  end
2491
2598
 
2492
2599
  # Issue #663 — the `pre_eval:` publication with every name an UNLISTED file also assigns retracted.
@@ -2515,8 +2622,9 @@ module Rigor
2515
2622
  # name of its own for the wildcard's paths to have been folded into.
2516
2623
  def unlisted_constant_writer?(name, listed)
2517
2624
  segment = name.split("::").last
2518
- writers = @project_constant_sources[name]
2519
- wildcard = @project_constant_sources["#{Inference::ScopeIndexer::DYNAMIC_TARGET_PREFIX}#{segment}"]
2625
+ sources = constant_writer_sources
2626
+ writers = sources[name]
2627
+ wildcard = sources["#{Inference::ScopeIndexer::DYNAMIC_TARGET_PREFIX}#{segment}"]
2520
2628
  [writers, wildcard].any? do |paths|
2521
2629
  paths&.any? { |path| !listed.include?(File.absolute_path(path)) }
2522
2630
  end
@@ -2560,13 +2668,18 @@ module Rigor
2560
2668
  @pre_analysis_plugin_rows ||= plugin_boundary_rows
2561
2669
  reads = []
2562
2670
  diagnostics = nil
2563
- record = Plugin::IoBoundary.attributing(reads) do
2564
- DependencyRecorder.record_for(path) do
2565
- diagnostics = analyze_with_effects(path, environment)
2671
+ record = nil
2672
+ # Issue #2020 — and every run-scoped row its analysis registered, with the registering plugins' reads.
2673
+ disclosures = RunDisclosureCredit.collect do
2674
+ record = Plugin::IoBoundary.attributing(reads) do
2675
+ DependencyRecorder.record_for(path) do
2676
+ diagnostics = analyze_with_effects(path, environment)
2677
+ end
2566
2678
  end
2567
2679
  end
2568
2680
  @file_dependencies[path] = record
2569
2681
  @analysis_reads[path] = reads unless reads.empty?
2682
+ @analysis_disclosures[path] = disclosures unless disclosures.empty?
2570
2683
  diagnostics
2571
2684
  end
2572
2685