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
@@ -35,7 +35,6 @@ module Rigor
35
35
  @load_errors = []
36
36
  end
37
37
 
38
- # @return [LocaleIndex]
39
38
  def load
40
39
  per_key = {} # dotted_key => { locale => Set<String> }
41
40
  per_key_kinds = {} # dotted_key => { locale => :string|:array|:hash }
@@ -46,10 +46,7 @@ module Rigor
46
46
  # skipped — the controller/action scope cannot be statically determined there.
47
47
  # - View template lazy keys (`t('.key')` inside ERB / Haml / Slim) are validated for key existence and
48
48
  # per-locale coverage. Interpolation variable validation is skipped for view templates (the hash may
49
- # come from controller instance variables not visible in the template). The view scan is a
50
- # project-wide pass surfaced through the per-file diagnostic hook, so under `--workers` each fork-pool
51
- # worker re-emits the full set (the same once-per-run limitation the `load-error` path carries);
52
- # sequential `rigor check` is unaffected.
49
+ # come from controller instance variables not visible in the template).
53
50
  # - Pluralization (`t('errors.messages.too_short', count: n)`) is recognised at the call site but the
54
51
  # `count` key is not used to validate the locale's pluralization branches.
55
52
  # - YAML aliases / merges are accepted (Psych's standard `aliases: true`) but custom Ruby classes
@@ -57,6 +54,7 @@ module Rigor
57
54
  class RailsI18n < Rigor::Plugin::Base
58
55
  manifest(
59
56
  id: "rails-i18n",
57
+ target_gems: ["i18n"],
60
58
  # Bumped 2026-06-23 — view template lazy-key scanning (`t('.key')` inside ERB / Haml / Slim under
61
59
  # `view_search_paths`).
62
60
  version: "0.3.0",
@@ -105,29 +103,26 @@ module Rigor
105
103
  @view_search_paths = Array(config.fetch("view_search_paths")).map(&:to_s)
106
104
  @configured_locales = Array(config.fetch("configured_locales")).map(&:to_s)
107
105
  @load_errors = []
108
- @load_errors_emitted = false
109
- @view_diagnostics_emitted = false
110
106
  end
111
107
 
112
- # File-level only: the once-per-run YAML load errors + the runtime (cache-load) error + the
113
- # view-template scan. Per-call `t('key')` validation runs over the engine-owned walk via the
114
- # node_rule below (ADR-37). The locale index and view diagnostics are lazily loaded + memoised by
115
- # `producer_value`.
108
+ # Returns no rows of its own: every file-level outcome here is run-scoped. The YAML load errors and the
109
+ # runtime (cache-load) errors are disclosures (#1051); the view-template scan is a positioned batch
110
+ # registered through `#emit_once` (#1060) — its rows name view files that are usually not analysed
111
+ # targets, so no per-file return could own them, and returning them from whichever file an instance
112
+ # saw first repeated the whole batch once per fork-pool worker. Per-call `t('key')` validation runs
113
+ # over the engine-owned walk via the node_rule below (ADR-37). The locale index and view diagnostics
114
+ # are lazily loaded + memoised by `producer_value`, so the repeat registrations are no-ops.
116
115
  def diagnostics_for_file(path:, scope:, root:) # rubocop:disable Lint/UnusedMethodArgument
117
116
  index = producer_value(:locale_index)
118
- diagnostics = []
119
- diagnostics.concat(consume_load_error_diagnostics(path)) unless @load_errors.empty?
120
- diagnostics << runtime_error_diagnostic(path) if index.nil? && producer_error(:locale_index)
121
- unless @view_diagnostics_emitted
122
- view_diags = producer_value(:view_diagnostics) || []
123
- if (view_err = producer_error(:view_diagnostics))
124
- diagnostics << view_runtime_error_diagnostic(path, view_err)
125
- else
126
- diagnostics.concat(view_diags)
127
- end
128
- @view_diagnostics_emitted = true
117
+ disclose_load_errors unless @load_errors.empty?
118
+ disclose_locale_runtime_error if index.nil? && producer_error(:locale_index)
119
+ view_diags = producer_value(:view_diagnostics) || []
120
+ if (view_err = producer_error(:view_diagnostics))
121
+ disclose_view_runtime_error(view_err)
122
+ else
123
+ emit_once(:view_diagnostics, view_diags)
129
124
  end
130
- diagnostics
125
+ []
131
126
  end
132
127
 
133
128
  # The lazy-key (`t('.key')`) expansion needs the enclosing method (the controller action), supplied
@@ -210,35 +205,39 @@ module Rigor
210
205
  end
211
206
 
212
207
  # The runner only invokes `diagnostics_for_file` for Ruby files (`paths:` is filtered to `.rb`). YAML
213
- # parse errors therefore can't be anchored on the offending locale file directly; instead, we emit
214
- # them once per run on the first analyzed Ruby file, naming the offending YAML path in the message.
215
- def consume_load_error_diagnostics(path)
216
- return [] if @load_errors_emitted
217
-
218
- @load_errors_emitted = true
219
- @load_errors.map do |err|
220
- Rigor::Analysis::Diagnostic.new(
221
- path: path, line: 1, column: 1,
208
+ # parse errors therefore can't be anchored on the offending locale file directly, and the row used to
209
+ # be pinned to the first analysed Ruby file — once per plugin INSTANCE, so once per fork-pool worker
210
+ # (#1051). They go through the run-scoped channel instead: keyed by the locale path that failed, so a
211
+ # project with two broken YAML files still discloses both, each exactly once, at `.rigor.yml:1:1`.
212
+ def disclose_load_errors
213
+ @load_errors.each do |err|
214
+ disclose_once(
215
+ "locale-parse:#{err.path}",
222
216
  message: "rigor-rails-i18n: failed to parse `#{err.path}`: #{err.message}",
223
217
  severity: :warning,
224
218
  rule: "load-error"
225
219
  )
226
220
  end
221
+ nil
227
222
  end
228
223
 
229
- def runtime_error_diagnostic(path)
224
+ # #1051 — both of these say the plugin could not READ something the project configured, which is a
225
+ # run-level fact with no source position; they were repeated on every analysed file (the locale one)
226
+ # or on each instance's first (the view one). A key distinct from the `:view_diagnostics` batch's:
227
+ # the two channels share one registration table per plugin.
228
+ def disclose_locale_runtime_error
230
229
  error = producer_error(:locale_index)
231
- Rigor::Analysis::Diagnostic.new(
232
- path: path, line: 1, column: 1,
230
+ disclose_once(
231
+ :locale_index_failed,
233
232
  message: "rigor-rails-i18n: failed to load locales: #{error.class}: #{error.message}",
234
233
  severity: :warning,
235
234
  rule: "load-error"
236
235
  )
237
236
  end
238
237
 
239
- def view_runtime_error_diagnostic(path, error)
240
- Rigor::Analysis::Diagnostic.new(
241
- path: path, line: 1, column: 1,
238
+ def disclose_view_runtime_error(error)
239
+ disclose_once(
240
+ :view_scan_failed,
242
241
  message: "rigor-rails-i18n: failed to scan view templates: #{error.class}: #{error.message}",
243
242
  severity: :warning,
244
243
  rule: "load-error"
@@ -24,8 +24,8 @@ module Rigor
24
24
  module Acronyms
25
25
  module_function
26
26
 
27
- # @param contents [String, nil] the source of `config/initializers/inflections.rb`.
28
- # @return [Array<String>] declared acronyms in declaration order. Empty for a missing / unparseable
27
+ # @param contents — the source of `config/initializers/inflections.rb`.
28
+ # @return declared acronyms in declaration order. Empty for a missing / unparseable
29
29
  # file, or one that declares none — which leaves composition exactly as it was.
30
30
  def discover(contents)
31
31
  return [] if contents.nil? || contents.empty?
@@ -51,9 +51,9 @@ module Rigor
51
51
  # camelization of the acronym appears at a camel-word boundary — `Oauth` in `OauthMetadata` and in
52
52
  # `WellKnown::Oauth`, but never the `Oauth` inside a hypothetical `Xoauth`.
53
53
  #
54
- # @param name [String] e.g. `"Activitypub::CollectionsController"`.
55
- # @param acronyms [Enumerable<String>] as returned by {.discover}.
56
- # @return [String] e.g. `"ActivityPub::CollectionsController"`.
54
+ # @param name — e.g. `"Activitypub::CollectionsController"`.
55
+ # @param acronyms — as returned by {.discover}.
56
+ # @return e.g. `"ActivityPub::CollectionsController"`.
57
57
  def apply(name, acronyms)
58
58
  acronyms.reduce(name) do |current, acronym|
59
59
  plain = Rigor::Plugin::Inflector.camelize(acronym.to_s.downcase)
@@ -61,20 +61,10 @@ module Rigor
61
61
  %r{(?:\A|/)config/}
62
62
  ].freeze
63
63
 
64
- # @param path [String] file being analysed
65
- # @param root [Prism::Node]
66
- # @param helper_table [HelperTable]
67
- # @return [Array<Diagnostic>]
68
64
  # The route-helper violations for a single call node (0..2), or `[]` when the node is not an
69
65
  # implicit `*_path` / `*_url` helper call, is shadowed by a same-file binding, or is in a
70
66
  # suppressed directory. ADR-37: the engine owns the walk; the same-file `shadowing` set is built
71
67
  # once as the node-rule file context.
72
- #
73
- # @param call_node [Prism::Node]
74
- # @param helper_table [HelperTable]
75
- # @param shadowing [Set<String>]
76
- # @param path [String]
77
- # @return [Array<Violation>]
78
68
  def violations_for(call_node:, helper_table:, shadowing:, path:)
79
69
  return [] unless call_node.is_a?(Prism::CallNode) && implicit_helper_call?(call_node)
80
70
 
@@ -58,6 +58,7 @@ module Rigor
58
58
  [nil, "registration", :get, :show]
59
59
  ]
60
60
  }.freeze
61
+ Ractor.make_shareable(CONTROLLER_HELPERS)
61
62
 
62
63
  # Dynamic-provider helper patterns. `_omniauth_authorize_path` and `_omniauth_callback_path` are
63
64
  # generated per configured provider (Facebook, GitHub, …) by an initializer this parser does not
@@ -70,11 +71,11 @@ module Rigor
70
71
 
71
72
  module_function
72
73
 
73
- # @param resource [String, Symbol] the `devise_for` argument — typically `:users`. Used as both the
74
+ # @param resource — the `devise_for` argument — typically `:users`. Used as both the
74
75
  # helper-name segment (`new_user_session_path`) and to derive the resource path.
75
- # @param skip [Array<Symbol>] controllers the project disables via `devise_for :users, skip:
76
+ # @param skip — controllers the project disables via `devise_for :users, skip:
76
77
  # [:registrations]`.
77
- # @return [Array<HelperTable::Entry>] one entry per generated `_path` helper. The caller is
78
+ # @return one entry per generated `_path` helper. The caller is
78
79
  # expected to pair `_url` variants the same way `RoutesParser` does for other entries.
79
80
  def generate(resource:, skip: [])
80
81
  resource_segment = singularize(resource.to_s)
@@ -135,6 +136,7 @@ module Rigor
135
136
  unlocks: %w[new_unlock_path unlock_path],
136
137
  registrations: %w[cancel_registration_path new_registration_path edit_registration_path registration_path]
137
138
  }.freeze
139
+ Ractor.make_shareable(SCOPED_HELPERS)
138
140
  private_constant :SCOPED_HELPERS
139
141
 
140
142
  def scoped_helpers(skip_set)
@@ -63,11 +63,12 @@ module Rigor
63
63
  ["oauth_authorized_application_url", 1, "/oauth/authorized_applications/:id", :delete, :destroy]
64
64
  ]
65
65
  }.freeze
66
+ Ractor.make_shareable(CONTROLLER_HELPERS)
66
67
 
67
68
  module_function
68
69
 
69
- # @param skip [Array<Symbol>] controller names the project omits via `skip_controllers :name, ...`.
70
- # @return [Array<HelperTable::Entry>] flattened entries.
70
+ # @param skip — controller names the project omits via `skip_controllers :name, ...`.
71
+ # @return flattened entries.
71
72
  def generate(skip: [])
72
73
  skip_set = skip.to_set(&:to_sym)
73
74
  CONTROLLER_HELPERS.flat_map do |controller, rows|
@@ -43,9 +43,9 @@ module Rigor
43
43
 
44
44
  module_function
45
45
 
46
- # @param contents_per_path [Hash{String => String}] file path → source text, read by the caller
46
+ # @param contents_per_path — file path → source text, read by the caller
47
47
  # (through the trusted `IoBoundary`, so cache invalidation works).
48
- # @return [Array<String>] the recognised helper-name prefixes (`["api_v3", "api_v4"]`). Empty when
48
+ # @return the recognised helper-name prefixes (`["api_v3", "api_v4"]`). Empty when
49
49
  # the project declares no grape API — nothing changes for such a project.
50
50
  def discover(contents_per_path)
51
51
  declarations = {}
@@ -43,10 +43,10 @@ module Rigor
43
43
 
44
44
  module_function
45
45
 
46
- # @param contents_per_path [Hash{String => String}] file path → source text. The caller is
46
+ # @param contents_per_path — file path → source text. The caller is
47
47
  # responsible for reading files (typically through the trusted `IoBoundary` so cache invalidation
48
48
  # works).
49
- # @return [Set<String>] method names suitable for inclusion in the `HelperTable`'s custom-helper set.
49
+ # @return method names suitable for inclusion in the `HelperTable`'s custom-helper set.
50
50
  def discover(contents_per_path)
51
51
  names = []
52
52
  contents_per_path.each_value do |contents|
@@ -26,21 +26,21 @@ module Rigor
26
26
 
27
27
  attr_reader :entries, :custom_helpers, :devise_resources
28
28
 
29
- # @param entries [Array<Entry>] freshly built; the factory below is the canonical construction path.
30
- # @param custom_helpers [Enumerable<String>] names of project-defined helper methods (typically
29
+ # @param entries — freshly built; the factory below is the canonical construction path.
30
+ # @param custom_helpers — names of project-defined helper methods (typically
31
31
  # pulled from `app/helpers/**/*.rb` by {HelperDiscoverer}) that the analyzer should treat as
32
32
  # known — they do NOT correspond to a route but their presence MUST NOT fire `unknown-helper`. No
33
33
  # arity / path metadata is recorded; the analyzer skips the route-side checks for these names.
34
- # @param devise_resources [Enumerable<String>] resource names declared via `devise_for :resource`
34
+ # @param devise_resources — resource names declared via `devise_for :resource`
35
35
  # (already singularised, e.g. `"user"`). Used to recognise the dynamic OmniAuth helper family
36
36
  # (`<resource>_<provider>_omniauth_(authorize|callback)_(path|url)`) whose provider segment is
37
37
  # supplied at runtime — no per-name entry is registered for them but they MUST NOT fire
38
38
  # `unknown-helper` either.
39
- # @param grape_prefixes [Enumerable<String>] helper-name prefixes generated by `grape-path-helpers`
39
+ # @param grape_prefixes — helper-name prefixes generated by `grape-path-helpers`
40
40
  # (`"api_v4"`), composed by {GrapeApiDiscoverer} from the project's own `prefix` / `version`
41
41
  # declarations. The names beyond the prefix come from grape's runtime route table and cannot be
42
42
  # enumerated statically, so the namespace is open — same contract as the OmniAuth family above.
43
- # @param controllers [Enumerable<String>] fully-qualified controller class names the routes file
43
+ # @param controllers — fully-qualified controller class names the routes file
44
44
  # dispatches to (`"Admin::UsersController"`). Published as the plugin's `:reachability_roots` fact
45
45
  # (ADR-102 WD3) — a controller is an entry point nothing in the project references, so without this
46
46
  # every routed controller reads as unused. Deliberately NOT consulted by any helper-name check.
@@ -58,20 +58,19 @@ module Rigor
58
58
  freeze
59
59
  end
60
60
 
61
- # @return [Array<String>] the routed controller class names (see the `controllers:` parameter). Reads
61
+ # @return the routed controller class names (see the `controllers:` parameter). Reads
62
62
  # through a nil guard rather than `attr_reader` so a table deserialised from a cache slot written
63
63
  # before this field existed answers `[]` instead of `nil`.
64
64
  def controllers
65
65
  @controllers || []
66
66
  end
67
67
 
68
- # @return [Entry, nil] First matching entry; for the uncountable-noun case this is the index helper
68
+ # @return First matching entry; for the uncountable-noun case this is the index helper
69
69
  # (the show helper is also registered but starts second).
70
70
  def find(helper_name)
71
71
  @by_name[helper_name.to_s]&.first
72
72
  end
73
73
 
74
- # @return [Boolean]
75
74
  def known?(helper_name)
76
75
  @by_name.key?(helper_name.to_s)
77
76
  end
@@ -118,7 +117,7 @@ module Rigor
118
117
  end
119
118
  end
120
119
 
121
- # @return [Boolean] true when any entry under this helper name accepts the given positional arity.
120
+ # @return true when any entry under this helper name accepts the given positional arity.
122
121
  #
123
122
  # Rails helpers have the signature `helper(*segments, options = {})`, so `expected + 1` is always
124
123
  # valid — the extra argument is treated as a query-params/options hash (e.g. `users_path(page: 2)`
@@ -127,7 +126,7 @@ module Rigor
127
126
  (@by_name[helper_name.to_s] || []).any? { |entry| entry.arity == arity || entry.arity + 1 == arity }
128
127
  end
129
128
 
130
- # @return [Array<Integer>] all accepted positional arities for a helper name. Empty when unknown.
129
+ # @return all accepted positional arities for a helper name. Empty when unknown.
131
130
  def acceptable_arities(helper_name)
132
131
  (@by_name[helper_name.to_s] || []).map(&:arity).uniq
133
132
  end
@@ -54,11 +54,10 @@ module Rigor
54
54
 
55
55
  module_function
56
56
 
57
- # @param contents [String] raw `config/routes.rb` source
58
- # @param file_reader [Proc, nil] called with `"name.rb"` to load a
57
+ # @param contents — raw `config/routes.rb` source
58
+ # @param file_reader — called with `"name.rb"` to load a
59
59
  # draw partial from `config/routes/name.rb`. Returns file contents
60
60
  # or nil when the file is absent.
61
- # @return [HelperTable]
62
61
  def parse(contents, file_reader: nil, custom_helpers: [], grape_prefixes: [], acronyms: [])
63
62
  parse_result = Prism.parse(contents)
64
63
  unless parse_result.errors.empty?
@@ -52,6 +52,7 @@ module Rigor
52
52
  class RailsRoutes < Rigor::Plugin::Base
53
53
  manifest(
54
54
  id: "rails-routes",
55
+ target_gems: %w[actionpack railties],
55
56
  # Bumped 2026-05-28 — GitLab FOSS sweep adds: (a) `draw_all :name` support (action_dispatch-draw_all
56
57
  # gem; single-file load semantics matching `draw :name`); (b) keyword-style `scope(path:
57
58
  # ':project_id', as: :project)` — path read from the `:path` keyword, not only from the positional
@@ -191,18 +192,12 @@ module Rigor
191
192
  publish_reachability_roots(services, table)
192
193
  end
193
194
 
194
- # File-level only: the once-per-run load-error emission. Per-call helper validation runs over the
195
- # engine-owned walk via the node_rule below (ADR-37), with the same-file `shadowing` set built once
196
- # as the node-rule file context.
195
+ # File-level only: the run-scoped load-error disclosure (#1051). Per-call helper validation runs over
196
+ # the engine-owned walk via the node_rule below (ADR-37), with the same-file `shadowing` set built
197
+ # once as the node-rule file context.
197
198
  def diagnostics_for_file(path:, scope:, root:) # rubocop:disable Lint/UnusedMethodArgument
198
199
  table = helper_table_or_nil
199
- if table.nil? && @load_error
200
- return [] if @load_error_emitted
201
-
202
- @load_error_emitted = true
203
- return [load_error_diagnostic(path)]
204
- end
205
-
200
+ disclose_load_error if table.nil? && @load_error
206
201
  []
207
202
  end
208
203
 
@@ -264,13 +259,11 @@ module Rigor
264
259
  nil
265
260
  end
266
261
 
267
- def load_error_diagnostic(path)
268
- Rigor::Analysis::Diagnostic.new(
269
- path: path, line: 1, column: 1,
270
- message: @load_error,
271
- severity: :warning,
272
- rule: "load-error"
273
- )
262
+ # #1051 — "the routes file did not load" is a fact about the project, not about the file that
263
+ # happened to be analysed first by this instance, so it goes through the run-scoped channel and the
264
+ # engine positions it at `.rigor.yml:1:1`, once per run rather than once per fork-pool worker.
265
+ def disclose_load_error
266
+ disclose_once(:routes_load_failed, message: @load_error, severity: :warning, rule: "load-error")
274
267
  end
275
268
  end
276
269
 
@@ -52,6 +52,7 @@ module Rigor
52
52
  class Railties < Rigor::Plugin::Base
53
53
  manifest(
54
54
  id: "railties",
55
+ target_gems: ["railties"],
55
56
  # Bumped 2026-09-01 (#534 item 2) — the four `Rails.` singleton readers now return lenient
56
57
  # nominals instead of `Dynamic[top]`.
57
58
  version: "0.2.0",
@@ -8,8 +8,18 @@ module Rigor
8
8
  class RbNaCl < Base
9
9
  manifest(
10
10
  id: "rbnacl",
11
+ target_gems: ["rbnacl"],
11
12
  version: "0.1.0",
12
- description: "Rigor type support for RbNaCl libsodium bindings"
13
+ description: "Rigor type support for RbNaCl libsodium bindings",
14
+ signature_paths: ["sig"],
15
+ # ADR-26 — `sig/rbnacl.rbs` declares three methods of a class that has many more (`nonce_bytes`,
16
+ # `key_bytes`, …). Contributing it without opening the receivers would close those classes and
17
+ # manufacture `call.undefined-method` on correct code, which is the trade the project refuses.
18
+ open_receivers: [
19
+ "RbNaCl::SecretBox",
20
+ "RbNaCl::Signatures::Ed25519::SigningKey",
21
+ "RbNaCl::Signatures::Ed25519::VerifyKey"
22
+ ]
13
23
  )
14
24
 
15
25
  # Issue 2 & 5: use robust positional binding extractor and preserve module context
@@ -28,3 +38,5 @@ module Rigor
28
38
  end
29
39
  end
30
40
  end
41
+
42
+ Rigor::Plugin.register(Rigor::Plugin::RbNaCl)
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "strscan"
4
+
5
+ module Rigor
6
+ module Plugin
7
+ class RbsInline < Base
8
+ # Issue #998 — the two same-line `%a{}` spellings rbs's built-in `RBS::InlineParser` and Steep's inline
9
+ # mode accept, repaired into the shape the `rbs-inline` gem's writer honours:
10
+ #
11
+ # - `# @rbs %a{…} () -> String` — the gem's `@rbs %a{}` rule lexes the annotations and stops, so the
12
+ # method type after them is discarded and the method renders `() -> untyped`;
13
+ # - `#: %a{…} () -> String` — the gem hands the whole payload to `RBS::Parser.parse_method_type`, which
14
+ # has no annotation prefix, so the line becomes a `SyntaxErrorAssertion` and nothing of it survives.
15
+ #
16
+ # Both were silent (ADR-32 WD12), and the second reached users as #997's "did not parse … DROPPED"
17
+ # notice on input that is valid RBS for the other two readers.
18
+ #
19
+ # This is a post-parse repair of the gem's annotation list, not a rewrite of the Ruby source. A source
20
+ # rewrite would have to put the annotation and the method type on two comment lines — the gem has no
21
+ # one-line spelling for the pair, which is the defect — and inserting a line moves every later line,
22
+ # including the ones a `#:` notice reports and the one a `def` uses to find its comment block.
23
+ #
24
+ # The repair is exact rather than heuristic because every step is the gem's own reading:
25
+ #
26
+ # - the leading `%a{…}` spellings are delimited by the gem's own `Tokenizer`, the lexer that already
27
+ # accepts them in the own-line form, so what counts as an annotation cannot drift from upstream;
28
+ # - each half is re-read by the gem's own `AnnotationParser` as the comment the author would have written
29
+ # in the own-line form (`# @rbs %a{…}`, then `# @rbs () -> String` or `#: () -> String`), on the same
30
+ # line and column, so the replacement annotations are indistinguishable from ones upstream built;
31
+ # - the split lands only when the remainder re-reads as a well-formed method type. Anything else keeps
32
+ # the gem's original annotation untouched, so a genuinely malformed line still reaches the WD12
33
+ # notices, and a split can never trade one silent loss for another.
34
+ module SameLineAnnotations
35
+ module_function
36
+
37
+ # The `@rbs` marker as the gem's `annotation_comment?` detects it, with the whitespace before it.
38
+ RBS_MARKER = /\A\s*@rbs\b/
39
+
40
+ # Replaces, in `parsing_result.annotations`, every same-line form that splits cleanly with the
41
+ # annotation and the method type it spells. Idempotent: a repaired list has no same-line form left.
42
+ def split!(parsing_result)
43
+ return parsing_result unless parsing_result.is_a?(::RBS::Inline::AnnotationParser::ParsingResult)
44
+
45
+ parsing_result.annotations.replace(parsing_result.annotations.flat_map { |annotation| expand(annotation) })
46
+ parsing_result
47
+ end
48
+
49
+ # Every `ParsingResult` a parsed declaration tree holds, which are the objects the writer reads.
50
+ def each_parsing_result(nodes, &)
51
+ nodes.each do |node|
52
+ comments = node.respond_to?(:comments) ? node.comments : nil
53
+ yield comments if comments.is_a?(::RBS::Inline::AnnotationParser::ParsingResult)
54
+ each_parsing_result(node.members, &) if node.respond_to?(:members)
55
+ end
56
+ end
57
+
58
+ # The text an `@rbs %a{…}` line carries after its annotations that the gem discarded — `nil` when
59
+ # there is none. Call it on an annotation {.split!} has already seen: a remainder that survives the
60
+ # split is one that did not read as a method type.
61
+ def dropped_remainder(annotation)
62
+ return nil unless annotation.is_a?(::RBS::Inline::AST::Annotations::RBSAnnotation)
63
+
64
+ parts = same_line_parts(annotation)
65
+ parts && parts[2].strip
66
+ end
67
+
68
+ def expand(annotation)
69
+ parts = same_line_parts(annotation)
70
+ return [annotation] if parts.nil?
71
+
72
+ prefix, spellings, rest = parts
73
+ method_type = reparse(annotation, "##{prefix}#{rest}")
74
+ return [annotation] unless well_formed_method_type?(method_type)
75
+
76
+ # Re-read even when `annotation` already is the `@rbs %a{}` carrier: the original still spans the
77
+ # method type in its source, and {.dropped_remainder} reads exactly that to report a failed split.
78
+ carrier = reparse(annotation, "# @rbs #{spellings.join(' ')}")
79
+ return [annotation] unless carrier.is_a?(::RBS::Inline::AST::Annotations::RBSAnnotation)
80
+
81
+ [carrier, method_type]
82
+ end
83
+
84
+ # `[prefix, spellings, rest]` for an annotation whose payload opens with one or more `%a{…}` and
85
+ # continues with something else, or `nil`. `prefix` is the marker as written (`" @rbs"` or `":"`),
86
+ # so the re-read keeps the gem's continuation offset for a multi-line comment block.
87
+ def same_line_parts(annotation)
88
+ # `annotations` also holds the `CommentLines` of plain prose, which has no `source` to read.
89
+ prefix =
90
+ case annotation
91
+ when ::RBS::Inline::AST::Annotations::RBSAnnotation
92
+ annotation.source.string[RBS_MARKER]
93
+ when ::RBS::Inline::AST::Annotations::SyntaxErrorAssertion
94
+ ":" if annotation.source.string.start_with?(":")
95
+ end
96
+ return nil if prefix.nil?
97
+
98
+ spellings, rest = leading_annotations(annotation.source.string.delete_prefix(prefix))
99
+ return nil if spellings.empty? || rest.strip.empty?
100
+
101
+ [prefix, spellings, rest]
102
+ end
103
+
104
+ # The `%a{…}` tokens the gem's tokenizer reads at the head of `body`, and the unread text after them.
105
+ def leading_annotations(body)
106
+ tokenizer = ::RBS::Inline::AnnotationParser::Tokenizer.new(StringScanner.new(body))
107
+ trivia = ::RBS::Inline::AST::Tree.new(:rigor_same_line_annotations)
108
+ tokenizer.advance(trivia)
109
+ tokenizer.advance(trivia)
110
+
111
+ spellings = []
112
+ while tokenizer.type?(::RBS::Inline::AnnotationParser::Tokens::T_ANNOTATION)
113
+ spellings << tokenizer.lookahead1[1]
114
+ tokenizer.advance(trivia)
115
+ end
116
+ [spellings, tokenizer.rest]
117
+ end
118
+
119
+ # Reads `comment_text` through the gem's `AnnotationParser` as if it stood where `annotation`'s comment
120
+ # block does — same first line, same column, continuation lines re-prefixed with the `#` that
121
+ # `CommentLines#string` strips — and returns the one annotation it yields, or `nil`.
122
+ def reparse(annotation, comment_text)
123
+ location = annotation.source.comments.first&.location
124
+ return nil if location.nil?
125
+
126
+ indent = " " * location.start_column
127
+ padding = "\n" * (location.start_line - 1)
128
+ source = "#{padding}#{indent}#{comment_text.gsub("\n") { "\n#{indent}#" }}\n"
129
+ results = ::RBS::Inline::AnnotationParser.parse(::Prism.parse(source).comments)
130
+ return nil unless results.size == 1
131
+
132
+ annotations = results.first.annotations
133
+ annotations.size == 1 && annotations.first.is_a?(::RBS::Inline::AST::Annotations::Base) ? annotations.first : nil
134
+ end
135
+
136
+ def well_formed_method_type?(annotation)
137
+ case annotation
138
+ when ::RBS::Inline::AST::Annotations::MethodTypeAssertion
139
+ true
140
+ when ::RBS::Inline::AST::Annotations::Method
141
+ annotation.method_types.is_a?(Array) && annotation.error_source.nil?
142
+ else
143
+ false
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end