rigortype 0.3.8 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (634) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/data/capability_roles/capability_roles.rbs +36 -0
  4. data/data/core_overlay/enumerable.rbs +51 -0
  5. data/data/core_overlay/enumerator.rbs +84 -0
  6. data/data/core_overlay/hash_rbs3.rbs +41 -0
  7. data/data/core_overlay/process.rbs +40 -0
  8. data/data/core_overlay/string_io.rbs +33 -0
  9. data/data/effects/core.yml +3 -3
  10. data/data/gem_overlay/activesupport/core_ext.rbs +236 -17
  11. data/docs/handbook/02-everyday-types.md +17 -14
  12. data/docs/handbook/03-narrowing.md +132 -14
  13. data/docs/handbook/04-tuples-and-shapes.md +8 -6
  14. data/docs/handbook/05-methods-and-blocks.md +1 -1
  15. data/docs/handbook/06-classes.md +2 -2
  16. data/docs/handbook/07-rbs-and-extended.md +19 -1
  17. data/docs/handbook/10-sorbet.md +9 -10
  18. data/docs/handbook/11-sig-gen.md +525 -13
  19. data/docs/handbook/README.md +1 -1
  20. data/docs/handbook/appendix-elixir.md +2 -2
  21. data/docs/handbook/appendix-go.md +2 -2
  22. data/docs/handbook/appendix-java-csharp.md +2 -2
  23. data/docs/handbook/appendix-mypy.md +2 -2
  24. data/docs/handbook/appendix-phpstan.md +1 -1
  25. data/docs/handbook/appendix-rust.md +1 -1
  26. data/docs/handbook/appendix-type-theory.md +4 -4
  27. data/docs/handbook/appendix-typescript.md +1 -1
  28. data/docs/llms.txt +2 -0
  29. data/docs/manual/02-cli-reference.md +95 -6
  30. data/docs/manual/03-configuration.md +18 -0
  31. data/docs/manual/04-diagnostics.md +15 -0
  32. data/docs/manual/07-plugins.md +15 -5
  33. data/docs/manual/08-skills.md +23 -2
  34. data/docs/manual/10-mcp-server.md +3 -2
  35. data/docs/manual/11-ci.md +9 -0
  36. data/docs/manual/15-type-protection-coverage.md +8 -0
  37. data/docs/manual/16-rbs-extended-annotations.md +46 -8
  38. data/docs/manual/18-removing-dead-code.md +10 -8
  39. data/docs/manual/19-effect-labels.md +10 -0
  40. data/docs/manual/plugins/README.md +7 -0
  41. data/docs/manual/plugins/rigor-actioncable.md +8 -1
  42. data/docs/manual/plugins/rigor-actionmailer.md +7 -0
  43. data/docs/manual/plugins/rigor-actionpack.md +261 -0
  44. data/docs/manual/plugins/rigor-active-model-serializers.md +153 -0
  45. data/docs/manual/plugins/rigor-activejob.md +7 -0
  46. data/docs/manual/plugins/rigor-activerecord.md +138 -4
  47. data/docs/manual/plugins/rigor-activestorage.md +8 -1
  48. data/docs/manual/plugins/rigor-activesupport-core-ext.md +43 -7
  49. data/docs/manual/plugins/rigor-grape.md +106 -0
  50. data/docs/manual/plugins/rigor-graphql.md +23 -2
  51. data/docs/manual/plugins/rigor-pundit.md +8 -1
  52. data/docs/manual/plugins/rigor-rails-i18n.md +10 -5
  53. data/docs/manual/plugins/rigor-rails-routes.md +7 -0
  54. data/docs/manual/plugins/rigor-rbs-inline.md +229 -1
  55. data/docs/manual/plugins/rigor-sidekiq.md +8 -1
  56. data/docs/manual/plugins/rigor-sorbet.md +21 -5
  57. data/exe/rigor +19 -4
  58. data/lib/rigor/analysis/baseline.rb +3 -3
  59. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +1 -1
  60. data/lib/rigor/analysis/check_rules/dead_version_guard_arms.rb +1 -5
  61. data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +167 -2
  62. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +10 -2
  63. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +189 -0
  64. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +6 -4
  65. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +15 -6
  66. data/lib/rigor/analysis/check_rules/rule_ids.rb +21 -6
  67. data/lib/rigor/analysis/check_rules/rule_walk.rb +41 -5
  68. data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +1 -1
  69. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +1 -1
  70. data/lib/rigor/analysis/check_rules/source_arity.rb +323 -0
  71. data/lib/rigor/analysis/check_rules/special_global_setters.rb +146 -0
  72. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +38 -4
  73. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +0 -1
  74. data/lib/rigor/analysis/check_rules.rb +718 -79
  75. data/lib/rigor/analysis/crash_signature.rb +8 -24
  76. data/lib/rigor/analysis/dependency_recorder.rb +23 -0
  77. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +1 -1
  78. data/lib/rigor/analysis/dependency_source_inference/builder.rb +0 -2
  79. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +0 -2
  80. data/lib/rigor/analysis/dependency_source_inference/index.rb +5 -5
  81. data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +1 -2
  82. data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
  83. data/lib/rigor/analysis/effects_cache_probe.rb +3 -4
  84. data/lib/rigor/analysis/erb_template_detector.rb +1 -2
  85. data/lib/rigor/analysis/fact_store.rb +15 -1
  86. data/lib/rigor/analysis/incremental.rb +42 -0
  87. data/lib/rigor/analysis/incremental_session.rb +117 -26
  88. data/lib/rigor/analysis/plugin_fact_fingerprint.rb +0 -1
  89. data/lib/rigor/analysis/project_scan.rb +11 -1
  90. data/lib/rigor/analysis/reachability/graph.rb +3 -5
  91. data/lib/rigor/analysis/reachability/plugin_roots.rb +5 -5
  92. data/lib/rigor/analysis/reachability/project_files.rb +2 -2
  93. data/lib/rigor/analysis/reachability/scan.rb +5 -5
  94. data/lib/rigor/analysis/reachability/scan_cache.rb +3 -2
  95. data/lib/rigor/analysis/reachability/signature_scan.rb +2 -2
  96. data/lib/rigor/analysis/result.rb +1 -3
  97. data/lib/rigor/analysis/rule_catalog.rb +131 -1
  98. data/lib/rigor/analysis/run_cache_key.rb +21 -12
  99. data/lib/rigor/analysis/run_cache_probe.rb +3 -5
  100. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -8
  101. data/lib/rigor/analysis/runner/declaration_position.rb +1 -2
  102. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +340 -33
  103. data/lib/rigor/analysis/runner/effect_annotation_residual_pass.rb +2 -3
  104. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +18 -14
  105. data/lib/rigor/analysis/runner/pool_coordinator.rb +357 -97
  106. data/lib/rigor/analysis/runner/project_pre_passes.rb +38 -13
  107. data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
  108. data/lib/rigor/analysis/runner.rb +407 -51
  109. data/lib/rigor/analysis/template_unit_collector.rb +303 -0
  110. data/lib/rigor/analysis/template_unit_paths.rb +91 -0
  111. data/lib/rigor/analysis/template_unit_positions.rb +293 -0
  112. data/lib/rigor/analysis/template_units.rb +399 -0
  113. data/lib/rigor/analysis/worker_session.rb +62 -20
  114. data/lib/rigor/bleeding_edge.rb +10 -24
  115. data/lib/rigor/builtins/hkt_builtins.rb +3 -2
  116. data/lib/rigor/builtins/imported_refinements.rb +159 -21
  117. data/lib/rigor/builtins/predefined_constant_refinements.rb +5 -7
  118. data/lib/rigor/builtins/regex_refinement.rb +21 -13
  119. data/lib/rigor/builtins/static_return_refinements.rb +7 -6
  120. data/lib/rigor/cache/annotation_location.rb +2 -4
  121. data/lib/rigor/cache/descriptor.rb +63 -20
  122. data/lib/rigor/cache/engine_source.rb +33 -5
  123. data/lib/rigor/cache/file_digest.rb +9 -1
  124. data/lib/rigor/cache/incremental_snapshot.rb +72 -6
  125. data/lib/rigor/cache/rbs_cache_producer.rb +4 -0
  126. data/lib/rigor/cache/rbs_class_ancestor_table.rb +0 -3
  127. data/lib/rigor/cache/rbs_class_type_param_names.rb +0 -3
  128. data/lib/rigor/cache/rbs_constant_table.rb +0 -3
  129. data/lib/rigor/cache/rbs_descriptor.rb +112 -14
  130. data/lib/rigor/cache/rbs_environment.rb +8 -4
  131. data/lib/rigor/cache/rbs_known_class_names.rb +0 -3
  132. data/lib/rigor/cache/store.rb +76 -32
  133. data/lib/rigor/ci_detector.rb +1 -0
  134. data/lib/rigor/cli/annotate_command.rb +5 -6
  135. data/lib/rigor/cli/check_command.rb +57 -10
  136. data/lib/rigor/cli/check_invocation.rb +6 -11
  137. data/lib/rigor/cli/check_runner_factory.rb +1 -5
  138. data/lib/rigor/cli/coverage_command.rb +1 -1
  139. data/lib/rigor/cli/coverage_mutation.rb +12 -3
  140. data/lib/rigor/cli/coverage_scan.rb +1 -5
  141. data/lib/rigor/cli/diff_command.rb +1 -1
  142. data/lib/rigor/cli/doc_links.rb +4 -4
  143. data/lib/rigor/cli/docs_command.rb +6 -6
  144. data/lib/rigor/cli/doctor_command.rb +85 -38
  145. data/lib/rigor/cli/effects_command.rb +2 -2
  146. data/lib/rigor/cli/effects_diff_renderer.rb +3 -3
  147. data/lib/rigor/cli/effects_snapshot_command.rb +1 -1
  148. data/lib/rigor/cli/explain_command.rb +34 -1
  149. data/lib/rigor/cli/fused_protection_report.rb +7 -1
  150. data/lib/rigor/cli/lsp_command.rb +1 -1
  151. data/lib/rigor/cli/mcp_command.rb +1 -1
  152. data/lib/rigor/cli/measurement_integrity_warning.rb +4 -5
  153. data/lib/rigor/cli/mutation_fork_scan.rb +6 -6
  154. data/lib/rigor/cli/mutation_protection_report.rb +7 -1
  155. data/lib/rigor/cli/plugin_command.rb +4 -4
  156. data/lib/rigor/cli/plugins_command.rb +2 -1
  157. data/lib/rigor/cli/prism_colorizer.rb +1 -2
  158. data/lib/rigor/cli/protection_fork_scan.rb +6 -6
  159. data/lib/rigor/cli/show_bleedingedge_command.rb +1 -1
  160. data/lib/rigor/cli/sig_gen_command.rb +212 -31
  161. data/lib/rigor/cli/skill_command.rb +2 -2
  162. data/lib/rigor/cli/skill_deep_probe.rb +4 -4
  163. data/lib/rigor/cli/skill_describe.rb +35 -35
  164. data/lib/rigor/cli/trace_command.rb +4 -4
  165. data/lib/rigor/cli/trace_renderer.rb +5 -6
  166. data/lib/rigor/cli/triage_command.rb +1 -1
  167. data/lib/rigor/cli/type_of_command.rb +29 -11
  168. data/lib/rigor/cli/type_of_renderer.rb +20 -9
  169. data/lib/rigor/cli/type_of_template_probe.rb +189 -0
  170. data/lib/rigor/cli/type_scan_command.rb +4 -4
  171. data/lib/rigor/cli/unused_command.rb +11 -3
  172. data/lib/rigor/cli/upgrade_command.rb +1 -1
  173. data/lib/rigor/cli.rb +89 -9
  174. data/lib/rigor/config_audit.rb +30 -5
  175. data/lib/rigor/configuration/severity_profile.rb +25 -9
  176. data/lib/rigor/configuration.rb +109 -7
  177. data/lib/rigor/effects/ancestry_recorder.rb +191 -0
  178. data/lib/rigor/effects/attribution.rb +12 -5
  179. data/lib/rigor/effects/callee_rule.rb +368 -0
  180. data/lib/rigor/effects/catalog.rb +7 -4
  181. data/lib/rigor/effects/collector.rb +11 -5
  182. data/lib/rigor/effects/config_envelopes.rb +15 -10
  183. data/lib/rigor/effects/definition_context.rb +179 -0
  184. data/lib/rigor/effects/definition_lines.rb +25 -6
  185. data/lib/rigor/effects/effect_table.rb +11 -4
  186. data/lib/rigor/effects/entry_points.rb +0 -2
  187. data/lib/rigor/effects/envelope_check.rb +9 -9
  188. data/lib/rigor/effects/envelope_index.rb +20 -8
  189. data/lib/rigor/effects/file_collection.rb +59 -5
  190. data/lib/rigor/effects/framework_units.rb +5 -7
  191. data/lib/rigor/effects/identity.rb +20 -6
  192. data/lib/rigor/effects/inline_anchor.rb +7 -7
  193. data/lib/rigor/effects/label_intent.rb +3 -4
  194. data/lib/rigor/effects/liskov_check.rb +8 -8
  195. data/lib/rigor/effects/local_ownership.rb +38 -12
  196. data/lib/rigor/effects/method_key.rb +22 -1
  197. data/lib/rigor/effects/mutation_classifier.rb +23 -12
  198. data/lib/rigor/effects/plugin_facts.rb +47 -37
  199. data/lib/rigor/effects/propagator.rb +297 -19
  200. data/lib/rigor/effects/registry.rb +1 -1
  201. data/lib/rigor/effects/scanner.rb +122 -73
  202. data/lib/rigor/effects/signature_sources.rb +4 -6
  203. data/lib/rigor/effects/snapshot.rb +12 -11
  204. data/lib/rigor/effects/snapshot_diff.rb +1 -1
  205. data/lib/rigor/effects/summary.rb +27 -4
  206. data/lib/rigor/effects/unit_scan.rb +393 -38
  207. data/lib/rigor/effects/unknown_label_check.rb +3 -8
  208. data/lib/rigor/effects/unknown_label_report.rb +3 -4
  209. data/lib/rigor/effects/visibility.rb +101 -0
  210. data/lib/rigor/environment/bundle_sig_discovery.rb +6 -6
  211. data/lib/rigor/environment/class_registry.rb +3 -1
  212. data/lib/rigor/environment/failure_slot.rb +2 -2
  213. data/lib/rigor/environment/installed_gem_set.rb +85 -0
  214. data/lib/rigor/environment/lockfile_resolver.rb +68 -4
  215. data/lib/rigor/environment/member_consistency/comparator.rb +708 -0
  216. data/lib/rigor/environment/member_consistency.rb +298 -0
  217. data/lib/rigor/environment/missing_gem_constant_index.rb +4 -4
  218. data/lib/rigor/environment/rbs_collection_discovery.rb +5 -5
  219. data/lib/rigor/environment/rbs_coverage_report.rb +5 -5
  220. data/lib/rigor/environment/rbs_hierarchy.rb +3 -1
  221. data/lib/rigor/environment/rbs_loader.rb +674 -79
  222. data/lib/rigor/environment.rb +174 -24
  223. data/lib/rigor/flow_contribution/merger.rb +0 -2
  224. data/lib/rigor/flow_contribution.rb +11 -13
  225. data/lib/rigor/hashing/xxh3.rb +264 -0
  226. data/lib/rigor/inference/acceptance.rb +292 -17
  227. data/lib/rigor/inference/block_auto_splat.rb +216 -0
  228. data/lib/rigor/inference/block_call_timing.rb +338 -0
  229. data/lib/rigor/inference/block_parameter_binder.rb +75 -30
  230. data/lib/rigor/inference/block_repetition.rb +71 -0
  231. data/lib/rigor/inference/body_fixpoint.rb +7 -6
  232. data/lib/rigor/inference/budget_trace.rb +3 -6
  233. data/lib/rigor/inference/builtins/method_catalog.rb +2 -1
  234. data/lib/rigor/inference/builtins/string_catalog.rb +1 -1
  235. data/lib/rigor/inference/captured_locals.rb +389 -18
  236. data/lib/rigor/inference/closure_escape_analyzer.rb +159 -17
  237. data/lib/rigor/inference/content_join.rb +200 -27
  238. data/lib/rigor/inference/coverage_scanner.rb +4 -6
  239. data/lib/rigor/inference/def_return_typer.rb +11 -7
  240. data/lib/rigor/inference/define_method_block_self.rb +64 -0
  241. data/lib/rigor/inference/dynamic_origin.rb +1 -1
  242. data/lib/rigor/inference/element_read_widening.rb +180 -0
  243. data/lib/rigor/inference/error_info.rb +196 -0
  244. data/lib/rigor/inference/expression_typer.rb +2124 -468
  245. data/lib/rigor/inference/external_ancestor_resolution.rb +267 -0
  246. data/lib/rigor/inference/fork_map.rb +5 -7
  247. data/lib/rigor/inference/fresh_frame_blocks.rb +127 -0
  248. data/lib/rigor/inference/global_write_census.rb +239 -0
  249. data/lib/rigor/inference/guard_rebinding.rb +447 -0
  250. data/lib/rigor/inference/hash_lookup_mutation.rb +88 -0
  251. data/lib/rigor/inference/hkt_reducer.rb +2 -3
  252. data/lib/rigor/inference/hkt_registry.rb +4 -7
  253. data/lib/rigor/inference/index_write_widening.rb +16 -7
  254. data/lib/rigor/inference/indexed_narrowing.rb +61 -9
  255. data/lib/rigor/inference/jump_targets.rb +82 -0
  256. data/lib/rigor/inference/last_line/implicit_self.rb +210 -0
  257. data/lib/rigor/inference/last_line/self_evidence.rb +329 -0
  258. data/lib/rigor/inference/last_line.rb +340 -0
  259. data/lib/rigor/inference/last_status.rb +144 -0
  260. data/lib/rigor/inference/macro_block_self_type.rb +168 -21
  261. data/lib/rigor/inference/match_rebinding/calls.rb +281 -0
  262. data/lib/rigor/inference/match_rebinding/frame.rb +148 -0
  263. data/lib/rigor/inference/match_rebinding/operands.rb +236 -0
  264. data/lib/rigor/inference/match_rebinding/self_calls.rb +83 -0
  265. data/lib/rigor/inference/match_rebinding.rb +392 -0
  266. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
  267. data/lib/rigor/inference/method_dispatcher/block_folding.rb +87 -33
  268. data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +1 -1
  269. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +305 -19
  270. data/lib/rigor/inference/method_dispatcher/data_folding.rb +1 -1
  271. data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +147 -0
  272. data/lib/rigor/inference/method_dispatcher/file_folding.rb +1 -1
  273. data/lib/rigor/inference/method_dispatcher/hash_transform_keys_folding.rb +191 -0
  274. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +6 -2
  275. data/lib/rigor/inference/method_dispatcher/json_folding.rb +1 -1
  276. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -1
  277. data/lib/rigor/inference/method_dispatcher/match_data_folding.rb +159 -0
  278. data/lib/rigor/inference/method_dispatcher/math_folding.rb +41 -3
  279. data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -5
  280. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +165 -111
  281. data/lib/rigor/inference/method_dispatcher/process_folding.rb +56 -0
  282. data/lib/rigor/inference/method_dispatcher/proven_overload.rb +72 -0
  283. data/lib/rigor/inference/method_dispatcher/random_folding.rb +82 -0
  284. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +1046 -69
  285. data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +25 -4
  286. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +2 -8
  287. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +1 -1
  288. data/lib/rigor/inference/method_dispatcher/self_substitute.rb +142 -0
  289. data/lib/rigor/inference/method_dispatcher/set_folding.rb +1 -1
  290. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +129 -54
  291. data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +1 -1
  292. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +1 -2
  293. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -6
  294. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +85 -4
  295. data/lib/rigor/inference/method_dispatcher/time_folding.rb +1 -1
  296. data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +1 -2
  297. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  298. data/lib/rigor/inference/method_dispatcher.rb +225 -25
  299. data/lib/rigor/inference/method_parameter_binder.rb +12 -9
  300. data/lib/rigor/inference/multi_target_binder.rb +341 -55
  301. data/lib/rigor/inference/mutation_rejoin.rb +171 -0
  302. data/lib/rigor/inference/mutation_widening.rb +156 -85
  303. data/lib/rigor/inference/narrowing.rb +835 -162
  304. data/lib/rigor/inference/operand_effects.rb +167 -0
  305. data/lib/rigor/inference/operand_walk.rb +88 -0
  306. data/lib/rigor/inference/optimistic_origin.rb +153 -16
  307. data/lib/rigor/inference/origin_lookup.rb +2 -3
  308. data/lib/rigor/inference/parameter_inference_collector.rb +7 -7
  309. data/lib/rigor/inference/pre_eval_constants.rb +6 -5
  310. data/lib/rigor/inference/precision_scanner.rb +3 -4
  311. data/lib/rigor/inference/project_method_ownership.rb +136 -0
  312. data/lib/rigor/inference/project_patched_methods.rb +9 -4
  313. data/lib/rigor/inference/project_patched_scanner.rb +10 -6
  314. data/lib/rigor/inference/protection_scanner.rb +1 -2
  315. data/lib/rigor/inference/range_constant.rb +57 -0
  316. data/lib/rigor/inference/rbs_type_translator.rb +49 -11
  317. data/lib/rigor/inference/receiver_alias.rb +93 -4
  318. data/lib/rigor/inference/receiver_blind_block.rb +219 -0
  319. data/lib/rigor/inference/refinement_mutation.rb +76 -0
  320. data/lib/rigor/inference/repeated_or_writes.rb +463 -0
  321. data/lib/rigor/inference/return_barrier.rb +54 -0
  322. data/lib/rigor/inference/rewrite_mutation.rb +120 -0
  323. data/lib/rigor/inference/scope_indexer.rb +5228 -627
  324. data/lib/rigor/inference/statement_evaluator.rb +3452 -595
  325. data/lib/rigor/inference/stored_block_call.rb +54 -0
  326. data/lib/rigor/inference/string_mutation.rb +97 -0
  327. data/lib/rigor/inference/struct_fold_safety.rb +6 -7
  328. data/lib/rigor/inference/synthetic_method_index.rb +1 -2
  329. data/lib/rigor/inference/synthetic_method_scanner.rb +4 -6
  330. data/lib/rigor/inference/unknown_store_widening.rb +200 -0
  331. data/lib/rigor/inference/unthreaded_rebinds.rb +282 -0
  332. data/lib/rigor/inference/version_guard.rb +28 -21
  333. data/lib/rigor/inference/void_origin.rb +3 -3
  334. data/lib/rigor/inference/void_tail_summary.rb +2 -4
  335. data/lib/rigor/language_server/buffer_table.rb +4 -4
  336. data/lib/rigor/language_server/completion_provider.rb +1 -1
  337. data/lib/rigor/language_server/debouncer.rb +0 -1
  338. data/lib/rigor/language_server/diagnostic_publisher.rb +7 -6
  339. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  340. data/lib/rigor/language_server/folding_range_provider.rb +1 -1
  341. data/lib/rigor/language_server/hover_provider.rb +1 -1
  342. data/lib/rigor/language_server/hover_renderer.rb +4 -4
  343. data/lib/rigor/language_server/incremental_sync.rb +6 -6
  344. data/lib/rigor/language_server/project_context.rb +7 -8
  345. data/lib/rigor/language_server/publish_batcher.rb +2 -2
  346. data/lib/rigor/language_server/selection_range_provider.rb +2 -2
  347. data/lib/rigor/language_server/server.rb +9 -9
  348. data/lib/rigor/language_server/signature_help_provider.rb +1 -1
  349. data/lib/rigor/language_server/uri.rb +1 -1
  350. data/lib/rigor/mcp/server.rb +2 -1
  351. data/lib/rigor/plugin/base.rb +258 -26
  352. data/lib/rigor/plugin/box_probe.rb +91 -0
  353. data/lib/rigor/plugin/bundled_catalog.rb +167 -0
  354. data/lib/rigor/plugin/effect_attribution.rb +65 -11
  355. data/lib/rigor/plugin/effect_entry_points.rb +3 -3
  356. data/lib/rigor/plugin/fact_store.rb +6 -6
  357. data/lib/rigor/plugin/io_boundary.rb +93 -8
  358. data/lib/rigor/plugin/isolation.rb +29 -6
  359. data/lib/rigor/plugin/loader.rb +33 -13
  360. data/lib/rigor/plugin/macro/block_as_method.rb +45 -6
  361. data/lib/rigor/plugin/macro/heredoc_template.rb +1 -1
  362. data/lib/rigor/plugin/macro/trait_registry.rb +1 -1
  363. data/lib/rigor/plugin/manifest.rb +94 -11
  364. data/lib/rigor/plugin/registry.rb +76 -12
  365. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +21 -4
  366. data/lib/rigor/plugin/template_unit.rb +196 -0
  367. data/lib/rigor/plugin/trust_policy.rb +2 -4
  368. data/lib/rigor/plugin/type_node_resolver.rb +3 -3
  369. data/lib/rigor/plugin.rb +1 -0
  370. data/lib/rigor/plugin_gap_advisory.rb +96 -0
  371. data/lib/rigor/project_environment.rb +138 -0
  372. data/lib/rigor/protection/analysis_guard.rb +93 -14
  373. data/lib/rigor/protection/closure_kill_oracle.rb +67 -23
  374. data/lib/rigor/protection/dependency_closure.rb +5 -8
  375. data/lib/rigor/protection/diagnostic_oracle.rb +2 -2
  376. data/lib/rigor/protection/discovery_seed.rb +14 -12
  377. data/lib/rigor/protection/kill_signature.rb +1 -4
  378. data/lib/rigor/protection/measurement_integrity.rb +1 -3
  379. data/lib/rigor/protection/mutation_cache.rb +7 -11
  380. data/lib/rigor/protection/mutation_scanner.rb +12 -15
  381. data/lib/rigor/protection/mutator.rb +2 -1
  382. data/lib/rigor/protection/test_suite_oracle.rb +5 -5
  383. data/lib/rigor/rbs_extended/conformance_checker.rb +4 -3
  384. data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
  385. data/lib/rigor/rbs_extended/reporter.rb +36 -10
  386. data/lib/rigor/rbs_extended.rb +88 -21
  387. data/lib/rigor/reflection/constant_ancestors.rb +97 -0
  388. data/lib/rigor/reflection/constant_path.rb +139 -0
  389. data/lib/rigor/reflection.rb +93 -110
  390. data/lib/rigor/runtime/jit.rb +6 -8
  391. data/lib/rigor/scope/discovery_index.rb +165 -2
  392. data/lib/rigor/scope.rb +1041 -32
  393. data/lib/rigor/sig_gen/alias_index.rb +289 -0
  394. data/lib/rigor/sig_gen/classification.rb +22 -5
  395. data/lib/rigor/sig_gen/declaration_equivalence.rb +127 -0
  396. data/lib/rigor/sig_gen/effect_annotation.rb +202 -0
  397. data/lib/rigor/sig_gen/generator.rb +673 -53
  398. data/lib/rigor/sig_gen/inline_declarations.rb +184 -0
  399. data/lib/rigor/sig_gen/layout_index.rb +3 -4
  400. data/lib/rigor/sig_gen/meta_class_shape.rb +3 -5
  401. data/lib/rigor/sig_gen/method_candidate.rb +47 -3
  402. data/lib/rigor/sig_gen/observation_collector.rb +18 -16
  403. data/lib/rigor/sig_gen/path_mapper.rb +4 -6
  404. data/lib/rigor/sig_gen/rbs_validity.rb +4 -4
  405. data/lib/rigor/sig_gen/renderer.rb +128 -14
  406. data/lib/rigor/sig_gen/skip_reason_catalog.rb +154 -0
  407. data/lib/rigor/sig_gen/superclass_spelling.rb +27 -0
  408. data/lib/rigor/sig_gen/type_elaborator.rb +1 -3
  409. data/lib/rigor/sig_gen/write_result.rb +19 -3
  410. data/lib/rigor/sig_gen/writer.rb +189 -41
  411. data/lib/rigor/sig_gen.rb +3 -0
  412. data/lib/rigor/signature_path_audit.rb +153 -6
  413. data/lib/rigor/source/literals.rb +0 -23
  414. data/lib/rigor/source/node_children.rb +0 -2
  415. data/lib/rigor/source/node_locator.rb +5 -11
  416. data/lib/rigor/source/node_walker.rb +0 -6
  417. data/lib/rigor/source/parameter_envelope.rb +72 -0
  418. data/lib/rigor/source.rb +1 -0
  419. data/lib/rigor/triage/catalogue.rb +1 -3
  420. data/lib/rigor/triage.rb +3 -5
  421. data/lib/rigor/type/accepts_result.rb +24 -6
  422. data/lib/rigor/type/combinator.rb +144 -5
  423. data/lib/rigor/type/data_class.rb +2 -2
  424. data/lib/rigor/type/data_instance.rb +4 -4
  425. data/lib/rigor/type/difference.rb +1 -0
  426. data/lib/rigor/type/float_range.rb +128 -0
  427. data/lib/rigor/type/hash_shape.rb +6 -6
  428. data/lib/rigor/type/integer_range.rb +13 -8
  429. data/lib/rigor/type/nominal.rb +8 -2
  430. data/lib/rigor/type/refined.rb +4 -3
  431. data/lib/rigor/type/struct_class.rb +3 -3
  432. data/lib/rigor/type/struct_instance.rb +4 -4
  433. data/lib/rigor/type.rb +1 -0
  434. data/lib/rigor/type_node/generic.rb +1 -1
  435. data/lib/rigor/type_node/range_literal.rb +25 -0
  436. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  437. data/lib/rigor/type_node.rb +1 -0
  438. data/lib/rigor/version.rb +1 -1
  439. data/lib/rigor.rb +1 -0
  440. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +0 -4
  441. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +0 -1
  442. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +0 -2
  443. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/effects.rb +1 -0
  444. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +10 -5
  445. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +0 -4
  446. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +3 -5
  447. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +1 -4
  448. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +14 -4
  449. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +32 -30
  450. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +0 -1
  451. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -3
  452. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +96 -0
  453. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/effects.rb +65 -8
  454. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +270 -0
  455. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/render_locals.rb +402 -0
  456. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +370 -0
  457. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_units.rb +132 -0
  458. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +264 -3
  459. data/plugins/rigor-actionpack/sig/action_controller.rbs +71 -0
  460. data/plugins/rigor-actionpack/sig/action_view.rbs +43 -0
  461. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +220 -0
  462. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_index.rb +56 -0
  463. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +263 -0
  464. data/plugins/rigor-active-model-serializers/lib/rigor-active-model-serializers.rb +7 -0
  465. data/plugins/rigor-active-model-serializers/sig/active_model_serializers.rbs +45 -0
  466. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +0 -4
  467. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +2 -1
  468. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +0 -1
  469. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +0 -2
  470. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +1 -1
  471. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +10 -5
  472. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +35 -3
  473. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +72 -12
  474. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +383 -7
  475. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +14 -6
  476. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +1 -2
  477. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +1 -2
  478. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +322 -41
  479. data/plugins/rigor-activerecord/sig/active_record/framework.rbs +123 -0
  480. data/plugins/rigor-activerecord/sig/active_record/relation.rbs +206 -31
  481. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +26 -14
  482. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +9 -2
  483. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +9 -2
  484. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +290 -19
  485. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +1 -0
  486. data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +3 -0
  487. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +9 -7
  488. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +7 -3
  489. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +1 -0
  490. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +8 -6
  491. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +11 -7
  492. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -11
  493. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +9 -4
  494. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +2 -0
  495. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +0 -5
  496. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +0 -1
  497. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +0 -1
  498. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +8 -1
  499. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/types.rb +1 -1
  500. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +36 -4
  501. data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +1 -0
  502. data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +141 -0
  503. data/plugins/rigor-grape/lib/rigor-grape.rb +6 -0
  504. data/plugins/rigor-grape/sig/grape.rbs +263 -0
  505. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +8 -6
  506. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +78 -5
  507. data/plugins/rigor-graphql/sig/graphql.rbs +485 -0
  508. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +1 -0
  509. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -1
  510. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +2 -3
  511. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +1 -0
  512. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +0 -5
  513. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -2
  514. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +0 -1
  515. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +0 -1
  516. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +12 -7
  517. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +0 -12
  518. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +1 -3
  519. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +0 -1
  520. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +36 -37
  521. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/acronyms.rb +5 -5
  522. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +0 -10
  523. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +5 -3
  524. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +3 -2
  525. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +2 -2
  526. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +2 -2
  527. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +9 -10
  528. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +2 -3
  529. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +10 -17
  530. data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +1 -0
  531. data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +13 -1
  532. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline/same_line_annotations.rb +149 -0
  533. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +324 -18
  534. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +0 -3
  535. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +0 -3
  536. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -5
  537. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +2 -3
  538. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +1 -0
  539. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +0 -3
  540. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +1 -0
  541. data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +1 -0
  542. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +1 -9
  543. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +1 -0
  544. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +0 -4
  545. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/effects.rb +1 -1
  546. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +2 -1
  547. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +0 -1
  548. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +0 -2
  549. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +10 -5
  550. data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -0
  551. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +2 -5
  552. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +1 -4
  553. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +3 -9
  554. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +4 -4
  555. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +1 -2
  556. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +3 -5
  557. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +7 -5
  558. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +51 -6
  559. data/plugins/rigor-sorbet/sig/sorbet.rbs +277 -0
  560. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +1 -0
  561. data/sig/rigor/analysis/baseline.rbs +69 -7
  562. data/sig/rigor/analysis/fact_store.rbs +1 -1
  563. data/sig/rigor/analysis/project_scan.rbs +74 -0
  564. data/sig/rigor/analysis/reachability/scan_cache.rbs +12 -0
  565. data/sig/rigor/cache.rbs +2 -3
  566. data/sig/rigor/effects/config_envelopes.rbs +100 -0
  567. data/sig/rigor/effects/effect_table.rbs +60 -0
  568. data/sig/rigor/effects/envelope.rbs +97 -0
  569. data/sig/rigor/effects/envelope_index.rbs +33 -0
  570. data/sig/rigor/effects/file_collection.rbs +81 -0
  571. data/sig/rigor/effects/label.rbs +26 -0
  572. data/sig/rigor/effects/label_set.rbs +46 -0
  573. data/sig/rigor/effects/method_key.rbs +24 -0
  574. data/sig/rigor/effects/origin.rbs +50 -0
  575. data/sig/rigor/effects/plugin_facts.rbs +141 -0
  576. data/sig/rigor/effects/registry.rbs +68 -0
  577. data/sig/rigor/effects/summary.rbs +50 -0
  578. data/sig/rigor/effects/taint_cause.rbs +12 -0
  579. data/sig/rigor/environment.rbs +17 -10
  580. data/sig/rigor/inference/optimistic_origin.rbs +10 -0
  581. data/sig/rigor/inference.rbs +9 -14
  582. data/sig/rigor/plugin/additional_initializer.rbs +36 -0
  583. data/sig/rigor/plugin/base.rbs +30 -7
  584. data/sig/rigor/plugin/effect_ancestry.rbs +27 -0
  585. data/sig/rigor/plugin/effect_attribution.rbs +66 -0
  586. data/sig/rigor/plugin/effect_edge.rbs +33 -0
  587. data/sig/rigor/plugin/effect_entry_points.rbs +25 -0
  588. data/sig/rigor/plugin/io_boundary.rbs +4 -0
  589. data/sig/rigor/plugin/loader.rbs +3 -3
  590. data/sig/rigor/plugin/manifest.rbs +51 -11
  591. data/sig/rigor/plugin/protocol_contract.rbs +68 -0
  592. data/sig/rigor/plugin/registry.rbs +62 -1
  593. data/sig/rigor/plugin.rbs +1 -1
  594. data/sig/rigor/rbs_extended.rbs +1 -1
  595. data/sig/rigor/reflection.rbs +9 -6
  596. data/sig/rigor/scope.rbs +148 -12
  597. data/sig/rigor/sig_gen/skip_reason_catalog.rbs +21 -0
  598. data/sig/rigor/source.rbs +4 -4
  599. data/sig/rigor/testing.rbs +10 -4
  600. data/sig/rigor/type.rbs +33 -1
  601. data/sig/rigor.rbs +39 -20
  602. data/skills/rigor-ask/SKILL.md +16 -11
  603. data/skills/rigor-baseline-reduce/SKILL.md +4 -6
  604. data/skills/rigor-ci-setup/SKILL.md +17 -21
  605. data/skills/rigor-doctor/SKILL.md +24 -22
  606. data/skills/rigor-doctor/references/01-checks.md +97 -33
  607. data/skills/rigor-editor-setup/SKILL.md +6 -4
  608. data/skills/rigor-mcp-setup/SKILL.md +5 -4
  609. data/skills/rigor-monkeypatch-resolve/SKILL.md +5 -3
  610. data/skills/rigor-next-steps/SKILL.md +4 -2
  611. data/skills/rigor-plugin-author/SKILL.md +19 -23
  612. data/skills/rigor-plugin-author/references/01-plan-and-scaffold.md +9 -8
  613. data/skills/rigor-plugin-author/references/02-walker-and-types.md +25 -27
  614. data/skills/rigor-plugin-author/references/03-test-and-ship.md +6 -8
  615. data/skills/rigor-plugin-review/SKILL.md +6 -4
  616. data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +2 -1
  617. data/skills/rigor-plugin-tune/SKILL.md +4 -2
  618. data/skills/rigor-project-init/SKILL.md +15 -10
  619. data/skills/rigor-project-init/references/01-detect.md +13 -9
  620. data/skills/rigor-project-init/references/02-configure.md +43 -14
  621. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +14 -14
  622. data/skills/rigor-project-init/references/04-sig-uplift.md +27 -14
  623. data/skills/rigor-project-init/references/06-agent-contract.md +67 -0
  624. data/skills/rigor-protection-uplift/SKILL.md +4 -6
  625. data/skills/rigor-rbs-setup/SKILL.md +4 -2
  626. data/skills/rigor-type-oracle/SKILL.md +226 -0
  627. data/skills/rigor-type-oracle/references/01-oracle-commands.md +265 -0
  628. data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +52 -0
  629. data/skills/rigor-type-oracle/references/03-gap-protocol.md +125 -0
  630. data/skills/rigor-unused-adjudicate/SKILL.md +12 -7
  631. data/skills/rigor-upgrade/SKILL.md +13 -8
  632. metadata +133 -3
  633. data/lib/rigor/cli/probe_environment.rb +0 -85
  634. data/sig/rigor/inference/builtins/numeric_catalog.rbs +0 -3
@@ -9,6 +9,7 @@ require_relative "../scope"
9
9
  require_relative "../cache/store"
10
10
  require_relative "../cache/rbs_descriptor"
11
11
  require_relative "../cache/file_digest"
12
+ require_relative "../cache/incremental_snapshot"
12
13
  require_relative "run_cache_key"
13
14
  require_relative "path_expansion"
14
15
  require_relative "../plugin"
@@ -47,6 +48,7 @@ require_relative "runner/run_snapshots"
47
48
  require_relative "runner/project_pre_passes"
48
49
  require_relative "runner/pool_coordinator"
49
50
  require_relative "runner/diagnostic_aggregator"
51
+ require_relative "template_units"
50
52
  require_relative "runner/effect_envelope_pass"
51
53
  require_relative "runner/effect_annotation_residual_pass"
52
54
  require_relative "runner/buffer_pool_dispatcher"
@@ -74,6 +76,18 @@ module Rigor
74
76
  # file-positioned ones (`effect.annotations-unchecked`, `source-rbs-*`).
75
77
  :per_file_diagnostics
76
78
 
79
+ # Issues #796 / #794 — the two run-level rows a NARROWED run cannot re-derive from the files it
80
+ # analyses: the `definition-build-failed` set (whose producer is the analysis's own demand, and #696
81
+ # forbids Rigor demanding in its place) and the HKT-scan outcome (whose producer is an environment a
82
+ # nothing-changed recheck otherwise has no reason to build). {IncrementalSession} persists what a full
83
+ # run answers here and hands it back to the next narrowed run.
84
+ def run_level_rows
85
+ Cache::IncrementalSnapshot::RunLevelRows.new(
86
+ definition_build_failures: @snapshots.definition_build_failures,
87
+ hkt_scan_failure: @snapshots.hkt_scan_failure
88
+ )
89
+ end
90
+
77
91
  # ADR-46 — the per-file cross-file read records this run captured (empty unless
78
92
  # `record_dependencies: true`). Sequential analysis records into `@file_dependencies` via
79
93
  # {#analyze_file}; the fork pool records per-worker and marshals the records into the coordinator, so a
@@ -94,6 +108,32 @@ module Rigor
94
108
  @effect_table || Effects::EffectTable.empty
95
109
  end
96
110
 
111
+ # ADR-103 WD9 (#391) — the envelope index this run resolved, for `rigor sig-gen`'s annotation
112
+ # emission to consult. Public for the same reason {#effect_table} is: a consumer outside the
113
+ # diagnostic stream needs what the run already computed, and rebuilding it from configuration
114
+ # alone would read a narrower set of strata — the accepted signatures and the rbs-inline virtual
115
+ # RBS both come from the built environment.
116
+ #
117
+ # Built on demand when the run never analysed a file. A WARM whole-run cache hit serves the
118
+ # propagated table from cache, so {#analyze_with_effects} — the only caller of
119
+ # {#effect_envelope_index} — never runs and the ivar stays nil. Returning an empty index there
120
+ # would silently drop sig-gen's "the author already declared a bound" gate on exactly the runs a
121
+ # user makes most often: the same `sig-gen` invocation would withhold on its first run and emit on
122
+ # its second. `@run_environment` is resolved before the cache is consulted, so the warm build reads
123
+ # the same strata the cold one does.
124
+ #
125
+ # `@run_environment` is nil only when the run resolved no environment at all — a run over zero
126
+ # project files, or one that never reached {#compute_run_diagnostics}. The build then falls back to
127
+ # the configuration-only strata (`sig/` annotations, class-level ones and `effects.envelopes:`,
128
+ # without the accepted signatures or the rbs-inline virtual RBS), which is the narrower answer but
129
+ # not a wrong one: a run with no environment analysed no file, so there is no candidate for the
130
+ # missing strata to have bounded.
131
+ def effect_envelopes
132
+ return @effect_envelope_index if @effect_envelope_index
133
+
134
+ effect_envelope_index(@run_environment)
135
+ end
136
+
97
137
  # ADR-103 WD2 / WD6 / WD10 / #387 — the loaded plugins' effect contributions, compiled once per
98
138
  # process. Memoised on first use rather than built in the constructor for two reasons: the plugin
99
139
  # registry is adopted after construction (`apply_prebuilt` / the plugin-load pre-pass), and the
@@ -214,41 +254,40 @@ module Rigor
214
254
  end
215
255
  end
216
256
 
217
- # @param configuration [Rigor::Configuration]
218
- # @param explain [Boolean] surface fail-soft fallback events as `:info` diagnostics.
219
- # @param cache_store [Rigor::Cache::Store, nil] the persistent cache the runner exposes to producers
257
+ # @param explain — surface fail-soft fallback events as `:info` diagnostics.
258
+ # @param cache_store — the persistent cache the runner exposes to producers
220
259
  # (`RbsConstantTable` and successors). Pass `nil` to disable caching for this run; the CLI's
221
260
  # `--no-cache` flag wires `nil` through. v0.0.9 group A slice 1 introduces the surface; later
222
261
  # slices route real producers through it.
223
- # @param workers [Integer] ADR-15 Phase 4b — when greater than zero, per-file analysis dispatches
262
+ # @param workers — ADR-15 Phase 4b — when greater than zero, per-file analysis dispatches
224
263
  # across a pool of N workers. Default `0` keeps the sequential code path bit-for-bit unchanged.
225
264
  # Controlled via the `RIGOR_RACTOR_WORKERS` env var or `.rigor.yml` `parallel.workers:` (Phase 4c,
226
265
  # fully wired).
227
- # @param collect_stats [Boolean] when true (default), `#run` builds a {RunStats} summary exposed via
266
+ # @param collect_stats — when true (default), `#run` builds a {RunStats} summary exposed via
228
267
  # `result.stats` — this forces the RBS env build at end-of-run so the `class_decl_paths` snapshot
229
268
  # has real source attribution. Set to false to skip the stats summary entirely; the CLI's
230
269
  # `--no-stats` threads `false` through to keep trivial-fixture runs from warming `.rigor/cache`.
231
- # @param prebuilt [Rigor::Analysis::ProjectScan, nil] when supplied, the runner adopts the pre-built
270
+ # @param prebuilt — when supplied, the runner adopts the pre-built
232
271
  # plugin registry / dependency-source index / scanner outputs from the snapshot and skips the
233
272
  # per-call pre-passes that produce them. Used by long-lived integrations
234
273
  # (`Rigor::LanguageServer::ProjectContext`) to keep per-buffer requests fast — scanners walk the
235
274
  # project once per generation rather than once per request, and plugin `#prepare` runs once per
236
275
  # generation rather than once per request. Watched-file invalidation is the owner's responsibility;
237
276
  # the runner trusts the snapshot it was given.
238
- # @param environment [Rigor::Environment, nil] opt-in Environment override. When supplied, sequential
277
+ # @param environment — opt-in Environment override. When supplied, sequential
239
278
  # mode uses the provided env instance in `#analyze_files` instead of building a fresh one via
240
279
  # `Environment.for_project`, and attaches the runner's per-run reporter pair onto the env's mutable
241
280
  # `Reporters` slot via `Environment#attach_reporters!`. Long-lived consumers (LSP `ProjectContext`)
242
281
  # pass a shared env so per-publish work doesn't repeat the `Environment.for_project` build (bundler
243
282
  # / lockfile / collection discovery, RbsLoader construction). Pool mode ignores the override — each
244
283
  # worker continues to build its own Environment.
245
- # @param discovery_seed [Hash, nil] issue #260 — opt-in cross-file discovery tables, keyed by
284
+ # @param discovery_seed — issue #260 — opt-in cross-file discovery tables, keyed by
246
285
  # {Scope::DiscoveryIndex} slot name, seeded onto every per-file scope through
247
286
  # `project_scope_seed_tables`. The ONE deliberate exception to "a `prebuilt:` runner carries no
248
287
  # discovery tables": {Protection::DiagnosticOracle} threads the table set Tier 2's site filter already
249
288
  # judges anchors against, so a site admitted because a sibling-file class resolved is also a site the
250
289
  # oracle can kill at. nil (the default) leaves the prebuilt/LSP contract byte-identical.
251
- # @param no_tolerated_effects [Boolean] ADR-103 WD1 / #385 — `rigor check --no-tolerated-effects`.
290
+ # @param no_tolerated_effects — ADR-103 WD1 / #385 — `rigor check --no-tolerated-effects`.
252
291
  # Judges effect envelopes as if `effects.tolerated:` were empty. A judgment-time switch only: the
253
292
  # run, its collection and its cache identity are unchanged.
254
293
  def initialize(configuration:, explain: false, # rubocop:disable Metrics/ParameterLists,Metrics/AbcSize,Metrics/MethodLength
@@ -257,8 +296,12 @@ module Rigor
257
296
  buffer: nil, prebuilt: nil, environment: nil,
258
297
  record_dependencies: false, record_self_calls: false, analyze_only: nil,
259
298
  seed_bundles: nil, collect_seed_bundles: false, param_inferred_types: nil,
260
- discovery_seed: nil, no_tolerated_effects: false)
299
+ discovery_seed: nil, no_tolerated_effects: false,
300
+ restored_run_level_rows: nil)
261
301
  @configuration = configuration
302
+ # Issues #796 / #794 — the previous full run's run-level rows, supplied only by
303
+ # {IncrementalSession} on a narrowed run; see {PoolCoordinator#replay_restored_run_level_rows}.
304
+ @restored_run_level_rows = restored_run_level_rows
262
305
  @explain = explain
263
306
  @cache_store = enforce_read_only_cache(cache_store, buffer)
264
307
  @plugin_requirer = plugin_requirer
@@ -298,6 +341,10 @@ module Rigor
298
341
  # check reads.
299
342
  @no_tolerated_effects = no_tolerated_effects
300
343
  @file_effects = {}
344
+ # #392 — the run's template units, synthesised once per run after the plugins load (see
345
+ # {#template_units}). nil until then; a project whose plugins declare no `template_globs:` resolves
346
+ # to the shared empty index and pays one `Array#empty?` for the whole run.
347
+ @template_units = nil
301
348
  @effect_table = nil
302
349
  @effects_served_from_cache = false
303
350
  # #482 — set only by a run served from the summary entry: the two derived tables it carries, and
@@ -362,6 +409,16 @@ module Rigor
362
409
  # Issue #682 — the `Module.nesting` each class / module declaration HEADER is written in.
363
410
  @project_discovered_header_nestings = {}.freeze
364
411
  @project_discovered_includes = {}.freeze
412
+ # Issue #1123 — the instance-side prepend table (`prepend M`, and `Recv.prepend(M)`), in
413
+ # instance-ancestor SEARCH order.
414
+ @project_discovered_prepends = {}.freeze
415
+ # Issue #898 — the singleton-side twin of the include table (`extend M` / `extend self`).
416
+ @project_discovered_extends = {}.freeze
417
+ @project_discovered_deferred_ranges = {}.freeze
418
+ # Issue #1120 — `{refined class => {method => [refining modules]}}`, every `refine X do … end` body.
419
+ @project_discovered_refinements = {}.freeze
420
+ # Issue #1367 — the project's `Inference::GlobalWriteCensus`, which the `global.*` write rules read.
421
+ @project_discovered_global_write_census = Set.new.freeze
365
422
  @project_discovered_class_sources = {}.freeze
366
423
  # Issue #644 — the cross-file VALUE-constant publication table (`{qualified name => Type::Constant}`,
367
424
  # literal writes only) and its per-name write attribution. The first seeds `in_source_constants` on
@@ -372,8 +429,12 @@ module Rigor
372
429
  # ...and the raw per-(name, path) publication census the incremental producer diffs.
373
430
  @project_constant_writes = {}.freeze
374
431
  @published_constant_name_set = nil
432
+ @constant_writer_index = nil
433
+ @constant_shadower_index = nil
375
434
  @project_discovered_method_visibilities = {}.freeze
376
435
  @project_discovered_methods = {}.freeze
436
+ # Issue #992 — the cross-file parameter-envelope table `call.wrong-arity` reads for an undeclared `def`.
437
+ @project_discovered_parameter_envelopes = {}.freeze
377
438
  @project_data_member_layouts = {}.freeze
378
439
  @project_struct_member_layouts = {}.freeze
379
440
  # Issue #684 — set per run by {#project_discovery_expansion}; nil on every path that does not widen.
@@ -388,6 +449,9 @@ module Rigor
388
449
  # (sequential + fork-worker) through `project_scope_seed_tables`. Empty unless the project lists
389
450
  # `pre_eval:` files that declare publishable constants, so a project without one is byte-identical.
390
451
  @project_pre_eval_constants = {}.freeze
452
+ # Issue #663 — the listed set {#publishable_pre_eval_constants} asks "is this writer one of ours?"
453
+ # against; a function of the configuration alone, so it outlives every discovery rebuild.
454
+ @pre_eval_path_set = nil
391
455
  # ADR-84 WD2 — per-run identity token for the user-method return memo's bucket (see
392
456
  # Scope::DiscoveryIndex#run_generation). Minted fresh in `run_analysis` so the memo never serves an
393
457
  # entry across a run boundary (LSP re-check, ADR-62 warm loop); nil until the first run so
@@ -432,6 +496,12 @@ module Rigor
432
496
  @snapshots.reset_for_run
433
497
  # Per-run reset of the deferred-discovery memo (see `#ensure_project_discovery`).
434
498
  @project_discovery_done = false
499
+ # #392 — re-derive the template units each run: their source files can change between two runs of
500
+ # one long-lived Runner (the LSP's), and a unit is never served from a cache of its own. Since
501
+ # #1038 "re-derive" is not "recompile": a `prebuilt:` runner carries the snapshot's warm index into
502
+ # {#template_units}, which still re-expands every claim and revalidates every template against the
503
+ # filesystem, and only compiles what actually moved.
504
+ @template_units = nil
435
505
  # Per-run reset of the environment the cacheable path resolves, reused by the envelope pass so a
436
506
  # run never builds two.
437
507
  @run_environment = nil
@@ -479,9 +549,8 @@ module Rigor
479
549
  # so the result matches a one-file disk run; only the cross-file project pre-pass is empty (there is
480
550
  # one file, and the per-file indexer self-discovers its own classes / defs).
481
551
  #
482
- # @param source [String] Ruby source to analyze.
483
- # @param path [String] logical path for diagnostic locations.
484
- # @return [Result]
552
+ # @param source — Ruby source to analyze.
553
+ # @param path — logical path for diagnostic locations.
485
554
  def run_source(source:, path: "(source).rb")
486
555
  @in_memory_sources = { path => source }
487
556
  run([path])
@@ -607,7 +676,7 @@ module Rigor
607
676
  # arg-flooring surface). Only meaningful right after a run populated the memo (baseline / recheck),
608
677
  # before the bucket rolls. Keys are held live here; the session Marshals them for the snapshot.
609
678
  #
610
- # @return [Hash] `{ [path, "Class#method" | "Class.method"] => { keys:, returns:, effects: } }`.
679
+ # @return `{ [path, "Class#method" | "Class.method"] => { keys:, returns:, effects: } }`.
611
680
  def return_summaries
612
681
  memo = Inference::ExpressionTyper.harvest_return_memo
613
682
  return {} if memo.empty?
@@ -665,9 +734,9 @@ module Rigor
665
734
  # store) exactly as a real run's setup does, then re-drives each spec's def through the ADR-84 return
666
735
  # memo. Off any hot path — invoked only when declaration-stable changed pairs carry a persisted summary.
667
736
  #
668
- # @param paths [Array<String>, nil] analysis roots (nil → the configuration's `paths:`).
669
- # @param specs [Array<Hash>] each `{ class_name:, method_name:, singleton:, keys: [[receiver, args], …] }`.
670
- # @return [Hash] `{ [class_name, method_name, singleton] => [return_descriptor_or_nil, …] }`.
737
+ # @param paths — analysis roots (nil → the configuration's `paths:`).
738
+ # @param specs — each `{ class_name:, method_name:, singleton:, keys: [[receiver, args], …] }`.
739
+ # @return `{ [class_name, method_name, singleton] => [return_descriptor_or_nil, …] }`.
671
740
  def evaluate_return_types(paths, specs)
672
741
  return {} if specs.empty?
673
742
 
@@ -726,7 +795,10 @@ module Rigor
726
795
  @run_served_from_cache = false
727
796
  return assemble_run_diagnostics(expansion) unless run_result_cacheable?
728
797
 
729
- environment = @pool_coordinator.resolve_sequential_environment(source_files: target_files(expansion))
798
+ # #392 — `source_files:` is what a plugin's `source_rbs_synthesizer` is offered at env-build time,
799
+ # and a template unit is not Ruby the synthesiser can read (rbs-inline would be handed `.rbx`
800
+ # bytes). The `.rb` expansion is the right set here; the units join the ANALYSED set only.
801
+ environment = @pool_coordinator.resolve_sequential_environment(source_files: expansion.fetch(:files))
730
802
  # Lazy-files descriptor: the cache KEY reads only `gems` + `configs`; the RBS signature-tree `files`
731
803
  # are digested solely by `run_dependency_descriptor` on a MISS, so a warm HIT never walks the tree.
732
804
  rbs_descriptor = if environment&.rbs_loader
@@ -795,7 +867,7 @@ module Rigor
795
867
  # fixpoint over them, which is the pre-split behaviour and the retry the fail-soft posture wants.
796
868
  # Only when neither entry answers does the run re-analyse.
797
869
  #
798
- # @return [Boolean] whether this run was served.
870
+ # @return whether this run was served.
799
871
  def served_from_effect_entries?(descriptor)
800
872
  summary = peek_effect_summary(descriptor)
801
873
  if summary
@@ -978,7 +1050,14 @@ module Rigor
978
1050
  def close_effect_graph
979
1051
  return unless @record_effects
980
1052
 
981
- @effect_table = Effects::Propagator.propagate(effect_collection, discharge: effect_discharge)
1053
+ # `declined_unit_keys:` — #1065. The templates a plugin claimed and produced no unit for; a render
1054
+ # edge's format fallback must not resolve past one of them, because the file exists and the
1055
+ # framework runs it, so the other format's effects are not what that render produces.
1056
+ @effect_table = Effects::Propagator.propagate(
1057
+ effect_collection,
1058
+ discharge: effect_discharge,
1059
+ declined_unit_keys: template_units.declined_unit_keys
1060
+ )
982
1061
  rescue StandardError
983
1062
  @effect_table = Effects::EffectTable.empty
984
1063
  end
@@ -1009,6 +1088,11 @@ module Rigor
1009
1088
  # untouched so its lazy build timing is unchanged.
1010
1089
  environment = seed_pre_eval_constants(expansion, environment)
1011
1090
  diagnostics = @diagnostic_aggregator.pre_file_diagnostics(expansion)
1091
+ # #392 — a template transform that raised, a template that could not be read, or a unit naming a
1092
+ # file it was not offered. Reported through the same `:plugin_loader` `runtime-error` envelope a
1093
+ # raise from `#diagnostics_for_file` uses (ADR-2 § "Plugin Trust and I/O Policy"), rather than
1094
+ # dropped in silence: a plugin author whose view stopped being analysed has nothing else to read.
1095
+ diagnostics += template_unit_failure_diagnostics
1012
1096
  # ADR-46 — record which project files this run actually analyzed (the `analyze_only` subset, or
1013
1097
  # all of them). The incremental orchestrator serves every analyzed-but-not-affected file from the
1014
1098
  # per-file cache, so it needs the full analyzed set to subtract the affected closure from.
@@ -1025,12 +1109,39 @@ module Rigor
1025
1109
  # override re-stamps it; and a pool backend folds `.rigor.yml`-positioned prepare / pool-degraded rows
1026
1110
  # into the same return, which the slice drops. The run's own stream is stamped once, at the end.
1027
1111
  diagnostics += analyze_targets(targets, environment: environment, project_files: expansion.fetch(:files))
1112
+ # Issue #1060 — the positioned batches plugins registered through `Plugin::Base#emit_once`, read once
1113
+ # every worker payload has been merged. Kept beside the per-file stream (their rows name files and
1114
+ # lines) but outside `#per_file_diagnostics`, which `analyze_targets` has already sliced, so no one
1115
+ # file's incremental cache entry carries a project-wide batch.
1116
+ diagnostics += @diagnostic_aggregator.plugin_run_emission_diagnostics
1028
1117
  # ADR-103 WD12 — the effect fixpoint, in the post-pool aggregation slot beside the conformance
1029
1118
  # results. Graph-only over a finite lattice, so it is a plain worklist to a true fixpoint; it
1030
1119
  # contributes NO diagnostics and its result leaves through `#effect_table`, never through the
1031
1120
  # stream. Skipped entirely when collection did not run.
1032
1121
  close_effect_graph
1033
- diagnostics += @diagnostic_aggregator.rbs_quarantined_signature_diagnostics
1122
+ diagnostics + post_analysis_diagnostic_streams
1123
+ end
1124
+
1125
+ def template_unit_failure_diagnostics
1126
+ template_units.failures.map do |failure|
1127
+ Diagnostic.new(
1128
+ path: failure.path, line: 1, column: 1,
1129
+ message: "plugin #{failure.plugin_id.inspect} produced no template unit for " \
1130
+ "#{failure.path}: #{failure.message}",
1131
+ severity: :error, rule: "runtime-error", source_family: :plugin_loader
1132
+ )
1133
+ end
1134
+ end
1135
+ private :template_unit_failure_diagnostics
1136
+
1137
+ # The rbs-coverage build-failure ladder plus every remaining post-analysis stream
1138
+ # `#assemble_run_diagnostics` appends, in their fixed contract order (see the comments at each call
1139
+ # below and at the streams' own definitions). Split out of `#assemble_run_diagnostics` so that
1140
+ # method's `Metrics/AbcSize` stays under budget — every stream here is a plain `DiagnosticAggregator`
1141
+ # reader with no branching of its own, so the split changes nothing about WHEN or in WHAT order they
1142
+ # run.
1143
+ def post_analysis_diagnostic_streams
1144
+ diagnostics = @diagnostic_aggregator.rbs_quarantined_signature_diagnostics
1034
1145
  diagnostics += @diagnostic_aggregator.rbs_environment_build_failed_diagnostics
1035
1146
  # Issue #696 — after its env-wide twin and before the synthesized-namespace notice: the four
1036
1147
  # `rbs.coverage.*` build conditions surface widest-consequence first, and their relative order is
@@ -1040,12 +1151,25 @@ module Rigor
1040
1151
  # `type` alias would have contributed, never a class's own declared method surface, so it is the
1041
1152
  # narrowest-consequence rung on the same ladder.
1042
1153
  diagnostics += @diagnostic_aggregator.rbs_hkt_scan_failed_diagnostics
1043
- diagnostics += @diagnostic_aggregator.rbs_synthesized_namespace_diagnostics
1154
+ # The synthesized-namespace notice, then (#610) the plugin-signature stand-down: the outcome that
1155
+ # AVOIDED a definition-build failure, so the quietest row on that ladder.
1156
+ diagnostics += @diagnostic_aggregator.rbs_coverage_notice_diagnostics
1044
1157
  diagnostics += @diagnostic_aggregator.conforms_to_diagnostics
1045
1158
  diagnostics += @diagnostic_aggregator.rbs_extended_reporter_diagnostics
1046
1159
  diagnostics += @diagnostic_aggregator.boundary_cross_diagnostics
1047
- diagnostics + @diagnostic_aggregator.source_rbs_synthesis_diagnostics
1048
- end
1160
+ diagnostics += @diagnostic_aggregator.source_rbs_synthesis_diagnostics
1161
+ # Issue #1051 — read after `analyze_targets` for the same reason the refusal stream below is: a
1162
+ # plugin may register a disclosure from `#prepare` (the usual case) or while analysing a file, and
1163
+ # both have to be in before the table is harvested. Ordered before the refusals so the run-level
1164
+ # plugin rows read plugin-authored notice first, engine-authored envelope second.
1165
+ diagnostics += @diagnostic_aggregator.plugin_run_disclosure_diagnostics
1166
+ # Issue #959 — read LAST (after `analyze_targets`, which already ran before this method is called),
1167
+ # so every per-file plugin call has had its chance to record a refusal on its (per-plugin-instance
1168
+ # memoised) `IoBoundary`, alongside the prepare-time refusals the same boundary instance may
1169
+ # already carry.
1170
+ diagnostics + @diagnostic_aggregator.plugin_trust_refusal_diagnostics
1171
+ end
1172
+ private :post_analysis_diagnostic_streams
1049
1173
 
1050
1174
  # #788 round 7 — runs per-file analysis over `targets` and exposes what it produced as
1051
1175
  # `#per_file_diagnostics`: the `analyze_files` return, stamped with the same severity profile the run's
@@ -1053,7 +1177,9 @@ module Rigor
1053
1177
  # re-stamps), sliced to the rows positioned at the targets. Returns the RAW return for the run's stream,
1054
1178
  # which is stamped once, at the end of `#run_analysis`.
1055
1179
  def analyze_targets(targets, environment:, project_files:)
1056
- raw = @pool_coordinator.analyze_files(targets, environment: environment, project_files: project_files)
1180
+ raw = template_units.remap(
1181
+ @pool_coordinator.analyze_files(targets, environment: environment, project_files: project_files)
1182
+ )
1057
1183
  analysed = targets.to_set
1058
1184
  @per_file_diagnostics = @diagnostic_aggregator.apply_severity_profile(raw)
1059
1185
  .select { |diagnostic| analysed.include?(diagnostic.path) }
@@ -1161,7 +1287,8 @@ module Rigor
1161
1287
  # reconstructs `rbs.libraries` from config) can never drift out of key agreement.
1162
1288
  RunCacheKey.descriptor(
1163
1289
  configuration: @configuration, files: expansion.fetch(:files),
1164
- explain: @explain, rbs_config_entries: rbs_descriptor.configs
1290
+ explain: @explain, rbs_config_entries: rbs_descriptor.configs,
1291
+ template_units_digest: template_units.digest
1165
1292
  )
1166
1293
  end
1167
1294
 
@@ -1178,14 +1305,23 @@ module Rigor
1178
1305
 
1179
1306
  def build_run_dependency_descriptor(expansion, rbs_descriptor)
1180
1307
  entries = analyzed_file_entries(expansion) + discovery_file_entries(expansion) +
1181
- pre_eval_file_entries + rbs_descriptor.files
1182
- globs = []
1308
+ pre_eval_file_entries + template_unit_file_entries + rbs_descriptor.files
1309
+ # #979 — the signature ROOTS' listings, one glob row each. `rbs_descriptor.files` covers only the
1310
+ # `.rbs` files that existed while the run read them, so without these a signature file written after
1311
+ # the run left the slot validating fresh and the warm run answered without it.
1312
+ globs = rbs_descriptor.globs + template_unit_glob_entries
1183
1313
  @plugin_registry.plugins.each do |plugin|
1184
1314
  # Read the boundary WITHOUT triggering its lazy `@io_boundary ||=` initializer: plugin instances
1185
1315
  # are frozen after the run, and a plugin that never built a boundary read no files through it,
1186
1316
  # so it contributes no dependencies.
1187
1317
  boundary = plugin.instance_variable_get(:@io_boundary)
1188
- entries.concat(boundary.cache_descriptor.files) if boundary
1318
+ if boundary
1319
+ # #629 — a boundary carries GlobEntry listing rows too (`IoBoundary#list_directory`), and they
1320
+ # are the only edge back to a file the run decided was ABSENT from a directory it consulted.
1321
+ boundary_descriptor = boundary.cache_descriptor
1322
+ entries.concat(boundary_descriptor.files)
1323
+ globs.concat(boundary_descriptor.globs)
1324
+ end
1189
1325
  plugin.class.producers.each_value do |prod|
1190
1326
  globs.concat(plugin.send(:watch_glob_entries, prod[:watch])) if prod[:watch]
1191
1327
  end
@@ -1221,6 +1357,30 @@ module Rigor
1221
1357
  # expansion — so without this entry, editing one would leave the run-result cache serving diagnostics
1222
1358
  # computed against the previous version. The common case (a `lib/core_ext/` file that is also under
1223
1359
  # `paths:`) contributes a duplicate entry, which the descriptor's per-path validation absorbs.
1360
+ # #392 — the TEMPLATE files behind the run's units. The key slot carries the compiled Ruby's digest
1361
+ # (`template_units_digest`), which is what decides whether a cached answer describes this source; these
1362
+ # entries are the validation half, so a template edited between two runs re-globs and re-digests here
1363
+ # exactly as an analysed `.rb` file does. Both are needed: a transform that compiles two different
1364
+ # templates to the same Ruby must still not resurrect the wrong PATH in a diagnostic.
1365
+ # #392 — one `:names` row per claimed `template_globs:` pattern, whether or not it matched. The file
1366
+ # rows below cover edits to templates that EXIST; only a glob row notices one appearing or vanishing,
1367
+ # which is what let a project's first template stay invisible to every warm run.
1368
+ def template_unit_glob_entries
1369
+ template_units.glob_entries
1370
+ end
1371
+
1372
+ # Every template the run READ, successes and failures alike: a `plugin_loader` row produced by a
1373
+ # template that would not compile must not outlive the edit that fixes it, and a buffer-bound path is
1374
+ # digested at the bytes the run actually read.
1375
+ def template_unit_file_entries
1376
+ template_units.source_paths.filter_map do |path|
1377
+ physical = template_units.physical_path(path, @buffer)
1378
+ next unless File.file?(physical)
1379
+
1380
+ Cache::Descriptor::FileEntry.stat(path: physical, digest: Cache::FileDigest.hexdigest(physical))
1381
+ end
1382
+ end
1383
+
1224
1384
  def pre_eval_file_entries
1225
1385
  @configuration.pre_eval.filter_map do |path|
1226
1386
  physical = @buffer ? @buffer.resolve(path) : path
@@ -1305,18 +1465,39 @@ module Rigor
1305
1465
  @project_discovered_singleton_def_sources = discovery.discovered_singleton_def_sources
1306
1466
  @project_discovered_superclasses = discovery.discovered_superclasses
1307
1467
  @project_discovered_header_nestings = discovery.discovered_header_nestings
1308
- @project_discovered_includes = discovery.discovered_includes
1468
+ apply_discovery_mixin_tables(discovery)
1469
+ @project_discovered_deferred_ranges = discovery.discovered_deferred_ranges
1470
+ apply_discovery_call_surface_tables(discovery)
1309
1471
  @project_discovered_class_sources = discovery.discovered_class_sources
1310
1472
  @project_constant_values = discovery.constant_values
1311
1473
  @published_constant_name_set = nil
1474
+ @constant_writer_index = nil
1475
+ @constant_shadower_index = nil
1312
1476
  @project_constant_sources = discovery.constant_sources
1313
1477
  @project_constant_writes = discovery.constant_writes
1314
1478
  @project_discovered_method_visibilities = discovery.discovered_method_visibilities
1315
1479
  @project_discovered_methods = discovery.discovered_methods
1480
+ @project_discovered_parameter_envelopes = discovery.discovered_parameter_envelopes
1316
1481
  @project_data_member_layouts = discovery.data_member_layouts
1317
1482
  @project_struct_member_layouts = discovery.struct_member_layouts
1318
1483
  end
1319
1484
 
1485
+ # The tables only check rules read beyond dispatch — the issue #1120 refinements and the issue #1367
1486
+ # global-write census. Extracted to keep {#apply_discovery_result} under its ABC budget.
1487
+ def apply_discovery_call_surface_tables(discovery)
1488
+ @project_discovered_refinements = discovery.discovered_refinements
1489
+ @project_discovered_global_write_census = discovery.discovered_global_write_census
1490
+ end
1491
+
1492
+ # The three mixin tables the discovery pass carries — the ADR-24 `include` map, its issue #1123
1493
+ # `prepend` twin, and the #898 `extend` table. Extracted to keep {#apply_discovery_result} under its
1494
+ # ABC budget.
1495
+ def apply_discovery_mixin_tables(discovery)
1496
+ @project_discovered_includes = discovery.discovered_includes
1497
+ @project_discovered_prepends = discovery.discovered_prepends
1498
+ @project_discovered_extends = discovery.discovered_extends
1499
+ end
1500
+
1320
1501
  # Internal: builds the deferred cross-file discovery tables at most once per run and adopts them.
1321
1502
  # Memoised on `@project_discovery_done` (reset at the start of `#run`). No-op under `@prebuilt` — the
1322
1503
  # LSP path deliberately seeds an empty project scope from a snapshot that carries no discovery tables,
@@ -1399,7 +1580,7 @@ module Rigor
1399
1580
  3.0.0 3.1.0 3.2.0 3.3.0 3.4.0 3.5.0 4.0.0 4.1.0 4.2.0
1400
1581
  ].freeze
1401
1582
 
1402
- # @return [String, nil] the lowest `target_ruby` this Prism build accepts, or nil if none of the
1583
+ # @return the lowest `target_ruby` this Prism build accepts, or nil if none of the
1403
1584
  # ladder parses. Memoised per process (the value is constant for a given Prism).
1404
1585
  def self.prism_supported_floor
1405
1586
  @prism_supported_floor ||= PRISM_VERSION_LADDER.find do |candidate|
@@ -1450,6 +1631,25 @@ module Rigor
1450
1631
  # The buffer's logical path is added to the file list even if it's not under `paths:` — per design §
1451
1632
  # "Failure envelope": "--instead-of=Y with Y not under any paths: directory → treated as a valid
1452
1633
  # logical identity for the buffer".
1634
+ # #392 — the run's template units. Built once per run, on the parent, AFTER the plugin pre-pass has
1635
+ # loaded the registry; a run whose plugins declare no `template_globs:` never globs and never calls a
1636
+ # plugin.
1637
+ #
1638
+ # #1038 — a runner built with `prebuilt:` hands the snapshot's warm index to `collect(previous:)`, so
1639
+ # a per-buffer publish against a long-lived {LanguageServer::ProjectContext} re-runs the transform
1640
+ # only for a template that actually moved on disk (and for the buffer's own, always). The globs are
1641
+ # still re-expanded and every surviving template is still revalidated, so an added, deleted or edited
1642
+ # template is seen on the next publish without the owner invalidating anything. A runner with no
1643
+ # `prebuilt:` — every CLI run — passes no `previous:` and builds the index exactly as before.
1644
+ def template_units
1645
+ @template_units ||= if @plugin_registry.nil?
1646
+ TemplateUnits.empty
1647
+ else
1648
+ TemplateUnits.collect(registry: @plugin_registry, buffer: @buffer,
1649
+ previous: @prebuilt&.template_units)
1650
+ end
1651
+ end
1652
+
1453
1653
  def target_files(expansion)
1454
1654
  files = expansion.fetch(:files)
1455
1655
  # ADR-46 slice 2 — restrict the analyzed set to the affected closure while the pre-pass (run
@@ -1460,14 +1660,41 @@ module Rigor
1460
1660
  # the same allowance option A makes below.
1461
1661
  files = files.select { |path| @analyze_only.include?(path) }
1462
1662
  files |= [@buffer.logical_path] if @buffer && @analyze_only.include?(@buffer.logical_path)
1463
- return files
1663
+ return template_unit_targets(files)
1464
1664
  end
1465
- return files if @buffer.nil?
1665
+ return template_unit_targets(files) if @buffer.nil?
1466
1666
 
1467
- # Editor mode option A — no closure, so the buffer's single logical path IS the analyzed set.
1667
+ # Editor mode option A — no closure, so the buffer's single logical path IS the analyzed set. The
1668
+ # template units deliberately do NOT join it: a per-buffer publish answers about the buffer the
1669
+ # editor is showing, and appending every view would publish diagnostics for files the editor did
1670
+ # not ask about (and re-parse them on every keystroke).
1468
1671
  [@buffer.logical_path]
1469
1672
  end
1470
1673
 
1674
+ # #392 — the template units join the analysed set AFTER any narrowing, so an `--incremental` recheck
1675
+ # re-analyses every unit even when its closure is one `.rb` file. Nothing in the ADR-46 dependency
1676
+ # graph names a synthesised file, so there is no edge that could put a unit in a closure — narrowing
1677
+ # first and appending second is what makes "always re-analysed" true rather than a comment. The cost
1678
+ # is one parse per template on the warm path; the alternative silently drops every template
1679
+ # diagnostic from the second run onwards.
1680
+ #
1681
+ # Appended rather than merged in sorted order so the `.rb` expansion's own order — and every output
1682
+ # that follows it — is byte-identical for a project with no units.
1683
+ def template_unit_targets(files)
1684
+ return files if template_units.empty?
1685
+
1686
+ files + (template_units.paths - files)
1687
+ end
1688
+ private :template_unit_targets
1689
+
1690
+ # #392 — the unit paths this run synthesised. Read by {IncrementalSession}, which keeps them OUT of
1691
+ # its analysed-file set: a unit is never served from the per-file cache and never counts as a file
1692
+ # that was removed from the project, because `#target_files` re-analyses it every run.
1693
+ def template_unit_paths
1694
+ template_units.paths
1695
+ end
1696
+ public :template_unit_paths
1697
+
1471
1698
  # Editor mode (`buffer:` non-nil) auto-flips the cache store to `read_only: true` so multiple
1472
1699
  # debounced editor invocations against the same project don't churn the on-disk cache or race on
1473
1700
  # schema-version writes. Cache reads continue unchanged; misses still run the producer block but the
@@ -1504,7 +1731,9 @@ module Rigor
1504
1731
  synthetic_method_index: -> { @synthetic_method_index },
1505
1732
  project_patched_methods: -> { @project_patched_methods },
1506
1733
  project_scope_seed: -> { project_scope_seed_tables },
1507
- analyze_file: ->(path, environment) { analyze_file(path, environment) }
1734
+ template_units: -> { template_units },
1735
+ analyze_file: ->(path, environment) { analyze_file(path, environment) },
1736
+ restored_run_level_rows: @restored_run_level_rows
1508
1737
  )
1509
1738
  @diagnostic_aggregator = DiagnosticAggregator.new(
1510
1739
  configuration: @configuration,
@@ -1518,10 +1747,12 @@ module Rigor
1518
1747
  pre_eval_diagnostics_from_scanner: -> { @pre_eval_diagnostics_from_scanner },
1519
1748
  synthesized_namespaces_snapshot: -> { @snapshots.synthesized_namespaces },
1520
1749
  quarantined_signatures_snapshot: -> { @snapshots.quarantined_signatures },
1750
+ signature_standdowns_snapshot: -> { @snapshots.signature_standdowns },
1521
1751
  env_build_failure_snapshot: -> { @snapshots.env_build_failure },
1522
1752
  definition_build_failures_snapshot: -> { @snapshots.definition_build_failures },
1523
1753
  hkt_scan_failure_snapshot: -> { @snapshots.hkt_scan_failure },
1524
- conformance_results_snapshot: -> { @snapshots.conformance_results }
1754
+ conformance_results_snapshot: -> { @snapshots.conformance_results },
1755
+ pooled_run_disclosures: -> { @pool_coordinator.collected_run_disclosures }
1525
1756
  )
1526
1757
  end
1527
1758
 
@@ -1548,7 +1779,9 @@ module Rigor
1548
1779
  RunStats.new(
1549
1780
  wall_seconds: Process.clock_gettime(Process::CLOCK_MONOTONIC) - wall_started_at,
1550
1781
  peak_rss_bytes: RunStats.peak_rss_bytes,
1551
- target_files: expansion.fetch(:files).size,
1782
+ # #392 — the template units count too: they are files this run parsed and typed, and a stats line
1783
+ # that omitted them would under-report exactly the work a views-heavy project added.
1784
+ target_files: expansion.fetch(:files).size + template_units.paths.size,
1552
1785
  rbs_classes_total: snapshot.size,
1553
1786
  rbs_classes_project_sig: project_sig,
1554
1787
  rbs_classes_bundled: bundled,
@@ -1705,6 +1938,12 @@ module Rigor
1705
1938
  # so Prism's location data carries the LOGICAL path. Non-binding paths go through the cheaper
1706
1939
  # `Prism.parse_file` codepath unchanged.
1707
1940
  def parse_source(path)
1941
+ entry = template_units[path]
1942
+ if entry
1943
+ return Prism.parse(entry.source, filepath: path, version: @configuration.target_ruby,
1944
+ scopes: template_units.parse_scopes(path))
1945
+ end
1946
+
1708
1947
  if @in_memory_sources&.key?(path)
1709
1948
  return Prism.parse(@in_memory_sources[path], filepath: path, version: @configuration.target_ruby)
1710
1949
  end
@@ -1752,22 +1991,58 @@ module Rigor
1752
1991
  unless @project_discovered_header_nestings.empty?
1753
1992
  tables[:discovered_header_nestings] = @project_discovered_header_nestings
1754
1993
  end
1755
- tables[:discovered_includes] = @project_discovered_includes unless @project_discovered_includes.empty?
1994
+ seed_mixin_tables(tables)
1756
1995
  unless @project_discovered_method_visibilities.empty?
1757
1996
  tables[:discovered_method_visibilities] = @project_discovered_method_visibilities
1758
1997
  end
1759
1998
  tables[:discovered_methods] = @project_discovered_methods unless @project_discovered_methods.empty?
1999
+ unless @project_discovered_deferred_ranges.empty?
2000
+ tables[:discovered_deferred_ranges] = @project_discovered_deferred_ranges
2001
+ end
2002
+ seed_call_surface_tables(tables)
1760
2003
  seed_opt_in_pre_pass_tables(tables)
1761
2004
  seed_member_layout_tables(tables)
1762
2005
  seed_dependency_attribution_tables(tables)
1763
2006
  tables
1764
2007
  end
1765
2008
 
2009
+ # The tables only check rules read: the issue #992 parameter envelopes (`call.wrong-arity`), the issue #1120
2010
+ # refinements (`call.undefined-method`), and the issue #1367 global-write census (the `global.*` write
2011
+ # rules). Split out of {#project_scope_seed_tables} to keep it under the complexity budget.
2012
+ def seed_call_surface_tables(tables)
2013
+ unless @project_discovered_parameter_envelopes.empty?
2014
+ tables[:discovered_parameter_envelopes] = @project_discovered_parameter_envelopes
2015
+ end
2016
+ tables[:discovered_refinements] = @project_discovered_refinements unless @project_discovered_refinements.empty?
2017
+ # Issue #1367 — read by the `global.*` write rules.
2018
+ return if @project_discovered_global_write_census.empty?
2019
+
2020
+ tables[:discovered_global_write_census] = @project_discovered_global_write_census
2021
+ end
2022
+
2023
+ # The three mixin tables: the ADR-24 instance-side `include` map, its issue #1123 `prepend` twin
2024
+ # (which carries the instance-ancestor order the include table's set-shaped value cannot), and the
2025
+ # #898 singleton-side `extend` table. Grouped here because they are read as one picture of a class's
2026
+ # ancestry, and split out of {#project_scope_seed_tables} to keep it under the complexity budget.
2027
+ def seed_mixin_tables(tables)
2028
+ tables[:discovered_includes] = @project_discovered_includes unless @project_discovered_includes.empty?
2029
+ tables[:discovered_prepends] = @project_discovered_prepends unless @project_discovered_prepends.empty?
2030
+ tables[:discovered_extends] = @project_discovered_extends unless @project_discovered_extends.empty?
2031
+ end
2032
+
1766
2033
  # ADR-46 slice 1 / issue #644 — the two SOURCE-ATTRIBUTION tables, read only by the recording accessors
1767
2034
  # (`Scope#record_class_dependency` for the class-declaration map, `Scope#record_constant_dependency`
1768
- # for the constant-write map). A normal run carries neither. Extracted to keep
2035
+ # for the constant-write map). A normal run carries neither of the two. Extracted to keep
1769
2036
  # {#project_scope_seed_tables} under the complexity budget.
2037
+ #
2038
+ # Issue #617 — the census's binding writes, grouped by last segment, ride every run instead: a constant
2039
+ # compound write whose plain read resolves nothing reads its binding off them, a TYPE rather than an edge.
2040
+ # Issue #1290's shadowing writes ride beside them for the lexical ladder, for the same reason.
1770
2041
  def seed_dependency_attribution_tables(tables)
2042
+ writers = constant_writer_index
2043
+ tables[:constant_writers] = writers unless writers.empty?
2044
+ shadowers = constant_shadower_index
2045
+ tables[:constant_shadowers] = shadowers unless shadowers.empty?
1771
2046
  return unless @record_dependencies
1772
2047
 
1773
2048
  tables[:discovered_class_sources] = @project_discovered_class_sources unless
@@ -1802,12 +2077,44 @@ module Rigor
1802
2077
  tables[:published_constant_names] = names unless names.empty?
1803
2078
  end
1804
2079
 
1805
- # Issue #644 — the seeded `in_source_constants` table, joining the two cross-file constant producers.
1806
- # The whole-project literal table wins over a `pre_eval:` publication of the same name: both describe
1807
- # the same assignment, the literal table pins the value the write actually carries while ADR-17's
1808
- # widener erases it to the class, and listing a file under `pre_eval:` must never LOSE precision. The
1809
- # per-file table still wins over both (`ScopeIndexer.index`'s merge) — same-file stays the most
1810
- # specific authority.
2080
+ # Issue #617 — the censused names that bind, grouped by last segment (`Scope#bound_constant_names`). A
2081
+ # name some write other than a memo `||=` touches always binds. A memo-only name binds once memos of its
2082
+ # segment span two files: either may load first and set the constant the other then reads, so only a
2083
+ # memo no other file shares keeps the idiom's reading. Files are counted expanded, because discovery can
2084
+ # walk one file under two spellings (`lib/b.rb`, `./lib/b.rb`).
2085
+ def constant_writer_index
2086
+ @constant_writer_index ||= begin
2087
+ memo_files = memo_files_by_segment
2088
+ @project_constant_writes.each_with_object({}) do |(name, by_path), index|
2089
+ segment = name.split("::").last
2090
+ next if memo_only?(by_path) && memo_files.fetch(segment).size < 2
2091
+
2092
+ (index[segment] ||= []) << name
2093
+ end.each_value(&:freeze).freeze
2094
+ end
2095
+ end
2096
+
2097
+ # Issue #1290 — the censused names some write other than a memo `||=` assigns, grouped by last segment
2098
+ # (`Scope#shadowing_constant_names`): the ones that exist whether or not an outer constant of the name
2099
+ # does, and so stop the lexical ladder above it. A memo-only name is left out however many files memoize
2100
+ # it, unlike in {#constant_writer_index}: `X ||= v` resolves `X` through the very lookup whose lower rung
2101
+ # the stop would replace, so wherever that rung answers, the memo finds it and never creates the name.
2102
+ def constant_shadower_index
2103
+ @constant_shadower_index ||= @project_constant_writes.each_with_object({}) do |(name, by_path), index|
2104
+ (index[name.split("::").last] ||= []) << name unless memo_only?(by_path)
2105
+ end.each_value(&:freeze).freeze
2106
+ end
2107
+
2108
+ def memo_files_by_segment
2109
+ @project_constant_writes.each_with_object({}) do |(name, by_path), files|
2110
+ set = (files[name.split("::").last] ||= Set.new)
2111
+ by_path.each { |path, descriptor| set << File.expand_path(path) if memo_descriptor?(descriptor) }
2112
+ end
2113
+ end
2114
+
2115
+ def memo_only?(by_path) = by_path.each_value.all? { |descriptor| memo_descriptor?(descriptor) }
2116
+ def memo_descriptor?(descriptor) = descriptor == Inference::ScopeIndexer::CONSTANT_MEMO
2117
+
1811
2118
  # Issue #644 — the LAST SEGMENTS of the published table, the run-wide half of
1812
2119
  # `Scope#published_constant?`. Seeded on EVERY run (unlike `constant_sources`, which only a recording
1813
2120
  # run needs) because {CheckRules::PublishedConstantGuard} asks the question on every `if` / `unless`.
@@ -1816,11 +2123,57 @@ module Rigor
1816
2123
  @project_constant_values.keys.to_set { |name| name.split("::").last }.freeze
1817
2124
  end
1818
2125
 
2126
+ # Issue #644 — the seeded `in_source_constants` table, joining the two cross-file constant producers.
2127
+ # The whole-project literal table wins over a `pre_eval:` publication of the same name: both describe
2128
+ # the same assignment, the literal table pins the value the write actually carries while ADR-17's
2129
+ # widener erases it to the class, and listing a file under `pre_eval:` must never LOSE precision. The
2130
+ # per-file table still wins over both (`ScopeIndexer.index`'s merge) — same-file stays the most
2131
+ # specific authority.
1819
2132
  def merged_seed_constants
1820
- return @project_constant_values if @project_pre_eval_constants.empty?
1821
- return @project_pre_eval_constants if @project_constant_values.empty?
2133
+ pre_eval = publishable_pre_eval_constants
2134
+ return @project_constant_values if pre_eval.empty?
2135
+ return pre_eval if @project_constant_values.empty?
2136
+
2137
+ pre_eval.merge(@project_constant_values).freeze
2138
+ end
1822
2139
 
1823
- @project_pre_eval_constants.merge(@project_constant_values).freeze
2140
+ # Issue #663 — the `pre_eval:` publication with every name an UNLISTED file also assigns retracted.
2141
+ # Without this a listed `X = :a` answered `Symbol` at every reader while a sibling `X = "str"` decided
2142
+ # the runtime value half the time: a confident wrong type, not lost precision, and the one asymmetry
2143
+ # #644's precedence work left standing. The retraction costs the opt-in nothing where it earns its
2144
+ # keep — a name only listed files write is untouched, and where the literal table also publishes it
2145
+ # already won (above).
2146
+ #
2147
+ # Conflict is read off #644's write attribution, which censuses every assigning form, so a name the
2148
+ # listed file's own typed walk widened is retracted by an unlisted `X += 1` or `A, X = 1, 2` as
2149
+ # readily as by a plain reassignment. Among LISTED files the rule is unchanged: `PreEvalConstants`
2150
+ # widens first and drops only on disagreement, so two listed files writing `1` and `2` still publish
2151
+ # `Integer` rather than being retracted for being two.
2152
+ def publishable_pre_eval_constants
2153
+ return @project_pre_eval_constants if @project_pre_eval_constants.empty?
2154
+
2155
+ listed = pre_eval_path_set
2156
+ kept = @project_pre_eval_constants.reject { |name, _| unlisted_constant_writer?(name, listed) }
2157
+ kept.size == @project_pre_eval_constants.size ? @project_pre_eval_constants : kept.freeze
2158
+ end
2159
+
2160
+ # True when some file outside `listed` assigns `name`. The wildcard key is consulted alongside the
2161
+ # name's own attribution ([#668](https://github.com/rigortype/rigor/issues/668)): a `k::X = 7` names
2162
+ # no target, and a listed file kept out of `paths:` (ADR-17 WD5 permits it) contributes no censused
2163
+ # name of its own for the wildcard's paths to have been folded into.
2164
+ def unlisted_constant_writer?(name, listed)
2165
+ segment = name.split("::").last
2166
+ writers = @project_constant_sources[name]
2167
+ wildcard = @project_constant_sources["#{Inference::ScopeIndexer::DYNAMIC_TARGET_PREFIX}#{segment}"]
2168
+ [writers, wildcard].any? do |paths|
2169
+ paths&.any? { |path| !listed.include?(File.expand_path(path)) }
2170
+ end
2171
+ end
2172
+
2173
+ # The listed `pre_eval:` files, expanded so a census path and a configured one compare as the same
2174
+ # file whichever spelling each arrived in.
2175
+ def pre_eval_path_set
2176
+ @pre_eval_path_set ||= @configuration.pre_eval.to_set { |path| File.expand_path(path) }
1824
2177
  end
1825
2178
 
1826
2179
  # ADR-46 — seed the instance + singleton `"path:line"` def-source tables (each only when non-empty).
@@ -1865,10 +2218,11 @@ module Rigor
1865
2218
  def analyze_with_effects(path, environment)
1866
2219
  return analyze_file_body(path, environment) unless @record_effects
1867
2220
 
2221
+ entry = template_units[path]
1868
2222
  diagnostics = nil
1869
2223
  collection = Effects::Collector.collect_for(
1870
2224
  path, attribution: @effect_attribution, envelopes: effect_envelope_index(environment),
1871
- plugin_facts: effect_plugin_facts
2225
+ plugin_facts: effect_plugin_facts, unit_key: entry&.unit_key, unit_owner: entry&.self_type
1872
2226
  ) do
1873
2227
  diagnostics = analyze_file_body(path, environment)
1874
2228
  end
@@ -1907,7 +2261,9 @@ module Rigor
1907
2261
  # is a per-FILE site, so one `Thread.current` read is already nothing. The per-dispatch site in
1908
2262
  # `ExpressionTyper#call_type_for` is the one that pays for the integer fast path.
1909
2263
  Effects::Collector.record_root(parse_result.value)
1910
- scope = seed_project_scope(Scope.empty(environment: environment, source_path: path))
2264
+ scope = template_units.seed(
2265
+ seed_project_scope(Scope.empty(environment: environment, source_path: path)), path
2266
+ )
1911
2267
  # ADR-24 slice 4a/4 — record unresolved implicit-self calls during the typing pass ONLY (not
1912
2268
  # CheckRules, whose own `type_of` queries would otherwise re-trigger the choke-point).
1913
2269
  # `self_call_misses` feeds the `call.self-undefined-method` collector; the recorder is inert unless
@@ -1947,7 +2303,7 @@ module Rigor
1947
2303
  # output and the plugin diagnostics are then built from that single walk's results.
1948
2304
  def rule_and_plugin_diagnostics(path, parse_result, scope, index, self_call_misses)
1949
2305
  root = parse_result.value
1950
- node_collectors = CheckRules.build_node_collectors(path, index)
2306
+ node_collectors = CheckRules.build_node_collectors(path, index, root)
1951
2307
  node_results = node_rule_results_by_plugin(path, root, scope, node_collectors, index)
1952
2308
  diagnostics = CheckRules.diagnose(
1953
2309
  path: path,