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
@@ -18,7 +18,7 @@ module Rigor
18
18
  # recompute" lesson applied to self-calls.
19
19
  #
20
20
  # ADR-24 slice 4 (`call.self-undefined-method`) consumes the recorded misses behind a confidently-closed-class
21
- # gate (see `CheckRules` L775). The rule ships `:off` by default — {active?} is false on a normal run, so
21
+ # gate (see `CheckRules`). The rule ships `:off` by default — {active?} is false on a normal run, so
22
22
  # the instrumented choke-point pays a single integer read and records nothing. Recording is purely
23
23
  # observational; it never changes a diagnostic.
24
24
  #
@@ -173,9 +173,8 @@ module Rigor
173
173
  # copies one tag body once, and the second `@author` is the same copy seen from the other end, not a
174
174
  # second reading. Only a node OUTSIDE the winning run's own compiled span counts, which is what keeps
175
175
  # the busy lines of a real view answerable while the cross-tag repeat (`<%= v %> <%= v.to_s %>`, or
176
- # `<%= v %><%= "v" %>` probed at the STRING's `v`) still declines — the family a plugin-exported tag
177
- # span would answer instead
178
- # (see the PR), and the follow-up this rule is deliberately conservative ahead of.
176
+ # `<%= v %><%= "v" %>` probed at the STRING's `v`) still declines. That family is what a plugin-exported
177
+ # tag span would answer instead, and it is the follow-up this rule is deliberately conservative ahead of.
179
178
  #
180
179
  # `spill` is the nearest NON-BLANK compiled line above this template line's own, where a hoisted
181
180
  # leading-text literal lives. Only a string literal the run does NOT contain counts there. A code
@@ -189,6 +189,15 @@ module Rigor
189
189
  @entries.keys.sort
190
190
  end
191
191
 
192
+ # Issue #2233 — whether a plugin claims `path` by one of its `template_globs:`, in any spelling a caller holds it
193
+ # (absolute, `./`-prefixed, relative). A claimed template is analysed as a unit, never as a plain `.rb` file, so
194
+ # the runner keeps the buffer's logical path out of the `.rb` targets, so it is not analysed twice.
195
+ def claims?(path)
196
+ return false if @claimed_globs.empty?
197
+
198
+ TemplateUnitPaths.claims?(@claimed_globs, TemplateUnitPaths.relative(path, @root))
199
+ end
200
+
192
201
  def [](path)
193
202
  entry_for(path)
194
203
  end
@@ -293,7 +302,6 @@ module Rigor
293
302
  [entry.locals.keys.map(&:to_sym)]
294
303
  end
295
304
 
296
- # The `view:<logical_name>` effect-unit key for a path, or nil.
297
305
  # The file whose bytes a unit's path was READ from: the editor's buffer when one is bound to it, the
298
306
  # project file otherwise. Read by the run's dependency descriptor, so it digests what the run read.
299
307
  def physical_path(path, buffer)
@@ -20,10 +20,12 @@ require_relative "../inference/method_dispatcher/file_folding"
20
20
  require_relative "check_rules"
21
21
  require_relative "crash_signature"
22
22
  require_relative "dependency_recorder"
23
+ require_relative "run_disclosure_credit"
23
24
  require_relative "dependency_source_inference"
24
25
  require_relative "diagnostic"
25
26
  require_relative "erb_template_detector"
26
27
  require_relative "template_units"
28
+ require_relative "runner/run_level_facts"
27
29
 
28
30
  module Rigor
29
31
  module Analysis
@@ -38,8 +40,8 @@ module Rigor
38
40
  # - `configuration` — Phase 2a ({Rigor::Configuration} is `Ractor.shareable?`).
39
41
  # - `cache_store` — the fork backend passes the parent runner's pre-built Store (`cache_store:
40
42
  # @cache_store` in PoolCoordinator); workers share it rather than building their own at `cache_root`.
41
- # - `plugin_blueprints` — Phase 3a (`Array<Plugin::Blueprint>` is `Ractor.shareable?`).
42
- # - `explain` — Boolean.
43
+ # - `plugin_blueprints` — Phase 3a (a list of `Plugin::Blueprint` is `Ractor.shareable?`).
44
+ # - `explain` — a flag.
43
45
  # - `synthetic_method_index` / `project_patched_methods` / `project_scope_seed` — optional (default `nil`
44
46
  # / `{}`). NOT `Ractor.shareable?` (the seed tables carry Prism def nodes), so the Ractor pool path
45
47
  # leaves them unset; the fork backend (ADR-15 Amendment), which builds the session pre-fork on the
@@ -108,7 +110,7 @@ module Rigor
108
110
  # above: a worker and the parent agree without a flag to keep in sync.
109
111
  @effect_attribution = Effects::Attribution.build(configuration.effects_attribution)
110
112
  @file_effects = {}
111
- # ADR-32 WD4 — full project file list (frozen Array<String>) for env-build-time invocation of any
113
+ # ADR-32 WD4 — full project file list (frozen) for env-build-time invocation of any
112
114
  # loaded plugin's `source_rbs_synthesizer` callable.
113
115
  @source_files = source_files
114
116
  # #392 — the run's template units, synthesised on the PARENT before the fork and inherited here as
@@ -161,7 +163,7 @@ module Rigor
161
163
  end
162
164
 
163
165
  # Equivalent of {Rigor::Analysis::Runner#analyze_file} + `plugin_emitted_diagnostics` +
164
- # `explain_diagnostics`. Returns a flat `Array<Diagnostic>` for the file. Severity profile re-stamping
166
+ # `explain_diagnostics`. Returns a flat list of diagnostics for the file. Severity profile re-stamping
165
167
  # is intentionally NOT applied — that is a per-run aggregate concern handled by the caller. When
166
168
  # `record_dependencies:` was set, wraps the analysis in a {DependencyRecorder} window (ADR-46) so the
167
169
  # worker captures the file's cross-file reads for {#drain_dependencies}; the recorder's own disabled
@@ -171,12 +173,17 @@ module Rigor
171
173
 
172
174
  diagnostics = nil
173
175
  reads = []
174
- record = Plugin::IoBoundary.attributing(reads) do
175
- DependencyRecorder.record_for(path) { diagnostics = analyze_with_effects(path) }
176
+ record = nil
177
+ # Issue #2020 — and the run-scoped rows it registered, as `Runner#analyze_file` keeps them.
178
+ disclosures = RunDisclosureCredit.collect do
179
+ record = Plugin::IoBoundary.attributing(reads) do
180
+ DependencyRecorder.record_for(path) { diagnostics = analyze_with_effects(path) }
181
+ end
176
182
  end
177
183
  @file_dependencies[path] = record
178
184
  # Issue #1553 — the plugin reads this file's analysis made, as `Runner#analyze_file` keeps them.
179
185
  (@analysis_reads ||= {})[path] = reads unless reads.empty?
186
+ (@analysis_disclosures ||= {})[path] = disclosures unless disclosures.empty?
180
187
  diagnostics
181
188
  end
182
189
 
@@ -265,7 +272,7 @@ module Rigor
265
272
  # definition and never enters the failing build; a diagnostic wired off the parent's loader would
266
273
  # appear at `--workers=0` and vanish at `--workers=N`, which is the same "reports less depending on
267
274
  # how you ran it" defect the diagnostic exists to end. Draining it out of the workers is what makes
268
- # the two paths say the same thing. The payload is `[String, String, String, Array<String>]` tuples —
275
+ # the two paths say the same thing. The payload is a list of plain tuples —
269
276
  # Marshal-clean for the fork backend and shareable for the Ractor one.
270
277
  #
271
278
  # Issue #784 — `hkt_scan_failure` rides the same channel for the same reason: the PARENT's own
@@ -278,33 +285,25 @@ module Rigor
278
285
  # project whose `sig/` produced a single such event killed each worker HERE — after its files were
279
286
  # analysed — and the run degraded to in-process re-analysis with a `pool-degraded` row. All three
280
287
  # streams now carry `(path, line, column)` primitives.
288
+ #
289
+ # ADR-116 WD3 (#1195) — both halves are declared rather than listed here: each reporter snapshots its
290
+ # own streams ({Runner::RunLevelFacts::REPORTERS}), and the `:analysis` facts come from
291
+ # {Runner::RunLevelFacts.drain}, the same read the coordinator makes of an environment it analysed on.
292
+ # The facts are read first: their HKT demand (#784, made once per worker so a worker whose share of
293
+ # files demanded nothing still reports the run's outcome) is what fills the hkt-directive stream (#785).
294
+ # On a worker that analysed a file the demand is a memoised read; the fork backend never hands a worker
295
+ # an empty slice, so no idle worker pays the env load.
281
296
  def drain_reporters
282
- # Issue #784 — demand the registry once per worker before reading the slot, so a worker whose share
283
- # of files happened to contain no `Klass.method` call still reports the run's outcome (the same
284
- # reason `PoolCoordinator#hkt_scan_outcome` demands it on the sequential path). The Environment
285
- # object is built in the constructor, but its RBS env is lazy: on a worker that analysed a file this
286
- # is a memoised read; on one that demanded nothing it is the same cache-served env load that file
287
- # would have paid. The fork backend never hands a worker an empty slice, so no idle worker pays it.
288
- @environment&.hkt_registry
289
- {
290
- rbs_extended: {
291
- unresolved_payloads: @rbs_extended_reporter.unresolved_payloads,
292
- lossy_projections: @rbs_extended_reporter.lossy_projections,
293
- # Issue #785 — the HKT directive stream rides the same channel for the same reason
294
- # `hkt_scan_failure` does: the directives are read by the registry scan, and under the pool the
295
- # PARENT never demands that scan, so a diagnostic wired off the parent's reporter would appear
296
- # at `--workers=0` and vanish at `--workers=N`. The demand above is what fills this.
297
- hkt_directive_errors: @rbs_extended_reporter.hkt_directive_errors,
298
- # ADR-109 WD3 — the deprecated-form stream rides the same channel: the payload is read where
299
- # the annotated method is dispatched, which under the pool is only ever inside a worker.
300
- deprecated_forms: @rbs_extended_reporter.deprecated_forms
301
- },
302
- boundary_cross: @boundary_cross_reporter.entries,
303
- source_rbs_synthesis: @source_rbs_synthesis_reporter.entries,
304
- definition_build_failures: @environment&.rbs_loader&.definition_build_failures || [],
305
- hkt_scan_failure: @environment&.hkt_scan_failure
306
- }
297
+ facts = Runner::RunLevelFacts.drain(@environment)
298
+ Runner::RunLevelFacts.snapshot_reporters(reporters).merge(facts)
299
+ end
300
+
301
+ # The per-worker reporter accumulators, by {Runner::RunLevelFacts::REPORTERS} key.
302
+ def reporters
303
+ { rbs_extended: @rbs_extended_reporter, boundary_cross: @boundary_cross_reporter,
304
+ source_rbs_synthesis: @source_rbs_synthesis_reporter }
307
305
  end
306
+ private :reporters
308
307
 
309
308
  # ADR-46 — the per-file {DependencyRecorder::Record}s captured by this session's `#analyze` calls (empty
310
309
  # unless `record_dependencies:` was set). Marshal-clean (Data of frozen Sets / String Hashes), so the
@@ -320,6 +319,12 @@ module Rigor
320
319
  @analysis_reads || {}
321
320
  end
322
321
 
322
+ # Issue #2020 — the run-scoped rows each file's analysis registered, `{path => [record]}`; see
323
+ # {RunDisclosureCredit}. Marshal-clean like {#drain_run_disclosures}.
324
+ def drain_analysis_disclosures
325
+ @analysis_disclosures || {}
326
+ end
327
+
323
328
  # ADR-103 WD12 — the per-file {Effects::FileCollection}s this session collected (empty unless the
324
329
  # configuration carries an `effects:` block). Marshal-clean by construction — frozen Hashes of
325
330
  # Strings, `LabelSet`s of frozen Arrays, and `Data` edges — so the fork pool ships them back to the
@@ -445,7 +450,7 @@ module Rigor
445
450
  return nil if gem_name.nil? || gem_name.empty?
446
451
 
447
452
  spec = Gem.loaded_specs[gem_name]
448
- spec&.full_gem_path # rigor:disable undefined-method
453
+ spec&.full_gem_path
449
454
  rescue StandardError
450
455
  nil
451
456
  end
@@ -9,11 +9,9 @@ module Rigor
9
9
  # *today's* rules are) and is versioned with the gem, NOT a user-supplied file: the
10
10
  # inspectable counterpart to PHPStan's `bleedingEdge` include.
11
11
  #
12
- # The WD2 foundation slice wired the surface end-to-end (`bleeding_edge:` config, the
13
- # `rigor show-bleedingedge` command, the severity-composition hook in
14
- # {Configuration::SeverityProfile.resolve}), so a *severity* discipline lands as a single
15
- # {FEATURES} entry with no engine plumbing — as the first one,
16
- # `reject-unparseable-signatures`, does.
12
+ # The surface is wired end-to-end (`bleeding_edge:` config, `rigor show-bleedingedge`, the
13
+ # severity-composition hook in {Configuration::SeverityProfile.resolve}), so a *severity*
14
+ # discipline is a single {FEATURES} entry with no engine plumbing.
17
15
  #
18
16
  # A queued change whose effect is not a severity move — a measurement, an algorithm, or a
19
17
  # default that changes while every rule keeps its severity — is a `:behaviour` feature
@@ -40,7 +38,6 @@ module Rigor
40
38
  # {Configuration#bleeding_edge_active?}.
41
39
  KINDS = %i[severity behaviour].freeze
42
40
 
43
- # The severity map a `:behaviour` feature carries: none.
44
41
  NO_SEVERITY_OVERRIDES = {}.freeze
45
42
 
46
43
  # One queued change.
@@ -113,9 +113,8 @@ module Rigor
113
113
  },
114
114
  # ADR-13 § "Canonical type-function additions" — five shape-projection type functions
115
115
  # that the `rigor-typescript-utility-types` plugin (and any other plugin that ships a
116
- # shape-projection vocabulary) maps onto. Phase A handles `HashShape` carriers;
117
- # non-shape inputs return the input unchanged (the lossy-projection diagnostic lands
118
- # in slice 5).
116
+ # shape-projection vocabulary) maps onto. Non-shape inputs return the input unchanged;
117
+ # the Resolver records `dynamic.shape.lossy-projection` for them.
119
118
  "pick_of" => lambda { |args|
120
119
  return nil unless args.size == 2
121
120
 
@@ -148,7 +147,7 @@ module Rigor
148
147
  # `Class[range]` — a numeric class head bounded by a Ruby range literal (ADR-109). The
149
148
  # builder receives the `Range` value and returns the carrier, or `nil` when the range is
150
149
  # empty or its endpoints are not literals of the head class; `nil` surfaces as the ordinary
151
- # unresolved-payload diagnostic. `Float` joins this table with the `FloatRange` carrier.
150
+ # unresolved-payload diagnostic. `Float` builds a `FloatRange` carrier.
152
151
  RANGE_HEAD_BUILDERS = {
153
152
  "Integer" => lambda { |range|
154
153
  lo = range.begin
@@ -186,8 +185,7 @@ module Rigor
186
185
  # `Array<Integer>` and returns a `Rigor::Type` (or `nil`). Bounds are signed integer
187
186
  # literals. A reversed pair (`min` > `max`) is declined as `nil` like every other shape
188
187
  # mismatch, so the caller surfaces `dynamic.rbs-extended.unresolved` at the annotation;
189
- # `Type::IntegerRange` raises on it instead, and that `ArgumentError` used to escape as an
190
- # `internal analyzer error` that abandoned the whole Ruby file.
188
+ # `Type::IntegerRange` would raise on it instead, which must not escape the analyzer.
191
189
  PARAMETERISED_INT_BUILDERS = {
192
190
  "int" => lambda { |bounds|
193
191
  return nil unless bounds.size == 2
@@ -276,14 +274,14 @@ module Rigor
276
274
  # grammar:
277
275
  #
278
276
  # type := simple_name | parametric
279
- # simple_name := /[a-z][a-z0-9-]*/
277
+ # simple_name := /[a-z][a-z0-9_-]*/
280
278
  # parametric := simple_name '[' type_arg_list ']'
281
279
  # | simple_name '<' int_bound_list '>'
282
280
  # type_arg_list := type_arg (',' type_arg)*
283
281
  # type_arg := range_literal | type | class_name
284
282
  # class_name := /[A-Z][A-Za-z0-9_]*(?:::[A-Z][A-Za-z0-9_]*)*/
285
283
  # range_literal := endpoint? ('..' | '...') endpoint? (at least one side written)
286
- # endpoint := signed_int | 'nil'
284
+ # endpoint := signed_int | float_literal | 'nil' | 'Float::INFINITY' | 'Float::MAX'
287
285
  # int_bound_list := signed_int (',' signed_int)*
288
286
  # signed_int := /-?\d+/
289
287
  #
@@ -62,7 +62,7 @@ module Rigor
62
62
  # `RBS::VERSION` while Rigor checks itself, rubygems and Bundler internals while it checks
63
63
  # CRuby's `lib/` — and that is worse than no answer: identical source would type differently
64
64
  # depending on how the analyzer was installed. Removing an ad hoc `Object.const_get` from an
65
- # analysis path is the same move ADR-4 § 330 already made for predicate narrowing.
65
+ # analysis path is the same move ADR-4 already made for predicate narrowing.
66
66
  #
67
67
  # This module is consulted by `Environment#constant_for_name` BEFORE the RBS
68
68
  # constant-type table (widest types) but AFTER in-source constant writes (the user's own
@@ -8,13 +8,11 @@ module Rigor
8
8
  # carriers Rigor already ships (`decimal-int-string`, `hex-int-string`,
9
9
  # `octal-int-string`, `lowercase-string`, `uppercase-string`, `numeric-string`). See
10
10
  # `docs/type-specification/imported-built-in-types.md` for the registry the refinements
11
- # come from and `docs/ROADMAP.md` § "v0.1.1 — Planned" Track 1 slice 1 for the binding
12
- # scope of this recogniser.
11
+ # come from.
13
12
  #
14
- # The intended consumer is `Inference::Narrowing.analyse_match_write`: given `if
15
- # /(?<year>\d+)/ =~ str; year; end`, the v0.1.0 baseline narrows `year` to plain `String`;
16
- # v0.1.1 introspects the regex source and narrows further to `decimal-int-string`
17
- # whenever the named-capture body matches one of the rows in {RULES}.
13
+ # The consumer is `Inference::Narrowing.analyse_match_write`: given `if /(?<year>\d+)/ =~ str;
14
+ # year; end`, `year` narrows to `decimal-int-string` when the named-capture body matches a row
15
+ # in {RULES}, and stays plain `String` otherwise.
18
16
  #
19
17
  # Recognised body shapes (each row admits the `+` quantifier and the bounded `{n}` /
20
18
  # `{n,m}` forms with `n >= 1`):
@@ -10,7 +10,8 @@ module Rigor
10
10
  module Cache
11
11
  # Cache invalidation descriptor — the typed-slot schema fixed by
12
12
  # [`docs/design/20260505-cache-slice-taxonomy.md`](../../../docs/design/20260505-cache-slice-taxonomy.md).
13
- # Pure value object: no I/O, no global state, fully immutable after construction. The storage layer
13
+ # Value object, fully immutable after construction. Filesystem reads happen only in the `FileEntry.stat`,
14
+ # `FileEntry.kind` and `GlobEntry.compute` builders and in {#fresh?}. The storage layer
14
15
  # ([`Rigor::Cache::Store`](store.rb), v0.0.8 slice 2) consumes descriptors but does not extend them.
15
16
  #
16
17
  # The descriptor has six slots (`files`, `gems`, `plugins`, `configs`, `dependencies`, `globs`); every slot is
@@ -462,10 +463,10 @@ module Rigor
462
463
 
463
464
  # ADR-45 — re-validates this descriptor's recorded {FileEntry}s against the current filesystem. Used by
464
465
  # the record-and-validate run-result cache: a value cached alongside its dependency descriptor is fresh
465
- # iff every recorded file still matches. Only `files` are checked — non-file inputs (config / gems /
466
+ # iff every recorded file still matches. Only `files` and `globs` are checked — non-file inputs (config / gems /
466
467
  # version) belong in the cache *key*, not the validated dependency set — so a descriptor carrying any
467
468
  # non-file slot is never considered fresh (it was built wrong for this use). ADR-60 WD3 adds `globs`
468
- # alongside `files` as a re-validatable slot: a {GlobEntry} is fresh when re-globbing + re-digesting
469
+ # alongside `files` as a re-validatable slot: a {GlobEntry} is fresh when re-globbing
469
470
  # reproduces its recorded value. ADR-45 WD1 (#577): an absence row ({FileEntry.absent}) is fresh
470
471
  # while its path is still missing — the negative half of the dependency set.
471
472
  def fresh?
@@ -581,8 +582,8 @@ module Rigor
581
582
  false
582
583
  end
583
584
 
584
- # ADR-60 WD3 / ADR-87 WD2 — stat-validates the entry's per-file table against the live tree, re-hashing
585
- # only stat-moved files. Any failure reads as stale (recompute), never a crash.
585
+ # ADR-60 WD3 / ADR-87 WD2 — recomputes the entry's signature over the live tree and compares it. Any
586
+ # failure reads as stale (recompute), never a crash.
586
587
  def glob_entry_fresh?(entry)
587
588
  GlobEntry.fresh?(entry)
588
589
  end
@@ -8,6 +8,7 @@ require_relative "engine_source"
8
8
  require_relative "store"
9
9
  require_relative "../analysis/shadow_harness"
10
10
  require_relative "../analysis/run_cache_key"
11
+ require_relative "../analysis/runner/run_level_facts"
11
12
  require_relative "../environment"
12
13
  require_relative "../analysis/path_expansion"
13
14
  require_relative "../rooted_glob"
@@ -182,20 +183,39 @@ module Rigor
182
183
  # 41: issue #1583 — the payload gains `discovery_digests`, the packed digest of each file a run over path
183
184
  # arguments discovered without analysing, which a recheck compares to re-baseline when one moved. A pre-41
184
185
  # blob carries none; the gate rejects it and the next run is cold.
185
- SCHEMA = 41
186
+ # 42: issue #2184 — a `deferred_ranges` row for a `def` inside a block now carries the method's name, and a
187
+ # literal-named `define_method` / `define_singleton_method` call adds a row. A pre-42 bundle carries neither,
188
+ # so a warm run would report a top-level block call that a cold run declines.
189
+ # 43: issue #2020 — the payload gains `plugin_disclosures`, the run-scoped plugin rows each analysed file's
190
+ # last analysis registered, which a recheck replays for the files it serves. A pre-43 blob carries none, so
191
+ # its rechecks would drop every such row; the gate rejects it and the next run is cold.
192
+ # 44: PR #2286 review M1 — the payload gains `synthetic_surface_digest`, the ADR-16 synthetic-method index's
193
+ # {Rigor::Inference::SyntheticMethodIndex#surface_digest} at the run the cache was built under, which a recheck
194
+ # compares to re-baseline when a class-body DSL edit moved it. A pre-44 blob carries none; the gate rejects it
195
+ # and the next run is cold.
196
+ # 45: issue #1154 — each seed bundle's `global_write_census` also records the ivar writers the declared-parameter
197
+ # credit reads (`[:ivar_set, name]`, `[:ivar_set_class, class]`, `[:own_write, class, name, path]`), which a
198
+ # pre-45 bundle carries none of, so a warm run would credit an ivar write a cold run declines.
199
+ # 46: issue #2314 — each seed bundle's `global_write_census` also records the global writers
200
+ # (`[:global_writer, name, method]`) the statement walk reads past a call; a pre-46 bundle carries none, so a
201
+ # warm run would keep a global's binding a cold run forgets.
202
+ # 47: issues #2079 / #2170 — each seed bundle's `global_write_census` also records each instance-side ivar
203
+ # write's coarse value (`[:own_write_type, class, name, value, path]`), which the class-ivar census joins across
204
+ # files and along the chain; a pre-47 bundle carries none, so a warm run would keep a seed a cold run widens.
205
+ SCHEMA = 47
186
206
 
187
207
  # The persisted per-file state.
188
208
  # `cache` maps an analyzed file to its diagnostics.
189
- # `sources` maps a consumer to the Set of source files it read from.
209
+ # `sources` maps a consumer to the source files it read from.
190
210
  # `digests` maps a file to its content digest at analysis time.
191
211
  # `analyzed` is the ordered analyzed-file list.
192
212
  # ADR-46 slice 4:
193
- # `symbol_sources` maps a consumer to { source_path → Set<"ClassName#method"> }.
194
- # `ancestry_sources` maps a consumer to Set<source_path> (class-ancestry deps).
213
+ # `symbol_sources` maps a consumer to { source_path → the "ClassName#method" names it read }.
214
+ # `ancestry_sources` maps a consumer to the source paths it read for class ancestry.
195
215
  # `symbol_fingerprints` maps a path to { "ClassName#method" => sha256_hex }.
196
216
  # ADR-46 slice 3:
197
- # `missing` maps a consumer to Set<"kind:name"> it looked up and missed.
198
- # `class_decls` maps a path to Set<qualified class name> it declares.
217
+ # `missing` maps a consumer to the "kind:name" keys it looked up and missed.
218
+ # `class_decls` maps a path to the qualified class names it declares.
199
219
  # Issue #644:
200
220
  # `constant_decls` maps a path to that file's constant PUBLICATION CENSUS,
201
221
  # `{qualified name => [literal] | :unpublishable}`. The descriptor is what the recheck diffs: a name's
@@ -244,26 +264,36 @@ module Rigor
244
264
  # `effect_collections` (nil when collection was off). A plugin stamps its attributions and edges into a file's
245
265
  # collection, and `effects_identity` is plugin-blind (it is compared before any plugin loads), so the session
246
266
  # compares this one after the recheck has loaded them.
267
+ # Issue #2020 — `plugin_disclosures` is {Analysis::RunDisclosureLedger#to_h}: `files`, each analysed file's
268
+ # run-scoped plugin rows ({Plugin::Base#disclose_once} / {Plugin::Base#emit_once}) from its last analysis, which a
269
+ # recheck replays for the files it serves, and `reads`, the boundary rows per `[plugin id, key]` that make one
270
+ # stale.
247
271
  Payload = Data.define(:cache, :sources, :digests, :analyzed,
248
272
  :symbol_sources, :ancestry_sources, :symbol_fingerprints,
249
273
  :missing, :class_decls, :constant_decls, :seed_bundles, :plugin_fact_digest,
250
274
  :return_summaries, :param_table,
251
275
  :effect_collections, :effects_identity, :run_level_rows,
252
- :effect_plugin_digest, :plugin_reads, :discovery_digests) do
276
+ :effect_plugin_digest, :plugin_reads, :discovery_digests, :plugin_disclosures,
277
+ :synthetic_surface_digest) do
253
278
  # `effect_plugin_digest` defaults to nil so a payload built without it (an older caller, a spec fixture)
254
279
  # reads as "unknown", which the session treats as moved. `plugin_reads` defaults to no reads, and
255
- # `discovery_digests` to no discovery-only files.
256
- def initialize(effect_plugin_digest: nil, plugin_reads: {}, discovery_digests: {}, **rest)
280
+ # `discovery_digests` to no discovery-only files, and `plugin_disclosures` (#2020) to no replayed rows.
281
+ # `synthetic_surface_digest` defaults to nil, the empty index's digest: a run that builds a non-empty one
282
+ # reads it as moved.
283
+ def initialize(effect_plugin_digest: nil, plugin_reads: {}, discovery_digests: {}, plugin_disclosures: {},
284
+ synthetic_surface_digest: nil, **rest)
257
285
  super
258
286
  end
259
287
  end
260
288
 
261
- # Issues #796 / #794 — the two run-level rows the snapshot carries. `definition_build_failures` is the
262
- # reported per-class detail list ({Environment::RbsLoader#definition_build_failures}); `hkt_scan_failure`
263
- # is the `[error_class, first_line, frame, stage]` tuple ({Rigor::Environment#hkt_scan_failure}), nil
264
- # when the scan succeeded — which is why the presence of this object, not the tuple, is what says a
265
- # replay is available.
266
- RunLevelRows = Data.define(:definition_build_failures, :hkt_scan_failure)
289
+ # Issues #796 / #794 — the run-level rows the snapshot carries: one member per run-level fact declared
290
+ # `replay` ({Analysis::Runner::RunLevelFacts}, ADR-116 WD3). Today that is `definition_build_failures`,
291
+ # the reported per-class detail list ({Environment::RbsLoader#definition_build_failures}), and
292
+ # `hkt_scan_failure`, the `[error_class, first_line, frame, stage]` tuple
293
+ # ({Rigor::Environment#hkt_scan_failure}), nil when the scan succeeded — which is why the presence of
294
+ # this object, not the tuple, is what says a replay is available. Declaring another fact `replay`
295
+ # changes this shape, so it bumps {SCHEMA}.
296
+ RunLevelRows = Data.define(*Analysis::Runner::RunLevelFacts.replayable_names)
267
297
 
268
298
  # The global fingerprint that gates a snapshot load: a digest of the inputs whose change requires a full
269
299
  # rebuild — the engine version + schema, the engine's own SOURCE when the version does not pin it, the
@@ -303,6 +333,7 @@ module Rigor
303
333
  "sig:#{digest_signature_paths(project_sig_roots(configuration))}",
304
334
  "pre_eval:#{digest_pre_eval(configuration.pre_eval)}"
305
335
  ]
336
+ parts.concat(core_root_parts(configuration))
306
337
  parts.concat(dependency_source_parts(configuration))
307
338
  identity = EngineSource.process_identity
308
339
  parts << "engine-source:#{identity}" if identity
@@ -326,6 +357,15 @@ module Rigor
326
357
  nil
327
358
  end
328
359
 
360
+ # Issue #2517 — a selected core is read like the project's own `sig/`, so an edit in place to one of its files
361
+ # must drop the snapshot (and the language server's session, which keys on this fingerprint). No part for the
362
+ # default core, so every other project's fingerprint is as it was.
363
+ def self.core_root_parts(configuration)
364
+ core_root = configuration.core_root
365
+ core_root.nil? ? [] : ["core:#{digest_signature_paths([core_root])}"]
366
+ end
367
+ private_class_method :core_root_parts
368
+
329
369
  def self.required_features(configuration, roots)
330
370
  files = Analysis::PathExpansion.ruby_files(roots, configuration.exclude_patterns,
331
371
  base: configuration.exclude_base)
@@ -465,7 +505,9 @@ module Rigor
465
505
  run_level_rows: data[:run_level_rows],
466
506
  effect_plugin_digest: data[:effect_plugin_digest],
467
507
  plugin_reads: data[:plugin_reads] || {},
468
- discovery_digests: data.fetch(:discovery_digests, {})
508
+ plugin_disclosures: data.fetch(:plugin_disclosures, {}),
509
+ discovery_digests: data.fetch(:discovery_digests, {}),
510
+ synthetic_surface_digest: data[:synthetic_surface_digest]
469
511
  )
470
512
  end
471
513
  private :payload_from
@@ -492,8 +534,8 @@ module Rigor
492
534
  effects_identity: payload.effects_identity,
493
535
  run_level_rows: payload.run_level_rows,
494
536
  effect_plugin_digest: payload.effect_plugin_digest,
495
- plugin_reads: payload.plugin_reads,
496
- discovery_digests: payload.discovery_digests
537
+ plugin_reads: payload.plugin_reads, plugin_disclosures: payload.plugin_disclosures,
538
+ discovery_digests: payload.discovery_digests, synthetic_surface_digest: payload.synthetic_surface_digest
497
539
  )
498
540
  # #1507 — every `--incremental` run that analysed anything rewrites this whole blob, so its deflate is
499
541
  # on the edit path; see {Store::DEFLATE_LEVEL}.
@@ -6,8 +6,8 @@ module Rigor
6
6
  module Cache
7
7
  # Base for the RBS-derived cache producers.
8
8
  #
9
- # Every producer (`RbsKnownClassNames`, `RbsConstantTable`, `RbsEnvironment`, the ancestor / type-param /
10
- # definition tables, …) repeated the identical `fetch` wiring: build the RBS descriptor, then
9
+ # Every producer (`RbsKnownClassNames`, `RbsConstantTable`, `RbsEnvironment`, the ancestor / type-param
10
+ # tables, …) repeated the identical `fetch` wiring: build the RBS descriptor, then
11
11
  # `store.fetch_or_compute` under the producer's id, yielding to the producer's `compute`. Only the
12
12
  # `PRODUCER_ID` constant and the `compute(loader)` body actually differ between producers.
13
13
  #
@@ -6,7 +6,7 @@ require_relative "rbs_cache_producer"
6
6
  module Rigor
7
7
  module Cache
8
8
  # Cache producer that materialises the RBS-declared ancestor chain of every loaded class / module into a
9
- # Marshal-clean `Hash<String, Array<String>>`. Ancestor names are top-level-stripped (e.g. `"Integer"` not
9
+ # Marshal-clean table. Ancestor names are top-level-stripped (e.g. `"Integer"` not
10
10
  # `"::Integer"`) to match `Environment::RbsHierarchy#normalize_name`.
11
11
  #
12
12
  # The hierarchy is the substrate behind every `class_ordering` query, which is itself a hot path on the
@@ -6,8 +6,8 @@ require_relative "rbs_cache_producer"
6
6
  module Rigor
7
7
  module Cache
8
8
  # Cache producer that materialises every loaded class's RBS-declared type-parameter names as a
9
- # Marshal-clean `Hash<String, Array<Symbol>>` keyed by top-level-stripped class name (e.g. `"Array"` →
10
- # `[:Elem]`, `"Hash"` → `[:K, :V]`). Producer id `"rbs.class_type_param_names"`.
9
+ # Marshal-clean table keyed by top-level-stripped class name (e.g. `"Array"` →
10
+ # `[:Elem]`, `"Hash"` → `[:K, :V]`).
11
11
  #
12
12
  # The dispatcher reads type-parameter names every time it builds a substitution map from a receiver's
13
13
  # `type_args` into a method's return type — it is one of the hottest reflection lookups during analysis.
@@ -6,7 +6,7 @@ require_relative "rbs_cache_producer"
6
6
  module Rigor
7
7
  module Cache
8
8
  # Cache producer that materialises every RBS-declared constant to its translated `Rigor::Type` form and
9
- # stores the result as a `Hash<String, Rigor::Type>` keyed by canonical constant name. This is the v0.0.8
9
+ # stores the result as a table keyed by canonical constant name. This is the v0.0.8
10
10
  # first cached producer per ADR-6 § 7; it caches a post-translation artefact so the cache value is
11
11
  # `Marshal`-clean (RBS-native objects carry `RBS::Location`, which lacks `_dump_data`).
12
12
  #
@@ -77,9 +77,57 @@ module Rigor
77
77
  # reconstructs {.config_entries} without loading a plugin, so an entry there that it cannot rebuild
78
78
  # would turn the warm fast lane into a permanent miss on every project that enables a bundled plugin.
79
79
  def self.env_only_config_entries(loader)
80
- [deferred_signature_paths_entry(loader)].compact
80
+ [deferred_signature_paths_entry(loader), project_signature_paths_entry(loader), core_root_entry(loader)].compact
81
81
  end
82
82
 
83
+ # Issue #2517 — which directory the env loaded core from. The selected core's files are digested by
84
+ # {.file_entries} too, so this row is the identity of the choice and those rows its contents: switching
85
+ # between two roots with byte-identical files still re-keys. Absent for the default core, whose identity
86
+ # is the `rbs` gem row, so every project without the setting keeps its key.
87
+ def self.core_root_entry(loader)
88
+ core_root = selected_core_root(loader)
89
+ return nil if core_root.nil?
90
+
91
+ Descriptor::ConfigEntry.new(key: "rbs.core_root", value_hash: Digest::SHA256.hexdigest(core_root.to_s))
92
+ end
93
+
94
+ private_class_method :core_root_entry
95
+
96
+ # The loader's issue #2517 core root as a Pathname, or nil for the default core.
97
+ def self.selected_core_root(loader)
98
+ core_root = loader.respond_to?(:core_root) ? loader.core_root : nil
99
+ core_root.nil? ? nil : Pathname(core_root)
100
+ end
101
+
102
+ private_class_method :selected_core_root
103
+
104
+ # {.selected_core_root} as the signature roots it adds: none for the default core.
105
+ def self.core_root_dirs(loader)
106
+ core_root = selected_core_root(loader)
107
+ core_root.nil? ? [] : [core_root]
108
+ end
109
+
110
+ private_class_method :core_root_dirs
111
+
112
+ # Issue #2422 — which of the loader's `signature_paths:` are the project's own changes the env built from
113
+ # byte-identical files: a bundled plugin's method composes with another source's as an overload, but stands
114
+ # down to the project's own. Only read when a plugin defers signatures, so a project without one keeps its
115
+ # key.
116
+ def self.project_signature_paths_entry(loader)
117
+ return nil unless loader.respond_to?(:project_signature_paths) && loader.respond_to?(:deferred_signature_paths)
118
+ return nil if Array(loader.deferred_signature_paths).empty?
119
+
120
+ own = Array(loader.project_signature_paths).map(&:to_s).sort
121
+ return nil if own.empty?
122
+
123
+ Descriptor::ConfigEntry.new(
124
+ key: "rbs.project_signature_paths",
125
+ value_hash: Digest::SHA256.hexdigest(own.join("\n"))
126
+ )
127
+ end
128
+
129
+ private_class_method :project_signature_paths_entry
130
+
83
131
  def self.deferred_signature_paths_entry(loader)
84
132
  return nil unless loader.respond_to?(:deferred_signature_paths)
85
133
 
@@ -104,7 +152,9 @@ module Rigor
104
152
  # value must be deterministic; `:stat` (ADR-87 WD1) for the validation-only run-dependency descriptor
105
153
  # ({RunDescriptor#files}), where the stat tier short-circuits the SHA-256 on an unmoved file.
106
154
  def self.file_entries(loader, comparator: :digest)
107
- roots = loader.signature_paths +
155
+ # Issue #2517 — a selected core is project input like `signature_paths:`, not the engine-versioned `rbs`
156
+ # gem tree, so an edit in place must move the key.
157
+ roots = core_root_dirs(loader) + loader.signature_paths +
108
158
  Rigor::Environment::RbsLoader.vendored_gem_sig_paths +
109
159
  Rigor::Environment::RbsLoader.core_overlay_sig_paths +
110
160
  Rigor::Environment::RbsLoader.capability_role_sig_paths
@@ -149,7 +199,8 @@ module Rigor
149
199
  def self.glob_entries(loader)
150
200
  # Deduplicated by path: a root reachable twice (an explicit `signature_paths:` entry that is also a
151
201
  # bundled plugin's `sig/`, #610) would otherwise contribute two rows whose stat signatures race.
152
- loader.signature_paths.uniq(&:to_s).filter_map do |root|
202
+ # Issue #2517 — and the selected core's root, where a file appearing is as much a new input.
203
+ (core_root_dirs(loader) + loader.signature_paths).uniq(&:to_s).filter_map do |root|
153
204
  next unless root.directory?
154
205
 
155
206
  Descriptor::GlobEntry.compute(root: root.to_s, pattern: File.join("**", "*.rbs"), mode: :names)
@@ -33,7 +33,10 @@ module Rigor
33
33
  signature_paths: loader.signature_paths,
34
34
  virtual_rbs: loader.respond_to?(:virtual_rbs) ? loader.virtual_rbs : [],
35
35
  deferred_signature_paths:
36
- loader.respond_to?(:deferred_signature_paths) ? loader.deferred_signature_paths : []
36
+ loader.respond_to?(:deferred_signature_paths) ? loader.deferred_signature_paths : [],
37
+ project_signature_paths:
38
+ loader.respond_to?(:project_signature_paths) ? loader.project_signature_paths : [],
39
+ core_root: loader.respond_to?(:core_root) ? loader.core_root : nil
37
40
  )
38
41
  end
39
42
 
@@ -53,8 +53,8 @@ require_relative "annotation_location"
53
53
  # differently, and only the latter reaches `marshal_load`), so without the bump a stale blob would keep
54
54
  # reporting the moved position indefinitely.
55
55
  #
56
- # Idempotent: the guard checks `method_defined?(:_dump)` so requiring this file twice (or against an upstream
57
- # rbs that adds Marshal hooks itself) is a no-op.
56
+ # Idempotent: each hook is defined only when `method_defined?` reports it missing, so requiring this file twice
57
+ # (or against an upstream rbs that adds Marshal hooks itself) is a no-op.
58
58
  #
59
59
  # `RBS::TypeName` / `RBS::Namespace` carry a second, subtler Marshal hazard, introduced by rbs 4.1's
60
60
  # flyweight interning (ruby/rbs#2957): both memoise their `#hash` into an `@hash` ivar, and that value is