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,282 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "prism"
4
+
5
+ require_relative "../source/node_walker"
6
+ require_relative "captured_locals"
7
+
8
+ module Rigor
9
+ module Inference
10
+ # The names of a {CapturedLocals.writes} set that a block body rebinds somewhere `StatementEvaluator` does
11
+ # not carry into the body's exit scope.
12
+ #
13
+ # The per-element fold's issue #587 (b) fixpoint reads each pass's exit binding out of that evaluator, so a
14
+ # rebind the exit never sees leaves the fixpoint on a binding the program has already moved past, and every
15
+ # position of the fold re-reads it. Issue #617's unmoved-pin floor caught such a name only while nothing else
16
+ # moved its binding: `seen = nil; [1, 2].find { |e| seen ||= 0; (seen += 1) == 2 }` threads the `||=`, so the
17
+ # fixpoint left the seed, and every predicate read `0 + 1 == 2`. A name this scan returns takes the floor
18
+ # whatever the fixpoint converged to.
19
+ #
20
+ # The scan admits a write from these positions only, each one the evaluator threads, measured against it
21
+ # rather than read off it:
22
+ #
23
+ # - a statement, and the body of `(…)`, `begin` / `rescue` / `else` / `ensure`;
24
+ # - an assignment's value — every variable-write form, and an index `||=` / `&&=` / `op=`, but not its
25
+ # receiver or index arguments;
26
+ # - an `if` / `unless` / `case` predicate and every branch, but not a `when` condition or an `in` guard;
27
+ # - both operands of `&&` / `||`;
28
+ # - a `while` / `until` predicate and body, a `for` index, collection and body;
29
+ # - a multi-assign value and its local and instance-variable targets, and the LOCAL a `rescue => e`
30
+ # reference, a `for` index, a pattern capture or `=~`'s named captures binds — the evaluator binds no
31
+ # class variable or global through any of them, and no instance variable outside a multi-assign;
32
+ # - a nested block or lambda body, for a local or an instance variable: the call's write-back fixpoint, which
33
+ # joins the block's `next` and `break` paths, or the escaping-block floor covers both, and nothing covers
34
+ # a class variable or global there.
35
+ #
36
+ # Every other position counts as unthreaded: a call's receiver and arguments, an array or hash literal, an
37
+ # interpolation, a `return` / `next` / `break` value, `rescue` modifier, `self.w =` (which rebinds `@w` with
38
+ # no write node at all), a `def` or class body. The list is a whitelist, so a position the evaluator gains
39
+ # later floors a name needlessly rather than letting a pin through. Issue #1223 is such a gain: the evaluator
40
+ # threads a call's receiver and arguments, the literals, the interpolations and a `rescue` modifier
41
+ # ({StatementEvaluator#eval_call}), and the scan does not admit them yet.
42
+ #
43
+ # A threaded rebind can still miss the exit binding when a jump leaves after it. The fold's pass and a
44
+ # nested block's write-back join every block-level `next` (and the write-back every `break`) into the exit
45
+ # ({StatementEvaluator#evaluate_invocation}), but a `while` / `until` / `for` loop's continuation joins its
46
+ # `next` and `break` paths only in part ({StatementEvaluator#loop_iteration}): a `for` body runs one pass,
47
+ # and a name outside the `while` / `until` rebind fixpoint (an instance variable) sees the first pass's
48
+ # `next` exits and no `break` — so the rebind such a branch made can still drop out — and `redo` / `retry`
49
+ # re-enter code with bindings no join sees. A rebind that textually precedes such a jump is therefore
50
+ # unthreaded too; the body runs forward-only, so a rebind after every jump cannot be on a jump's path, and
51
+ # a jump that is its construct's final statement leaves with the exit scope itself. A rebind in an `ensure`
52
+ # runs after any jump in its `begin`, so under any such jump it counts as preceding one.
53
+ module UnthreadedRebinds
54
+ VARIABLE_WRITE_NODES = (CapturedLocals::LOCAL_WRITE_NODES | CapturedLocals::NON_LOCAL_WRITE_NODES).freeze
55
+ private_constant :VARIABLE_WRITE_NODES
56
+
57
+ TARGET_NODES = Set[
58
+ Prism::LocalVariableTargetNode, Prism::InstanceVariableTargetNode,
59
+ Prism::ClassVariableTargetNode, Prism::GlobalVariableTargetNode
60
+ ].freeze
61
+ private_constant :TARGET_NODES
62
+
63
+ VALUE_ONLY = { value: :code }.freeze
64
+ private_constant :VALUE_ONLY
65
+
66
+ # Parent class => the child fields the evaluator threads, each with how it is entered: `:code` threads
67
+ # writes, `:binding` threads only the targets the construct binds ({#binding_target_threaded?}),
68
+ # `:ensure` is an `ensure` body, `:loop` a loop body, `:nested` a call's block and `:lambda` a lambda's
69
+ # body.
70
+ THREADED_FIELDS = {
71
+ Prism::StatementsNode => { body: :code },
72
+ Prism::ParenthesesNode => { body: :code },
73
+ Prism::BeginNode => { statements: :code, rescue_clause: :code, else_clause: :code, ensure_clause: :code },
74
+ Prism::RescueNode => { reference: :binding, statements: :code, subsequent: :code },
75
+ Prism::EnsureNode => { statements: :ensure },
76
+ Prism::ElseNode => { statements: :code },
77
+ Prism::IfNode => { predicate: :code, statements: :code, subsequent: :code },
78
+ Prism::UnlessNode => { predicate: :code, statements: :code, else_clause: :code },
79
+ Prism::CaseNode => { predicate: :code, conditions: :code, else_clause: :code },
80
+ Prism::WhenNode => { statements: :code },
81
+ Prism::CaseMatchNode => { predicate: :code, conditions: :code, else_clause: :code },
82
+ Prism::InNode => { pattern: :binding, statements: :code },
83
+ Prism::AndNode => { left: :code, right: :code },
84
+ Prism::OrNode => { left: :code, right: :code },
85
+ Prism::MatchPredicateNode => { value: :code, pattern: :binding },
86
+ Prism::MatchRequiredNode => { value: :code, pattern: :binding },
87
+ Prism::MatchWriteNode => { targets: :binding },
88
+ Prism::MultiWriteNode => { lefts: :binding, rest: :binding, rights: :binding, value: :code },
89
+ Prism::WhileNode => { predicate: :code, statements: :loop },
90
+ Prism::UntilNode => { predicate: :code, statements: :loop },
91
+ Prism::ForNode => { index: :binding, collection: :code, statements: :loop },
92
+ Prism::CallNode => { block: :nested },
93
+ Prism::BlockNode => { body: :code },
94
+ Prism::LambdaNode => { body: :lambda },
95
+ Prism::IndexOrWriteNode => VALUE_ONLY,
96
+ Prism::IndexAndWriteNode => VALUE_ONLY,
97
+ Prism::IndexOperatorWriteNode => VALUE_ONLY,
98
+ **(VARIABLE_WRITE_NODES - TARGET_NODES).to_h { |klass| [klass, VALUE_ONLY] }
99
+ }.transform_values(&:freeze).freeze
100
+ private_constant :THREADED_FIELDS
101
+
102
+ # The jumps whose path the exit binding misses, per construct. A block's `next` and `break` are joined
103
+ # ({StatementEvaluator#evaluate_invocation}, the write-back), and a `break` out of the fold's own call ends
104
+ # it, so no later iteration reads what it carried; `redo` and `retry` are joined nowhere. A loop joins
105
+ # its `next` and `break` paths only in part, so both still count.
106
+ BLOCK_JUMPS = Set[Prism::RedoNode, Prism::RetryNode].freeze
107
+ LOOP_JUMPS = (BLOCK_JUMPS | [Prism::NextNode, Prism::BreakNode]).freeze
108
+ private_constant :BLOCK_JUMPS, :LOOP_JUMPS
109
+
110
+ # The constructs that own the jumps inside them.
111
+ JUMP_BOUNDARY_NODES = Set[
112
+ Prism::BlockNode, Prism::LambdaNode, Prism::DefNode,
113
+ Prism::WhileNode, Prism::UntilNode, Prism::ForNode
114
+ ].freeze
115
+ private_constant :JUMP_BOUNDARY_NODES
116
+
117
+ NO_JUMP = -1
118
+ private_constant :NO_JUMP
119
+
120
+ EMPTY = Set.new.freeze
121
+ private_constant :EMPTY
122
+
123
+ # How a walk position reaches the exit scope: whether a local rebind there is threaded, whether an
124
+ # instance variable's is, whether a class variable's or global's is, the offset below which a rebind
125
+ # precedes a jump,
126
+ # whether the position binds targets only (`:multi_assign` under a multi-assign, `:capture` under any
127
+ # other binding construct, false otherwise), and the locals a nested block shadows.
128
+ Position = Data.define(:local, :ivar, :other, :horizon, :binding, :shadowed)
129
+ private_constant :Position
130
+
131
+ module_function
132
+
133
+ # @param names — the rebound names from {CapturedLocals.writes}, sigil and all.
134
+ # @return the subset of `names` the body rebinds somewhere the exit scope misses.
135
+ def names(block_node, names)
136
+ body = block_node.body
137
+ return EMPTY if body.nil? || names.empty?
138
+
139
+ wanted = names.to_set
140
+ found = Set.new
141
+ horizon = last_jump_offset(body, BLOCK_JUMPS)
142
+ start = Position.new(local: true, ivar: true, other: true, horizon: horizon, binding: false, shadowed: EMPTY)
143
+ visit(body, start, wanted, found)
144
+ found
145
+ end
146
+
147
+ def visit(node, position, wanted, found)
148
+ return unless node.is_a?(Prism::Node)
149
+ return if node.is_a?(Prism::DefinedNode)
150
+
151
+ check(node, position, wanted, found)
152
+ return node.rigor_each_child { |child| visit(child, position, wanted, found) } if position.binding
153
+
154
+ fields = THREADED_FIELDS[node.class]
155
+ return node.rigor_each_child { |child| visit_unthreaded(child, wanted, found) } if fields.nil?
156
+
157
+ threaded = threaded_children(node, fields)
158
+ node.rigor_each_child do |child|
159
+ mode = threaded[child]
160
+ if mode
161
+ visit(child, enter(node, child, mode, position), wanted, found)
162
+ else
163
+ visit_unthreaded(child, wanted, found)
164
+ end
165
+ end
166
+ end
167
+
168
+ def threaded_children(node, fields)
169
+ threaded = {}.compare_by_identity
170
+ fields.each do |field, mode|
171
+ value = node.public_send(field)
172
+ if value.is_a?(Array)
173
+ value.each { |child| threaded[child] = mode }
174
+ elsif value
175
+ threaded[value] = mode
176
+ end
177
+ end
178
+ threaded
179
+ end
180
+
181
+ # Every rebind under an unthreaded position misses the exit scope, whatever the position beneath it.
182
+ def visit_unthreaded(node, wanted, found)
183
+ Source::NodeWalker.each(node) do |descendant|
184
+ name = rebound_name(descendant)
185
+ found << name if name && wanted.include?(name)
186
+ end
187
+ end
188
+
189
+ def enter(parent, child, mode, position)
190
+ case mode
191
+ when :binding then position.with(binding: parent.is_a?(Prism::MultiWriteNode) ? :multi_assign : :capture)
192
+ when :ensure then position.with(horizon: position.horizon == NO_JUMP ? NO_JUMP : Float::INFINITY)
193
+ when :loop then extend_horizon(position, child, LOOP_JUMPS)
194
+ when :nested then nested_block(child, position)
195
+ when :lambda then nested_body(parent, child, position)
196
+ else position
197
+ end
198
+ end
199
+
200
+ def extend_horizon(position, body, kinds)
201
+ position.with(horizon: [position.horizon, last_jump_offset(body, kinds)].max)
202
+ end
203
+
204
+ # A call's block. A block-pass argument (`&blk`) carries no body the call's write-back reads.
205
+ def nested_block(child, position)
206
+ return position.with(local: false, ivar: false, other: false) unless child.is_a?(Prism::BlockNode)
207
+
208
+ nested_body(child, child.body, position)
209
+ end
210
+
211
+ # A block or lambda body: its parameters and block-locals shadow, its own jumps join the horizon, and a
212
+ # class variable's or global's rebind is no longer carried out.
213
+ def nested_body(owner, body, position)
214
+ extend_horizon(position, body, BLOCK_JUMPS).with(
215
+ other: false, shadowed: position.shadowed | CapturedLocals.introduced_locals(owner)
216
+ )
217
+ end
218
+
219
+ def check(node, position, wanted, found)
220
+ name = rebound_name(node)
221
+ return if name.nil? || !wanted.include?(name)
222
+ return if position.shadowed.include?(name)
223
+
224
+ found << name unless threaded_rebind?(node, name, position)
225
+ end
226
+
227
+ def threaded_rebind?(node, name, position)
228
+ return false unless VARIABLE_WRITE_NODES.include?(node.class)
229
+ return false unless TARGET_NODES.include?(node.class) == (position.binding ? true : false)
230
+
231
+ kind = CapturedLocals.variable_kind(name)
232
+ return false if position.binding && !binding_target_threaded?(kind, position.binding)
233
+ return false unless kind_threaded?(kind, position)
234
+
235
+ node.location.start_offset >= position.horizon
236
+ end
237
+
238
+ def kind_threaded?(kind, position)
239
+ case kind
240
+ when :local then position.local
241
+ when :ivar then position.ivar
242
+ else position.other
243
+ end
244
+ end
245
+
246
+ # A binding construct's target reaches the exit scope only for the kinds the evaluator binds there: a
247
+ # multi-assign binds locals and instance variables, and a `rescue =>` reference, a `for` index, a pattern
248
+ # capture or a named capture binds locals only. `a, $g = 1, :z` leaves `$g` where it was.
249
+ def binding_target_threaded?(kind, binding)
250
+ kind == :local || (kind == :ivar && binding == :multi_assign)
251
+ end
252
+
253
+ def rebound_name(node)
254
+ return node.name if VARIABLE_WRITE_NODES.include?(node.class)
255
+
256
+ CapturedLocals.setter_ivar_name(node)
257
+ end
258
+
259
+ # The start offset of the last `kinds` jump that belongs to the construct whose body is `body`, or
260
+ # {NO_JUMP}. A jump that is the body's final statement is skipped: it leaves with the exit scope itself.
261
+ def last_jump_offset(body, kinds)
262
+ return NO_JUMP if body.nil?
263
+
264
+ final = body.body.last if body.is_a?(Prism::StatementsNode)
265
+ offset = NO_JUMP
266
+ each_owned_jump(body, kinds) do |jump|
267
+ offset = [offset, jump.location.start_offset].max unless jump.equal?(final)
268
+ end
269
+ offset
270
+ end
271
+
272
+ def each_owned_jump(node, kinds, &)
273
+ yield node if kinds.include?(node.class)
274
+ return if node.is_a?(Prism::DefinedNode)
275
+
276
+ node.rigor_each_child do |child|
277
+ each_owned_jump(child, kinds, &) unless JUMP_BOUNDARY_NODES.include?(child.class)
278
+ end
279
+ end
280
+ end
281
+ end
282
+ end
@@ -48,6 +48,8 @@ module Rigor
48
48
  # with the running Ruby**, where "the analyzer's Ruby is the target's Ruby" already covers them.
49
49
  # A third-party gem's `VERSION` is not read: its version is the project's `Gemfile.lock`'s to say,
50
50
  # and the lockfile is not visible from the inference layer.
51
+ # * The ROOTED spelling of any of the above — `::RUBY_VERSION`, `::RUBY_ENGINE`, `::Psych::VERSION` —
52
+ # names the same constant as its bare twin and folds identically; see {.read_constant}.
51
53
  # * `RUBY_PLATFORM` — **never** folded. Every comparison against it is by construction
52
54
  # platform-dependent, and the machine running `rigor` need not be the machine running the program.
53
55
  # * `<=>` — never folded (it yields -1/0/1, not a branch verdict), and neither are `defined?(Ractor)`
@@ -68,7 +70,7 @@ module Rigor
68
70
 
69
71
  # Predefined constants read from the analyzer's runtime, mapped to the operand kind they produce.
70
72
  # `:string` operands accept every comparison; `:engine` operands accept equality only.
71
- PREDEFINED = { RUBY_VERSION: :string, RUBY_ENGINE: :engine }.freeze
73
+ PREDEFINED = { "RUBY_VERSION" => :string, "RUBY_ENGINE" => :engine }.freeze
72
74
  private_constant :PREDEFINED
73
75
 
74
76
  # Operand kinds that carry a plain Ruby String, i.e. the ones `Gem::Version.new` may wrap and the
@@ -88,8 +90,8 @@ module Rigor
88
90
  GEM_VERSION_PATH = "Gem::Version"
89
91
  private_constant :GEM_VERSION_PATH
90
92
 
91
- # @param node [Prism::Node, nil] an `if` / `unless` predicate
92
- # @return [Symbol, nil] `:truthy` / `:falsey` when the guard is decidable on the analyzer's Ruby,
93
+ # @param node — an `if` / `unless` predicate
94
+ # @return `:truthy` / `:falsey` when the guard is decidable on the analyzer's Ruby,
93
95
  # otherwise nil (both arms stay live)
94
96
  def verdict(node)
95
97
  return nil unless node.is_a?(Prism::CallNode)
@@ -109,35 +111,40 @@ module Rigor
109
111
  end
110
112
 
111
113
  # Reads one side of the comparison into `[kind, value]`, or nil when it is not readable.
112
- #
113
- # @return [Array(Symbol, Object), nil]
114
114
  def read_operand(node)
115
115
  case node
116
116
  when Prism::StringNode then [:literal_string, node.unescaped]
117
- when Prism::ConstantReadNode then read_predefined(node)
118
- when Prism::ConstantPathNode then read_version_constant(node)
117
+ when Prism::ConstantReadNode, Prism::ConstantPathNode then read_constant(node)
119
118
  when Prism::CallNode then read_gem_version(node)
120
119
  end
121
120
  end
122
121
  private_class_method :read_operand
123
122
 
124
- def read_predefined(node)
125
- kind = PREDEFINED[node.name]
126
- return nil unless kind
127
-
128
- value = runtime_value(node.name.to_s)
129
- value && [kind, value]
130
- end
131
- private_class_method :read_predefined
132
-
133
- def read_version_constant(node)
123
+ # Dispatches on the RESOLVED name rather than on the node class. `::RUBY_VERSION` names the very
124
+ # constant `RUBY_VERSION` names — the leading `::` only makes the top-level lookup explicit — but
125
+ # Prism spells it as a `ConstantPathNode` with a nil parent, so a node-class dispatch sent it down
126
+ # the `X::VERSION` path and declined it for not being a curated qualified name. The same program
127
+ # then had two diagnostic sets for two spellings of one guard
128
+ # ([#877](https://github.com/rigortype/rigor/issues/877)).
129
+ #
130
+ # The foldable set is unchanged and stays closed: an unqualified name folds only when it is one of
131
+ # {PREDEFINED}, a qualified one only when it is in {VERSION_CONSTANTS}.
132
+ def read_constant(node)
134
133
  path = Source::ConstantPath.qualified_name_or_nil(node)
135
- return nil unless path && VERSION_CONSTANTS.include?(path)
134
+ return nil unless path
135
+
136
+ kind =
137
+ if path.include?("::")
138
+ :string if VERSION_CONSTANTS.include?(path)
139
+ else
140
+ PREDEFINED[path]
141
+ end
142
+ return nil unless kind
136
143
 
137
144
  value = runtime_value(path)
138
- value && [:string, value]
145
+ value && [kind, value]
139
146
  end
140
- private_class_method :read_version_constant
147
+ private_class_method :read_constant
141
148
 
142
149
  # `Gem::Version.new(<readable>)`. The inner operand must be a plain version String — an engine name
143
150
  # is not a version, and `Gem::Version.new` raises on anything `Gem::Version.correct?` rejects, so a
@@ -165,7 +172,7 @@ module Rigor
165
172
  # `const_get` would execute the target file inside the analyzer. `Module#autoload?` is what
166
173
  # separates the two ([#680](https://github.com/rigortype/rigor/issues/680)).
167
174
  #
168
- # @return [String, nil] the constant's value when it is a non-empty String
175
+ # @return the constant's value when it is a non-empty String
169
176
  def runtime_value(path)
170
177
  mod = ::Object
171
178
  path.split("::").each do |part|
@@ -9,11 +9,11 @@ module Rigor
9
9
  # consistency, normalization, or erasure.
10
10
  #
11
11
  # @!attribute class_name
12
- # @return [String] the receiver class whose method declared `-> void`.
12
+ # @return the receiver class whose method declared `-> void`.
13
13
  # @!attribute method_name
14
- # @return [Symbol] the method whose return was recovered.
14
+ # @return the method whose return was recovered.
15
15
  # @!attribute kind
16
- # @return [Symbol] `:instance` or `:singleton`.
16
+ # @return `:instance` or `:singleton`.
17
17
  VoidOrigin = Data.define(:class_name, :method_name, :kind) do
18
18
  # A human-facing `Class#method` / `Class.method` label for the diagnostic message.
19
19
  def label
@@ -75,10 +75,8 @@ module Rigor
75
75
  # consult; the memo persists across consults for the run. The memo is probed before the visited
76
76
  # set is allocated so the common post-warmup hit path allocates nothing.
77
77
  #
78
- # @param def_node [Prism::DefNode]
79
- # @param owner [String] the qualified receiver class the `def` belongs to.
80
- # @param kind [Symbol] `:instance` or `:singleton`.
81
- # @return [Inference::VoidOrigin, nil]
78
+ # @param owner — the qualified receiver class the `def` belongs to.
79
+ # @param kind — `:instance` or `:singleton`.
82
80
  def origin_for(def_node, owner, kind)
83
81
  return nil if def_node.nil?
84
82
 
@@ -51,7 +51,7 @@ module Rigor
51
51
  # computed from the stale text would be wrong. Consumers check {#desynchronized?} and decline to answer
52
52
  # rather than answering wrongly; the mark clears on the next `didOpen` or full-text change.
53
53
  #
54
- # @return [Boolean] true when the changes were applied.
54
+ # @return true when the changes were applied.
55
55
  def apply_changes(uri:, changes:, version:)
56
56
  text = IncrementalSync.apply_all(@entries[uri]&.bytes, changes)
57
57
  @desynchronized.delete(uri)
@@ -63,13 +63,13 @@ module Rigor
63
63
  false
64
64
  end
65
65
 
66
- # @return [Boolean] true when the last `didChange` for `uri` could not be applied, so the held text no
66
+ # @return true when the last `didChange` for `uri` could not be applied, so the held text no
67
67
  # longer matches the editor's.
68
68
  def desynchronized?(uri)
69
69
  @desynchronized.key?(uri)
70
70
  end
71
71
 
72
- # @return [String, nil] why `uri` is desynchronised, for the log line; nil when it is in sync.
72
+ # @return why `uri` is desynchronised, for the log line; nil when it is in sync.
73
73
  def desynchronization_reason(uri)
74
74
  @desynchronized[uri]
75
75
  end
@@ -91,7 +91,7 @@ module Rigor
91
91
  @dirty.delete(uri)
92
92
  end
93
93
 
94
- # @return [Boolean] true when `uri` has unsaved changes. A buffer that is dirty may only be published
94
+ # @return true when `uri` has unsaved changes. A buffer that is dirty may only be published
95
95
  # from an analysis that bound ITS bytes — see the publish-set invariant in
96
96
  # `docs/design/20260517-language-server.md`.
97
97
  def dirty?(uri)
@@ -42,7 +42,7 @@ module Rigor
42
42
  @project_context = project_context
43
43
  end
44
44
 
45
- # @return [Array<Hash>, nil] LSP `CompletionItem[]` or nil
45
+ # @return LSP `CompletionItem[]` or nil
46
46
  # when the cursor isn't at a position the provider can
47
47
  # enumerate completions for. Returning nil maps to
48
48
  # `result: null` per the LSP spec — clients treat it as
@@ -68,7 +68,6 @@ module Rigor
68
68
  end
69
69
  end
70
70
 
71
- # @return [Integer] number of currently-pending tasks.
72
71
  def pending_size
73
72
  @mutex.synchronize { @tasks.size }
74
73
  end
@@ -30,13 +30,13 @@ module Rigor
30
30
  hint: 4
31
31
  }.freeze
32
32
 
33
- # @param debouncer [Rigor::LanguageServer::Debouncer, nil]
33
+ # @param debouncer —
34
34
  # when present, `publish_for` schedules its work through
35
35
  # the debouncer (cancels prior pending task for the same
36
36
  # URI, fires after `debounce_seconds` quiet-time). Nil
37
37
  # keeps the slice 4-7 synchronous behaviour — primarily
38
38
  # useful for specs.
39
- # @param debounce_seconds [Numeric] quiet-time before the
39
+ # @param debounce_seconds — quiet-time before the
40
40
  # debounced publish fires. 0 with a debouncer means
41
41
  # "schedule on next-tick" (still async); without a
42
42
  # debouncer the value is unused.
@@ -212,7 +212,7 @@ module Rigor
212
212
  notify(uri, diagnostics)
213
213
  end
214
214
 
215
- # @return [Hash, nil] `{ uri:, path:, bytes: }` when `uri` is eligible for the batch, or nil to
215
+ # @return `{ uri:, path:, bytes: }` when `uri` is eligible for the batch, or nil to
216
216
  # exclude it. Mirrors `#run_and_notify`'s own guards: a buffer closed during the debounce window is
217
217
  # dropped silently (its didClose empty publish already cleared the markers); a desynchronised buffer
218
218
  # publishes an EMPTY set immediately (same as the single-buffer path) rather than joining the batch.
@@ -268,8 +268,9 @@ module Rigor
268
268
 
269
269
  # Runs `Analysis::Runner` with a `BufferBinding` so the buffer bytes (instead of the on-disk file) drive
270
270
  # the parse. The `Rigor::Analysis::ProjectScan` cached on the ProjectContext is passed through
271
- # `prebuilt:` so plugin `#prepare`, the dependency-source walker, and the synthetic-method /
272
- # project-patched scanners do not re-run per publish. The snapshot rebuilds only when
271
+ # `prebuilt:` so plugin `#prepare`, the dependency-source walker, the synthetic-method /
272
+ # project-patched scanners and (#1038) the template-unit transforms do not re-run per publish — the
273
+ # last of those is an Erubi compile of every view for an ERB project. The snapshot rebuilds only when
273
274
  # `ProjectContext#invalidate!` fires (watched-file or configuration change). Returns the LSP-shaped
274
275
  # Diagnostic Array, ready to serialize into the notification's `params.diagnostics` field.
275
276
  def run_analysis(path:, bytes:)
@@ -320,7 +321,7 @@ module Rigor
320
321
  end
321
322
  end
322
323
 
323
- # @return [Hash, nil] the LSP `Diagnostic` Hash, or nil to
324
+ # @return the LSP `Diagnostic` Hash, or nil to
324
325
  # skip diagnostics outside the buffer's own path (e.g.
325
326
  # `.rigor.yml`-anchored info diagnostics get filtered —
326
327
  # they belong to the project, not the buffer).
@@ -30,7 +30,7 @@ module Rigor
30
30
  @project_context = project_context
31
31
  end
32
32
 
33
- # @return [Array<Hash>, nil] LSP `DocumentSymbol[]` for the
33
+ # @return LSP `DocumentSymbol[]` for the
34
34
  # buffer at `uri`. Returns nil when the URI isn't open or
35
35
  # doesn't parse cleanly enough to surface symbols — LSP
36
36
  # clients fall back to no-outline in that case.
@@ -23,7 +23,7 @@ module Rigor
23
23
  @project_context = project_context
24
24
  end
25
25
 
26
- # @return [Array<Hash>, nil] LSP `FoldingRange[]` for the
26
+ # @return LSP `FoldingRange[]` for the
27
27
  # buffer, or nil when the URI isn't open / parseable.
28
28
  def provide(uri)
29
29
  path, entry = buffer_for(uri)
@@ -29,7 +29,7 @@ module Rigor
29
29
  @renderer = renderer
30
30
  end
31
31
 
32
- # @return [Hash, nil] an LSP `Hover` payload or nil when no
32
+ # @return an LSP `Hover` payload or nil when no
33
33
  # expression sits at the queried position. Returning nil
34
34
  # maps to `result: null` per the LSP spec — clients
35
35
  # suppress the hover popup in that case.
@@ -13,7 +13,7 @@ module Rigor
13
13
  # (method call, constant, local, ivar, literal) gets the most relevant type-aware presentation per
14
14
  # `docs/design/20260517-lsp-hover-completion.md`.
15
15
  class HoverRenderer
16
- # @param node_scope_lookup [#[]] node-to-scope table built by `ScopeIndexer.index`. The renderer indexes
16
+ # @param node_scope_lookup — node-to-scope table built by `ScopeIndexer.index`. The renderer indexes
17
17
  # into it to retrieve the receiver's narrow scope when specialising on `CallNode`. The lookup never
18
18
  # returns nil (the indexer's Hash carries `default_scope` as its default value), so the renderer trusts
19
19
  # the lookup result.
@@ -83,9 +83,9 @@ module Rigor
83
83
  )
84
84
  end
85
85
 
86
- # @return [[RBS::Definition::Method, String, Symbol], nil] the resolved method definition, the receiver
87
- # class name, and the dispatch kind (`:instance` or `:singleton`); nil when the receiver shape isn't
88
- # yet supported or the method doesn't resolve through the RBS env.
86
+ # @return `[definition, receiver_class_name, kind]` — the resolved method definition (nil when the
87
+ # method doesn't resolve through the RBS env), the receiver class name, and the dispatch kind
88
+ # (`:instance` or `:singleton`); `[nil, nil, nil]` when the receiver shape isn't yet supported.
89
89
  def lookup_method(receiver_type, method_name, scope)
90
90
  case receiver_type
91
91
  when Type::Singleton
@@ -46,10 +46,10 @@ module Rigor
46
46
  # Applies every change in order, each against the result of the previous — the LSP contract for a
47
47
  # multi-change `didChange` notification.
48
48
  #
49
- # @param text [String, nil] the held buffer text; nil when no buffer is open for the URI.
50
- # @param changes [Array<Hash>] the `contentChanges` array.
51
- # @return [String] the new buffer text.
52
- # @raise [UnappliableChange] if any change cannot be applied.
49
+ # @param text — the held buffer text; nil when no buffer is open for the URI.
50
+ # @param changes — the `contentChanges` array.
51
+ # @return the new buffer text.
52
+ # @raise UnappliableChange — if any change cannot be applied.
53
53
  def apply_all(text, changes)
54
54
  raise UnappliableChange, "contentChanges must be an Array, got #{changes.class}" unless changes.is_a?(Array)
55
55
 
@@ -64,7 +64,7 @@ module Rigor
64
64
  # companion to `range` and is accepted-but-ignored: it is redundant with `range`, historically ambiguous
65
65
  # about its units, and `range` is the authoritative field.
66
66
  #
67
- # @raise [UnappliableChange]
67
+ # @raise UnappliableChange
68
68
  def apply(text, change)
69
69
  raise UnappliableChange, "contentChanges entry must be a Hash, got #{change.class}" unless change.is_a?(Hash)
70
70
 
@@ -95,7 +95,7 @@ module Rigor
95
95
  raise UnappliableChange, "held text is not valid UTF-8: #{e.message}"
96
96
  end
97
97
 
98
- # @return [Array<Array(Integer, Integer)>] one `[content_start, content_end]` pair per line, in Ruby
98
+ # @return one `[content_start, content_end]` pair per line, in Ruby
99
99
  # character indices. `content_end` excludes the line terminator, so it doubles as the clamp target for
100
100
  # an over-long `character`. A trailing terminator yields a final empty span — the virtual last line an
101
101
  # editor puts the cursor on, and the anchor for an end-of-document insert.
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "../environment"
4
+ require_relative "../project_environment"
4
5
  require_relative "../cache/store"
5
6
  require_relative "../analysis/runner"
6
7
  require_relative "../analysis/incremental_session"
@@ -11,12 +12,14 @@ module Rigor
11
12
  # Per-session cache of the project-wide analyzer state the LSP reads on every request — chiefly the
12
13
  # `Environment` (with its ~100-300ms RBS env build), a read-only `Cache::Store` that lets the runner hit
13
14
  # the on-disk RBS cache without writing back, and (since the pre-pass cache slice) a frozen
14
- # {Rigor::Analysis::ProjectScan} snapshot covering the plugin registry, dependency-source index, and
15
- # pre-pass scanner outputs.
15
+ # {Rigor::Analysis::ProjectScan} snapshot covering the plugin registry, dependency-source index,
16
+ # pre-pass scanner outputs and (since #1038) the compiled template-unit index.
16
17
  #
17
18
  # The pre-pass scan lets `DiagnosticPublisher#run_analysis` build a `Runner` with `prebuilt:` so per-buffer
18
19
  # publishes skip plugin `#prepare`, the synthetic-method scanner, the project-patched scanner, and the
19
- # dependency-source walker. For projects with substrate plugins / opt-in dependency source / sizeable
20
+ # dependency-source walker — and, since #1038, the template-unit transform: a publish recompiles only a
21
+ # template that moved on disk since the scan was built (plus the buffer's own, always), rather than every
22
+ # view in the project. For projects with substrate plugins / opt-in dependency source / sizeable
20
23
  # `pre_eval:` configuration this cuts publish wall time substantially — for the trivial case the savings
21
24
  # are small (the per-publish path is already ≈2ms once Environment is warm).
22
25
  #
@@ -74,11 +77,7 @@ module Rigor
74
77
  dependency_source_index: project_scan.dependency_source_index,
75
78
  synthetic_method_index: project_scan.synthetic_method_index,
76
79
  project_patched_methods: project_scan.project_patched_methods,
77
- bundler_bundle_path: @configuration.bundler_bundle_path,
78
- bundler_auto_detect: @configuration.bundler_auto_detect,
79
- bundler_lockfile: @configuration.bundler_lockfile,
80
- rbs_collection_lockfile: @configuration.rbs_collection_lockfile,
81
- rbs_collection_auto_detect: @configuration.rbs_collection_auto_detect
80
+ **ProjectEnvironment.dependency_discovery_options(@configuration)
82
81
  )
83
82
  end
84
83
 
@@ -15,10 +15,10 @@ module Rigor
15
15
  # dropped. The same claim/consume shape `DiagnosticPublisher#run_project_round` already uses for the
16
16
  # whole-project save round (#246), generalised to an arbitrary key type and an arbitrary batch action.
17
17
  class PublishBatcher
18
- # @param on_batch [#call] `(keys) -> void`, called with the deduplicated Array of keys pending at the
18
+ # @param on_batch — `(keys) -> void`, called with the deduplicated Array of keys pending at the
19
19
  # start of one round. May be called more than once in a row when keys keep arriving while a round
20
20
  # runs.
21
- # @param on_error [#call, nil] `(exception) -> void`, called when `on_batch` raises. A round must
21
+ # @param on_error — `(exception) -> void`, called when `on_batch` raises. A round must
22
22
  # never wedge the coalescing lock for every future `#enqueue` call — ownership is always released
23
23
  # before this fires. Whatever was mid-flight when the round raised is lost; anything enqueued by a
24
24
  # concurrent `#enqueue` call after this round's drain but before the rescue stays pending and rides