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
@@ -0,0 +1,399 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+
5
+ require_relative "../cache/descriptor"
6
+ require_relative "template_unit_collector"
7
+ require_relative "template_unit_paths"
8
+ require_relative "../plugin/template_unit"
9
+ require_relative "../type/combinator"
10
+ require_relative "diagnostic"
11
+
12
+ module Rigor
13
+ module Analysis
14
+ # The run's **template units** — the engine side of the ADR-16 Tier-D seam #392 revived (design note
15
+ # `docs/design/20260816-effect-labels.md` § 11.3).
16
+ #
17
+ # Built ONCE, on the parent, before analysis: the loaded plugins' `template_globs:` are expanded, each
18
+ # match's bytes are handed to {Plugin::Base#template_units_for_file}, and what comes back is frozen into
19
+ # this index. Everything after that point is data — `Marshal`-clean Strings and Hashes — so a fork-pool
20
+ # worker analyses a unit from exactly the bytes the parent synthesised and the pooled and sequential
21
+ # paths cannot disagree about what the template said.
22
+ #
23
+ # ## Why the unit is analysed as a file rather than as a synthesised method
24
+ #
25
+ # Wrapping `ruby_source` in a `class … def … end` would shift every line, so the engine would have to
26
+ # compose the plugin's line map with an offset of its own, and a change to the wrapper would silently
27
+ # move every diagnostic. Instead the unit's Ruby is parsed **as-is**, under a per-file {Scope} whose
28
+ # `self_type`, locals and ivars the index seeds directly — the same seeding
29
+ # `StatementEvaluator#build_method_entry_scope` performs at a method boundary, applied at the file
30
+ # boundary. Line 12 of `ruby_source` is line 12 of the parse, and the ONLY mapping in play is the
31
+ # plugin's own.
32
+ #
33
+ # ## What a unit does not participate in
34
+ #
35
+ # `--incremental` dependents. A template unit is synthesised, not read, so nothing in the ADR-46
36
+ # dependency graph names it and an edit to a `.rb` file a unit calls into has no edge back. The
37
+ # conservative reading is taken and stated: **a run always re-analyses every template unit**, which
38
+ # costs a parse per template on the warm incremental path and can never serve a stale answer. Making
39
+ # units first-class dependents is a later slice's work, and needs a decision the design note does not
40
+ # settle.
41
+ #
42
+ # ## Carrying an index across runs (#1038)
43
+ #
44
+ # A long-lived {LanguageServer::ProjectContext} holds one warm {ProjectScan}, which carries the index it
45
+ # built, and every per-buffer publish hands it back through `collect(previous:)`. A path whose template
46
+ # has not moved on disk REUSES the unit already compiled for it, so a keystroke runs no plugin code —
47
+ # for ERB (#393) that is an Erubi compile of every view in the project, per keystroke, which is what
48
+ # this exists to remove. What is re-done per run is cheap and is what notices a change: the globs are
49
+ # re-expanded (only a glob notices a template APPEARING or vanishing) and each surviving path is
50
+ # revalidated through the ADR-87 stat-then-digest choke point ({Cache::FileDigest.stat_fresh?}), whose
51
+ # authority is the content digest, not the stat tuple. Three things rebuild the index outright, because
52
+ # each can change what a transform produces for bytes that never moved: a different root, a different
53
+ # set of claimed globs, and a different set of glob-claiming plugins.
54
+ #
55
+ # A fourth rebuilds one PLUGIN'S claim outright (#1047): any of that plugin's templates edited, added or
56
+ # deleted (except the deletion of a template that had produced no unit, which the carried rows cannot
57
+ # attribute to a plugin and which contributed nothing a sibling could read). A transform may read the
58
+ # plugin's other templates — rigor-actionpack seeds a partial's locals from the views that render it —
59
+ # so the freshness of `_card` alone cannot vouch for `_card`'s unit. The editor's buffer does not count
60
+ # as an edit, so this never costs a keystroke, and it bites only on an on-disk edit the owner has not
61
+ # invalidated for — a save already rebuilds cold. A template read with no unit (declined, or raised) is
62
+ # carried as a bare stat pack for exactly this decision, so an unchanged declined file is not mistaken
63
+ # for an edit.
64
+ #
65
+ # Separately, and whatever the rest of the index does, the editor's own buffer is never carried: a
66
+ # path the buffer is bound to is recompiled from the buffer's bytes on every publish, and the compiled
67
+ # result stays in THAT run's index — the warm index on the ProjectScan only ever holds units compiled
68
+ # from files on disk. A template the plugin DECLINES, or whose transform raised, likewise produces no
69
+ # unit to carry and is re-offered every run.
70
+ #
71
+ # A sequential CLI run passes no `previous:` and so builds the index from scratch exactly as before: it
72
+ # has no warm index to carry, its process ends with the run, and adding a cross-process memo would be a
73
+ # second cache to prove sound for no measurable gain (the index is built once per `rigor check`).
74
+ class TemplateUnits
75
+ # What a declared type name binds to when nothing resolves it. `Dynamic[top]` — the analyzer's own
76
+ # "a value is here and I cannot see its class".
77
+ DYNAMIC = Type::Combinator.dynamic(Type::Combinator.top)
78
+ private_constant :DYNAMIC
79
+
80
+ # One synthesised unit, flattened off the plugin's {Plugin::TemplateUnit} with its identity resolved.
81
+ #
82
+ # `path` is both the file the user wrote AND the logical path the engine analyses under: a diagnostic
83
+ # the parse produces already carries it, so nothing has to rewrite a path — only a LINE.
84
+ Entry = Data.define(:logical_name, :path, :source, :line_map, :self_type, :locals, :ivar_seeds,
85
+ :digest, :unit_key, :plugin_id, :suppressed_rules) do
86
+ # #393 — the per-unit rule posture. True when the plugin declared that this family of finding is
87
+ # not meaningful in its compiled output.
88
+ def suppresses?(rule)
89
+ return false if rule.nil? || suppressed_rules.empty?
90
+
91
+ suppressed_rules.any? { |prefix| rule.to_s.start_with?(prefix) }
92
+ end
93
+
94
+ # See {Plugin::TemplateUnit#template_line}. Re-implemented on the flattened entry so a worker never
95
+ # needs the plugin value object (or the plugin) to position a diagnostic.
96
+ def template_line(ruby_line)
97
+ return ruby_line if line_map.empty?
98
+ return line_map[ruby_line] if line_map.key?(ruby_line)
99
+
100
+ before = line_map.keys.select { |line| line < ruby_line }
101
+ before.empty? ? 1 : line_map[before.max]
102
+ end
103
+ end
104
+
105
+ # The index a run with no template-claiming plugin analyses under. Eagerly built on the main Ractor
106
+ # at load time (bottom of the class body) rather than memoised on first use: `@empty ||= new({})` is
107
+ # a class-ivar WRITE, which a non-main Ractor may not perform. {WorkerSession#initialize} reaches it
108
+ # whenever `template_units:` is not supplied, which is exactly how the Ractor backend constructs its
109
+ # workers — so the lazy memo killed every worker in its constructor and degraded every run (#1055).
110
+ class << self
111
+ attr_reader :empty
112
+ end
113
+
114
+ # Expands every loaded plugin's `template_globs:` and runs its transform. The work lives in
115
+ # {TemplateUnitCollector}, which owns the glob expansion, the failure isolation, the editor-buffer
116
+ # substitution and the #1038 carry; this class is what the rest of the run READS.
117
+ #
118
+ # @param previous — a warm index from an earlier run of the same project (#1038). Every unit in it
119
+ # whose template is unchanged on disk is carried over instead of recompiled; see the class note.
120
+ def self.collect(registry:, root: Dir.pwd, buffer: nil, previous: nil)
121
+ TemplateUnitCollector.collect(registry: registry, root: root, buffer: buffer, previous: previous)
122
+ end
123
+
124
+ # One template file a plugin claimed and did not deliver a usable unit for — a transform that raised,
125
+ # a file that could not be read, or a unit naming the wrong path. Reported as a `plugin_loader`
126
+ # `runtime-error` row by {Runner#template_unit_failure_diagnostics}, which is the isolation envelope
127
+ # every other plugin hook reports through (ADR-2 § "Plugin Trust and I/O Policy").
128
+ Failure = Data.define(:plugin_id, :path, :message)
129
+
130
+ def initialize(entries, failures: [], claimed_globs: [], root: Dir.pwd, stats: {},
131
+ plugin_signature: [])
132
+ @entries = entries.freeze
133
+ @failures = failures.freeze
134
+ # The globs the loaded plugins claimed, whether or not anything matched. They are the CACHE's
135
+ # business, not the analysis's: see {#glob_entries}.
136
+ @claimed_globs = claimed_globs.freeze
137
+ @root = root
138
+ # #1038 — the freshness token per template this index READ successfully: the ADR-87
139
+ # `(digest, size, mtime_ns, ctime_ns, inode)` pack over the bytes the transform was handed. Not part
140
+ # of the index's own identity ({#digest}) — a stat is not an answer, only the question of whether a
141
+ # carried answer still holds.
142
+ @stats = stats.freeze
143
+ @plugin_signature = plugin_signature.freeze
144
+ @declined_unit_keys = build_declined_unit_keys
145
+ freeze
146
+ end
147
+
148
+ attr_reader :failures
149
+
150
+ # #1038 — the `{ path => [entry, stat_pack] }` a later run may reuse, or `{}` when the whole index has
151
+ # to be rebuilt. The three whole-index refusals live here rather than per path because each of them
152
+ # can change what a transform PRODUCES for a template whose bytes never moved: a different root is a
153
+ # different project, a different claim set can hand a path to a different plugin, and a different
154
+ # plugin set is different code.
155
+ def carry_over(root:, claimed_globs:, plugin_signature:)
156
+ return {} unless @root.to_s == root.to_s
157
+ return {} unless @claimed_globs == claimed_globs
158
+ return {} unless @plugin_signature == plugin_signature
159
+
160
+ # A template that was READ but produced no unit (declined, or its transform raised) is carried as
161
+ # `[nil, pack]`: there is no unit to reuse, but its freshness still answers whether it CHANGED, which
162
+ # is what the collector's whole-claim decision needs (#1047) — without it one declined template
163
+ # would read as an edit on every run and cost the whole claim its carry.
164
+ @stats.each_with_object({}) do |(path, packed), carried|
165
+ carried[path] = [@entries[path], packed] if packed
166
+ end
167
+ end
168
+
169
+ def empty?
170
+ @entries.empty?
171
+ end
172
+
173
+ # #1065 — the `view:` keys of the templates this run READ and produced no unit for: an ERB whose
174
+ # compiled Ruby does not parse, and a handler the claiming plugin declines outright (a `.haml` view
175
+ # under a decline-only claim). They are what stops the effect edge's format fallback from resolving
176
+ # PAST a template that exists: Action View runs `_watchers.js.haml`, so joining `_watchers.html`'s
177
+ # effects there would be a label no execution produces. An unclaimed handler is invisible here and
178
+ # the fallback still fires for it, which is why a plugin claims the handlers it declines.
179
+ #
180
+ # The key is derived from the paths the plugin ITSELF keyed: a produced unit gives the root prefix
181
+ # its logical name was taken relative to (`app/views/` for `app/views/users/_card.html.erb` →
182
+ # `users/_card.html`), and a declined path under that prefix is keyed the same way, minus its
183
+ # handler. A run whose claim produced no unit at all learns no prefix and blocks nothing, which
184
+ # leaves the fallback exactly as it was rather than guessing at a layout the engine does not own.
185
+ attr_reader :declined_unit_keys
186
+
187
+ # The logical paths the run analyses on top of its `.rb` expansion, sorted.
188
+ def paths
189
+ @entries.keys.sort
190
+ end
191
+
192
+ def [](path)
193
+ @entries[normalize(path)]
194
+ end
195
+
196
+ def key?(path)
197
+ @entries.key?(normalize(path))
198
+ end
199
+
200
+ # `{ path => ruby_source }` — what {Runner#parse_source} and {WorkerSession#parse_source} read
201
+ # instead of the bytes on disk.
202
+ def sources
203
+ @entries.transform_values(&:source)
204
+ end
205
+
206
+ # The index's cache identity: every unit's digest (source bytes + transform id + synthesis version)
207
+ # keyed by path, plus every FAILURE keyed by path, hashed once. nil only when the run's plugins
208
+ # claimed no globs at all, so a project with no template-unit plugin perturbs no cache key.
209
+ #
210
+ # The failures are in it because a run that produced only failures still produced an ANSWER — one
211
+ # `plugin_loader` row per file — and without them that run's key equalled the no-templates key. A
212
+ # later run whose template now compiles, or whose template is gone, reconstructed the same key, and
213
+ # the ADR-87 boot-slim probe (which loads no plugin, so its slot is always absent) served the stale
214
+ # rows. The slot now exists whenever any plugin claimed a glob, which is exactly the condition under
215
+ # which the probe's key is knowingly unreconstructable.
216
+ def build_declined_unit_keys
217
+ declined = @stats.keys - @entries.keys
218
+ return NO_DECLINED_KEYS if declined.empty?
219
+
220
+ prefixes = @entries.filter_map { |path, entry| root_prefix(path, entry.logical_name) }.uniq
221
+ return NO_DECLINED_KEYS if prefixes.empty?
222
+
223
+ declined.filter_map { |path| declined_unit_key(path, prefixes) }.uniq.sort.freeze
224
+ end
225
+ private :build_declined_unit_keys
226
+
227
+ NO_DECLINED_KEYS = [].freeze
228
+ private_constant :NO_DECLINED_KEYS
229
+
230
+ # The directory a plugin took a logical name relative to, or nil when the path and the name do not
231
+ # agree — a plugin free to key a unit any way it likes is one this derivation must decline for.
232
+ def root_prefix(path, logical_name)
233
+ stem = strip_handler(path)
234
+ stem.end_with?(logical_name) ? stem.delete_suffix(logical_name) : nil
235
+ end
236
+ private :root_prefix
237
+
238
+ def declined_unit_key(path, prefixes)
239
+ stem = strip_handler(path)
240
+ prefix = prefixes.find { |candidate| stem.start_with?(candidate) && stem.length > candidate.length }
241
+ prefix.nil? ? nil : "#{Plugin::TemplateUnit::KEY_PREFIX}#{stem.delete_prefix(prefix)}"
242
+ end
243
+ private :declined_unit_key
244
+
245
+ # `app/views/users/_card.html.erb` → `app/views/users/_card.html`. A template's logical name never
246
+ # carries its handler, which is the one naming rule the seam itself states.
247
+ def strip_handler(path)
248
+ path.sub(%r{\.[^./]+\z}, "")
249
+ end
250
+ private :strip_handler
251
+
252
+ def digest
253
+ return nil if @claimed_globs.empty?
254
+
255
+ rows = @entries.keys.sort.map { |path| "unit\x00#{path}\x00#{@entries[path].digest}" } +
256
+ @failures.map { |failure| "fail\x00#{failure.path}\x00#{failure.message}" }.sort
257
+ Digest::SHA256.hexdigest((["globs\x00#{@claimed_globs.sort.join("\x00")}"] + rows).join("\n"))
258
+ end
259
+
260
+ # The ADR-60 WD3 / #979 `:names` glob rows the run's dependency descriptor records: one per claimed
261
+ # pattern, whether or not it matched.
262
+ #
263
+ # Without them a template APPEARING under a claimed glob moved nothing a warm run could see — the
264
+ # descriptor listed only the files that already existed — so a project whose first template was added
265
+ # between two runs kept replaying the answer computed before it existed. `:names` rather than `:stat`
266
+ # for the same reason the signature roots use it (`cache.md` § the run-descriptor row inventory): the
267
+ # question a glob row adds is which files MATCH, and edits to those files are carried by their own
268
+ # file rows.
269
+ def glob_entries
270
+ @claimed_globs.map do |pattern|
271
+ Cache::Descriptor::GlobEntry.compute(root: @root.to_s, pattern: pattern, mode: :names)
272
+ end
273
+ end
274
+
275
+ # Every template file this run read — the ones that produced a unit AND the ones that failed. A
276
+ # failure's file is as much an input to the run's answer as a success's: the `plugin_loader` row it
277
+ # produced must not outlive the edit that fixes the template.
278
+ def source_paths
279
+ (@entries.keys + @failures.map(&:path)).uniq.sort
280
+ end
281
+
282
+ # The Prism `scopes:` argument for a unit's parse, or nil for a path that is not one.
283
+ #
284
+ # Without it the seeded locals are inert: Prism parses a bare identifier with no assignment in sight
285
+ # as a **method call**, so `size` in a template was a `CallNode` and `Scope#local(:size)` was never
286
+ # consulted. Declaring the render site's locals as an enclosing scope is exactly what Rails does when
287
+ # it compiles a partial's locals into the method's parameters, and it is what makes `locals:` mean
288
+ # anything.
289
+ def parse_scopes(path)
290
+ entry = @entries[normalize(path)]
291
+ return nil if entry.nil? || entry.locals.empty?
292
+
293
+ [entry.locals.keys.map(&:to_sym)]
294
+ end
295
+
296
+ # The `view:<logical_name>` effect-unit key for a path, or nil.
297
+ # The file whose bytes a unit's path was READ from: the editor's buffer when one is bound to it, the
298
+ # project file otherwise. Read by the run's dependency descriptor, so it digests what the run read.
299
+ def physical_path(path, buffer)
300
+ TemplateUnitCollector.physical_path(path, @root, buffer)
301
+ end
302
+
303
+ def unit_key_for(path)
304
+ @entries[normalize(path)]&.unit_key
305
+ end
306
+
307
+ # Binds the declared `self`, locals and ivar seeds onto the per-file scope, so the unit's body types
308
+ # as the render site would run it.
309
+ #
310
+ # A type name that resolves to nothing is bound `Dynamic` rather than guessed OR left unbound, and
311
+ # the difference matters most for `self_type:`. Leaving it unbound is not "no claim": it is the claim
312
+ # that the body runs at top level, so every helper call in the template reports
313
+ # `call.unresolved-toplevel` — a finding per line, caused by the plugin naming a class whose RBS the
314
+ # project does not ship (`ActionView::Base`, on the very first Rails app to try this). `Dynamic` is
315
+ # the honest reading of "a receiver is declared and the analyzer cannot see it" (ADR-5), and it is
316
+ # silent.
317
+ def seed(scope, path)
318
+ entry = @entries[normalize(path)]
319
+ return scope if entry.nil?
320
+
321
+ scope = bind_self(scope, entry)
322
+ entry.locals.each do |name, type_name|
323
+ scope = scope.with_local(name.to_sym, resolve(scope, type_name))
324
+ end
325
+ entry.ivar_seeds.each do |name, type_name|
326
+ scope = scope.with_ivar(name.to_sym, resolve(scope, type_name))
327
+ end
328
+ scope
329
+ end
330
+
331
+ # Re-points a diagnostic produced inside a unit at the template's own line, and drops the ones the
332
+ # unit's plugin declared it does not report (#393). Every other diagnostic passes through untouched,
333
+ # so it is safe to stamp a whole run's stream through this.
334
+ #
335
+ # The suppression is per unit rather than global because it is a claim about ONE compiler's output:
336
+ # a plugin whose synthesised receivers are still coarse suppresses `call.` so the view layer can be
337
+ # analysed for its EFFECTS — which need no receiver precision — without the typing half costing the
338
+ # project a diagnostic per template line. It is deliberately not a `disable:` entry: `disable:` would
339
+ # silence the rule in the project's `.rb` files too, which is the opposite of what is wanted.
340
+ def remap(diagnostics)
341
+ return diagnostics if @entries.empty?
342
+
343
+ diagnostics.filter_map do |diagnostic|
344
+ entry = @entries[normalize(diagnostic.path)]
345
+ next diagnostic if entry.nil?
346
+ next nil if entry.suppresses?(diagnostic.rule)
347
+ next diagnostic if entry.line_map.empty?
348
+
349
+ relocate(diagnostic, entry.template_line(diagnostic.line))
350
+ end
351
+ end
352
+
353
+ # The template's lines and the compiled Ruby's columns do not correspond — a compiler preserves lines
354
+ # and rewrites the text of each (Erubi's documented property, and the reason a Rails backtrace can
355
+ # name `show.html.erb:12`). So a diagnostic from a unit keeps its line and drops to column 1 rather
356
+ # than pointing at a column of a file whose bytes are not what was analysed.
357
+ #
358
+ # Applied whenever the unit carries a map, NOT only when the line moves: a compiler that happens to
359
+ # leave a line where it was still rewrote that line's text, and ERB is exactly that case — the map is
360
+ # near-identity and the columns are meaningless anyway.
361
+ def relocate(diagnostic, line)
362
+ Diagnostic.new(
363
+ path: diagnostic.path, line: line, column: 1, message: diagnostic.message,
364
+ severity: diagnostic.severity, rule: diagnostic.rule, source_family: diagnostic.source_family,
365
+ receiver_type: diagnostic.receiver_type, method_name: diagnostic.method_name,
366
+ project_definition_site: diagnostic.project_definition_site
367
+ )
368
+ end
369
+
370
+ private
371
+
372
+ def normalize(path)
373
+ TemplateUnitPaths.relative(path, @root)
374
+ end
375
+
376
+ def bind_self(scope, entry)
377
+ return scope if entry.self_type.nil?
378
+
379
+ scope.with_self_type(resolve(scope, entry.self_type))
380
+ end
381
+
382
+ def resolve(scope, type_name)
383
+ environment = scope.environment
384
+ resolved = environment&.nominal_for_name(type_name)
385
+ return resolved if resolved
386
+ return DYNAMIC unless scope.discovered_classes.key?(type_name)
387
+
388
+ Type::Combinator.nominal_of(type_name)
389
+ end
390
+
391
+ # Populates the `@empty` singleton on the main Ractor at load time. `Ractor.make_shareable` rather
392
+ # than `freeze` so a worker's READ of the class ivar is legal too: a non-main Ractor may read a
393
+ # class/module ivar only when the value is deeply shareable, and `#initialize` freezes the index
394
+ # itself but leaves `@root` — a fresh `Dir.pwd` String — unfrozen. The baked root is inert here: an
395
+ # index with no entries and no claimed globs answers nothing and carries nothing over.
396
+ @empty = Ractor.make_shareable(new({}))
397
+ end
398
+ end
399
+ end
@@ -3,6 +3,7 @@
3
3
  require "prism"
4
4
 
5
5
  require_relative "../environment"
6
+ require_relative "../project_environment"
6
7
  require_relative "../scope"
7
8
  require_relative "../cache/store"
8
9
  require_relative "../plugin"
@@ -21,6 +22,7 @@ require_relative "dependency_recorder"
21
22
  require_relative "dependency_source_inference"
22
23
  require_relative "diagnostic"
23
24
  require_relative "erb_template_detector"
25
+ require_relative "template_units"
24
26
 
25
27
  module Rigor
26
28
  module Analysis
@@ -73,17 +75,17 @@ module Rigor
73
75
  :rbs_extended_reporter, :boundary_cross_reporter,
74
76
  :prepare_diagnostics
75
77
 
76
- # @param configuration [Rigor::Configuration]
77
- # @param cache_store [Rigor::Cache::Store, nil] persistent cache the session exposes to plugin-side
78
+ # @param cache_store — persistent cache the session exposes to plugin-side
78
79
  # producers and the RBS loader. Pass `nil` to disable caching.
79
- # @param plugin_blueprints [Array<Rigor::Plugin::Blueprint>] replay descriptors. Empty array yields a
80
+ # @param plugin_blueprints — replay descriptors. Empty array yields a
80
81
  # session with no plugin contributions.
81
- # @param explain [Boolean] when true, `#analyze` additionally emits one `:info` `fallback` diagnostic
82
+ # @param explain — when true, `#analyze` additionally emits one `:info` `fallback` diagnostic
82
83
  # per directly-unrecognised node, mirroring {Rigor::Analysis::Runner#explain_diagnostics}.
83
84
  def initialize(configuration:, cache_store: nil, # rubocop:disable Metrics/MethodLength,Metrics/ParameterLists
84
85
  plugin_blueprints: [], explain: false, buffer: nil,
85
86
  synthetic_method_index: nil, project_patched_methods: nil,
86
- project_scope_seed: {}, source_files: [], record_dependencies: false)
87
+ project_scope_seed: {}, source_files: [], record_dependencies: false,
88
+ template_units: nil, locked_gems: nil)
87
89
  @configuration = configuration
88
90
  @cache_store = cache_store
89
91
  @explain = explain
@@ -108,6 +110,15 @@ module Rigor
108
110
  # ADR-32 WD4 — full project file list (frozen Array<String>) for env-build-time invocation of any
109
111
  # loaded plugin's `source_rbs_synthesizer` callable.
110
112
  @source_files = source_files
113
+ # #392 — the run's template units, synthesised on the PARENT before the fork and inherited here as
114
+ # frozen data. A worker analyses a unit from the same bytes the parent compiled, which is what makes
115
+ # pooled and sequential agree about a file that does not exist on disk in the form being parsed.
116
+ @template_units = template_units || TemplateUnits.empty
117
+ # #1064 — the project's `Gemfile.lock` map, resolved by the PARENT. Bundler's lockfile parser reads
118
+ # RubyGems module state a non-main Ractor may not touch, so a Ractor worker that resolved it here got
119
+ # `Ractor::IsolationError` and an empty map — no gem `sig/` directories, no ADR-72 overlays. nil (the
120
+ # fork backend and every other caller) resolves inside `Environment.for_project` exactly as before.
121
+ @locked_gems = locked_gems
111
122
 
112
123
  # NOTE: `Inference::MethodDispatcher::FileFolding.fold_platform_specific_paths` is process-global
113
124
  # state. Writing it from a non-main Ractor would raise `Ractor::IsolationError`, so the session does
@@ -137,14 +148,11 @@ module Rigor
137
148
  rbs_extended_reporter: @rbs_extended_reporter,
138
149
  boundary_cross_reporter: @boundary_cross_reporter,
139
150
  source_rbs_synthesis_reporter: @source_rbs_synthesis_reporter,
140
- bundler_bundle_path: configuration.bundler_bundle_path,
141
- bundler_auto_detect: configuration.bundler_auto_detect,
142
- bundler_lockfile: configuration.bundler_lockfile,
143
- rbs_collection_lockfile: configuration.rbs_collection_lockfile,
144
- rbs_collection_auto_detect: configuration.rbs_collection_auto_detect,
151
+ **ProjectEnvironment.dependency_discovery_options(configuration),
145
152
  synthetic_method_index: @synthetic_method_index,
146
153
  project_patched_methods: @project_patched_methods,
147
- source_files: @source_files
154
+ source_files: @source_files,
155
+ locked_gems: @locked_gems
148
156
  )
149
157
  @prepare_diagnostics = run_plugin_prepare.freeze
150
158
  end
@@ -168,10 +176,12 @@ module Rigor
168
176
  def analyze_with_effects(path)
169
177
  return analyze_body(path) unless @record_effects
170
178
 
179
+ entry = @template_units[path]
171
180
  diagnostics = nil
172
181
  collection = Effects::Collector.collect_for(
173
182
  path, attribution: @effect_attribution, envelopes: effect_envelope_index,
174
- plugin_facts: effect_plugin_facts
183
+ plugin_facts: effect_plugin_facts, unit_key: entry&.unit_key,
184
+ unit_owner: entry&.self_type
175
185
  ) do
176
186
  diagnostics = analyze_body(path)
177
187
  end
@@ -210,21 +220,24 @@ module Rigor
210
220
  return parse_diagnostics(path, parse_result)
211
221
  end
212
222
 
213
- Effects::Collector.record_root(parse_result.value)
214
- scope = seed_project_scope(Scope.empty(environment: @environment, source_path: path))
215
- index = Inference::ScopeIndexer.index(parse_result.value, default_scope: scope)
223
+ root = parse_result.value
224
+ Effects::Collector.record_root(root)
225
+ scope = @template_units.seed(
226
+ seed_project_scope(Scope.empty(environment: @environment, source_path: path)), path
227
+ )
228
+ index = Inference::ScopeIndexer.index(root, default_scope: scope)
216
229
  # ADR-53 B4 — built-in collectors + plugin node rules share one walk.
217
- node_collectors = CheckRules.build_node_collectors(path, index)
218
- node_results = node_rule_results_by_plugin(path, parse_result.value, scope, node_collectors, index)
230
+ node_collectors = CheckRules.build_node_collectors(path, index, root)
231
+ node_results = node_rule_results_by_plugin(path, root, scope, node_collectors, index)
219
232
  diagnostics = CheckRules.diagnose(
220
233
  path: path,
221
- root: parse_result.value,
234
+ root: root,
222
235
  scope_index: index,
223
236
  comments: parse_result.comments,
224
237
  disabled_rules: @configuration.disabled_rules,
225
238
  node_collectors: node_collectors
226
239
  )
227
- diagnostics += plugin_emitted_diagnostics(path, parse_result.value, scope, node_results)
240
+ diagnostics += plugin_emitted_diagnostics(path, root, scope, node_results)
228
241
  diagnostics + explain_diagnostics(path, parse_result.value, scope)
229
242
  rescue Errno::ENOENT => e
230
243
  [analyzer_error(path, e.message)]
@@ -271,7 +284,10 @@ module Rigor
271
284
  # `hkt_scan_failure` does: the directives are read by the registry scan, and under the pool the
272
285
  # PARENT never demands that scan, so a diagnostic wired off the parent's reporter would appear
273
286
  # at `--workers=0` and vanish at `--workers=N`. The demand above is what fills this.
274
- hkt_directive_errors: @rbs_extended_reporter.hkt_directive_errors
287
+ hkt_directive_errors: @rbs_extended_reporter.hkt_directive_errors,
288
+ # ADR-109 WD3 — the deprecated-form stream rides the same channel: the payload is read where
289
+ # the annotated method is dispatched, which under the pool is only ever inside a worker.
290
+ deprecated_forms: @rbs_extended_reporter.deprecated_forms
275
291
  },
276
292
  boundary_cross: @boundary_cross_reporter.entries,
277
293
  source_rbs_synthesis: @source_rbs_synthesis_reporter.entries,
@@ -296,6 +312,26 @@ module Rigor
296
312
  @file_effects
297
313
  end
298
314
 
315
+ # Issue #1051 — the run-scoped disclosures ({Plugin::Base#disclose_once}) this session's plugin
316
+ # instances registered, each tagged with the plugin id the coordinator de-duplicates by — including the
317
+ # positioned batches of {Plugin::Base#emit_once} (#1060), which ride the same records. Marshal-clean
318
+ # (frozen Hashes of Strings and Symbols, and frozen `Diagnostic` copies for a batch) so the fork pool
319
+ # ships them back like the three drains above;
320
+ # a registration made on the PARENT during `#prepare` is already visible to the coordinator through
321
+ # the pre-fork session, and one a worker makes while analysing its slice arrives through the payload.
322
+ # Entries are not cleared — a drain is a read, and the parent session is drained again on the degrade
323
+ # path.
324
+ def drain_run_disclosures
325
+ return [] if @plugin_registry.empty?
326
+
327
+ @plugin_registry.plugins.flat_map do |plugin|
328
+ plugin_id = safe_plugin_id(plugin)
329
+ Array(plugin.run_disclosure_records).map { |record| record.merge(plugin_id: plugin_id) }
330
+ rescue StandardError
331
+ []
332
+ end
333
+ end
334
+
299
335
  private
300
336
 
301
337
  # Mirrors {Runner#seed_project_scope}: applies the cross-file pre-pass discovery tables the
@@ -311,6 +347,12 @@ module Rigor
311
347
  # physical bytes but stamp the parse buffer's `filepath:` as the LOGICAL path so downstream
312
348
  # diagnostics carry the logical path.
313
349
  def parse_source(path)
350
+ entry = @template_units[path]
351
+ if entry
352
+ return Prism.parse(entry.source, filepath: path, version: @configuration.target_ruby,
353
+ scopes: @template_units.parse_scopes(path))
354
+ end
355
+
314
356
  physical = @buffer ? @buffer.resolve(path) : path
315
357
  return Prism.parse_file(physical, version: @configuration.target_ruby) if physical == path
316
358