rigortype 0.3.8 → 0.3.9

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 (447) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/data/capability_roles/capability_roles.rbs +36 -0
  4. data/data/gem_overlay/activesupport/core_ext.rbs +109 -13
  5. data/docs/handbook/02-everyday-types.md +17 -14
  6. data/docs/handbook/03-narrowing.md +37 -4
  7. data/docs/handbook/05-methods-and-blocks.md +1 -1
  8. data/docs/handbook/06-classes.md +2 -2
  9. data/docs/handbook/07-rbs-and-extended.md +14 -1
  10. data/docs/handbook/11-sig-gen.md +72 -1
  11. data/docs/handbook/README.md +1 -1
  12. data/docs/handbook/appendix-elixir.md +2 -2
  13. data/docs/handbook/appendix-go.md +2 -2
  14. data/docs/handbook/appendix-java-csharp.md +2 -2
  15. data/docs/handbook/appendix-mypy.md +2 -2
  16. data/docs/handbook/appendix-phpstan.md +1 -1
  17. data/docs/handbook/appendix-rust.md +1 -1
  18. data/docs/handbook/appendix-type-theory.md +4 -4
  19. data/docs/handbook/appendix-typescript.md +1 -1
  20. data/docs/llms.txt +2 -0
  21. data/docs/manual/02-cli-reference.md +32 -4
  22. data/docs/manual/03-configuration.md +11 -0
  23. data/docs/manual/04-diagnostics.md +11 -0
  24. data/docs/manual/07-plugins.md +15 -5
  25. data/docs/manual/08-skills.md +23 -2
  26. data/docs/manual/11-ci.md +9 -0
  27. data/docs/manual/15-type-protection-coverage.md +8 -0
  28. data/docs/manual/16-rbs-extended-annotations.md +6 -1
  29. data/docs/manual/18-removing-dead-code.md +10 -8
  30. data/docs/manual/plugins/rigor-actionpack.md +18 -0
  31. data/docs/manual/plugins/rigor-activerecord.md +28 -0
  32. data/docs/manual/plugins/rigor-activesupport-core-ext.md +32 -2
  33. data/docs/manual/plugins/rigor-rbs-inline.md +42 -1
  34. data/lib/rigor/analysis/baseline.rb +2 -2
  35. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +1 -1
  36. data/lib/rigor/analysis/check_rules/dead_version_guard_arms.rb +1 -5
  37. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +10 -2
  38. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +1 -1
  39. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +15 -6
  40. data/lib/rigor/analysis/check_rules/rule_ids.rb +1 -1
  41. data/lib/rigor/analysis/check_rules/rule_walk.rb +39 -4
  42. data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +1 -1
  43. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +1 -1
  44. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
  45. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +0 -1
  46. data/lib/rigor/analysis/check_rules.rb +200 -32
  47. data/lib/rigor/analysis/crash_signature.rb +8 -24
  48. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +1 -1
  49. data/lib/rigor/analysis/dependency_source_inference/builder.rb +0 -2
  50. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +0 -2
  51. data/lib/rigor/analysis/dependency_source_inference/index.rb +5 -5
  52. data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +1 -2
  53. data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
  54. data/lib/rigor/analysis/effects_cache_probe.rb +3 -4
  55. data/lib/rigor/analysis/erb_template_detector.rb +1 -2
  56. data/lib/rigor/analysis/fact_store.rb +6 -1
  57. data/lib/rigor/analysis/incremental.rb +16 -0
  58. data/lib/rigor/analysis/incremental_session.rb +81 -22
  59. data/lib/rigor/analysis/plugin_fact_fingerprint.rb +0 -1
  60. data/lib/rigor/analysis/reachability/graph.rb +3 -5
  61. data/lib/rigor/analysis/reachability/plugin_roots.rb +6 -5
  62. data/lib/rigor/analysis/reachability/project_files.rb +2 -2
  63. data/lib/rigor/analysis/reachability/scan.rb +5 -5
  64. data/lib/rigor/analysis/reachability/scan_cache.rb +2 -2
  65. data/lib/rigor/analysis/reachability/signature_scan.rb +2 -2
  66. data/lib/rigor/analysis/result.rb +1 -3
  67. data/lib/rigor/analysis/rule_catalog.rb +4 -1
  68. data/lib/rigor/analysis/run_cache_key.rb +1 -1
  69. data/lib/rigor/analysis/run_cache_probe.rb +3 -5
  70. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -8
  71. data/lib/rigor/analysis/runner/declaration_position.rb +1 -2
  72. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +177 -26
  73. data/lib/rigor/analysis/runner/effect_annotation_residual_pass.rb +2 -3
  74. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +9 -10
  75. data/lib/rigor/analysis/runner/pool_coordinator.rb +256 -88
  76. data/lib/rigor/analysis/runner/project_pre_passes.rb +12 -6
  77. data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
  78. data/lib/rigor/analysis/runner.rb +133 -35
  79. data/lib/rigor/analysis/worker_session.rb +9 -10
  80. data/lib/rigor/bleeding_edge.rb +12 -24
  81. data/lib/rigor/builtins/hkt_builtins.rb +2 -2
  82. data/lib/rigor/builtins/imported_refinements.rb +155 -21
  83. data/lib/rigor/builtins/predefined_constant_refinements.rb +5 -7
  84. data/lib/rigor/builtins/regex_refinement.rb +4 -4
  85. data/lib/rigor/builtins/static_return_refinements.rb +5 -6
  86. data/lib/rigor/cache/annotation_location.rb +2 -4
  87. data/lib/rigor/cache/descriptor.rb +58 -20
  88. data/lib/rigor/cache/engine_source.rb +4 -4
  89. data/lib/rigor/cache/file_digest.rb +9 -1
  90. data/lib/rigor/cache/incremental_snapshot.rb +40 -6
  91. data/lib/rigor/cache/rbs_class_ancestor_table.rb +0 -3
  92. data/lib/rigor/cache/rbs_class_type_param_names.rb +0 -3
  93. data/lib/rigor/cache/rbs_constant_table.rb +0 -3
  94. data/lib/rigor/cache/rbs_descriptor.rb +79 -11
  95. data/lib/rigor/cache/rbs_environment.rb +8 -4
  96. data/lib/rigor/cache/rbs_known_class_names.rb +0 -3
  97. data/lib/rigor/cache/store.rb +77 -32
  98. data/lib/rigor/cli/annotate_command.rb +5 -6
  99. data/lib/rigor/cli/check_command.rb +57 -10
  100. data/lib/rigor/cli/check_invocation.rb +6 -11
  101. data/lib/rigor/cli/check_runner_factory.rb +1 -5
  102. data/lib/rigor/cli/coverage_command.rb +1 -1
  103. data/lib/rigor/cli/coverage_mutation.rb +12 -3
  104. data/lib/rigor/cli/coverage_scan.rb +1 -5
  105. data/lib/rigor/cli/diff_command.rb +1 -1
  106. data/lib/rigor/cli/doc_links.rb +3 -3
  107. data/lib/rigor/cli/docs_command.rb +2 -2
  108. data/lib/rigor/cli/doctor_command.rb +85 -38
  109. data/lib/rigor/cli/effects_command.rb +2 -2
  110. data/lib/rigor/cli/effects_diff_renderer.rb +3 -3
  111. data/lib/rigor/cli/effects_snapshot_command.rb +1 -1
  112. data/lib/rigor/cli/explain_command.rb +34 -1
  113. data/lib/rigor/cli/fused_protection_report.rb +7 -1
  114. data/lib/rigor/cli/lsp_command.rb +1 -1
  115. data/lib/rigor/cli/mcp_command.rb +1 -1
  116. data/lib/rigor/cli/measurement_integrity_warning.rb +4 -5
  117. data/lib/rigor/cli/mutation_fork_scan.rb +6 -6
  118. data/lib/rigor/cli/mutation_protection_report.rb +7 -1
  119. data/lib/rigor/cli/plugin_command.rb +1 -1
  120. data/lib/rigor/cli/plugins_command.rb +2 -1
  121. data/lib/rigor/cli/prism_colorizer.rb +2 -2
  122. data/lib/rigor/cli/protection_fork_scan.rb +6 -6
  123. data/lib/rigor/cli/show_bleedingedge_command.rb +1 -1
  124. data/lib/rigor/cli/sig_gen_command.rb +2 -2
  125. data/lib/rigor/cli/skill_command.rb +1 -1
  126. data/lib/rigor/cli/skill_deep_probe.rb +4 -4
  127. data/lib/rigor/cli/skill_describe.rb +36 -35
  128. data/lib/rigor/cli/trace_command.rb +4 -4
  129. data/lib/rigor/cli/trace_renderer.rb +5 -6
  130. data/lib/rigor/cli/triage_command.rb +1 -1
  131. data/lib/rigor/cli/type_of_command.rb +10 -6
  132. data/lib/rigor/cli/type_scan_command.rb +4 -4
  133. data/lib/rigor/cli/unused_command.rb +11 -3
  134. data/lib/rigor/cli/upgrade_command.rb +1 -1
  135. data/lib/rigor/cli.rb +68 -6
  136. data/lib/rigor/config_audit.rb +30 -5
  137. data/lib/rigor/configuration/severity_profile.rb +6 -6
  138. data/lib/rigor/configuration.rb +43 -4
  139. data/lib/rigor/effects/attribution.rb +1 -3
  140. data/lib/rigor/effects/config_envelopes.rb +6 -8
  141. data/lib/rigor/effects/definition_lines.rb +25 -6
  142. data/lib/rigor/effects/effect_table.rb +0 -1
  143. data/lib/rigor/effects/entry_points.rb +0 -2
  144. data/lib/rigor/effects/envelope_check.rb +8 -8
  145. data/lib/rigor/effects/envelope_index.rb +5 -8
  146. data/lib/rigor/effects/framework_units.rb +4 -6
  147. data/lib/rigor/effects/identity.rb +4 -6
  148. data/lib/rigor/effects/inline_anchor.rb +7 -7
  149. data/lib/rigor/effects/label_intent.rb +3 -4
  150. data/lib/rigor/effects/liskov_check.rb +8 -8
  151. data/lib/rigor/effects/method_key.rb +1 -1
  152. data/lib/rigor/effects/plugin_facts.rb +4 -6
  153. data/lib/rigor/effects/propagator.rb +2 -3
  154. data/lib/rigor/effects/scanner.rb +1 -1
  155. data/lib/rigor/effects/signature_sources.rb +3 -5
  156. data/lib/rigor/effects/snapshot.rb +10 -10
  157. data/lib/rigor/effects/snapshot_diff.rb +1 -1
  158. data/lib/rigor/effects/unit_scan.rb +10 -10
  159. data/lib/rigor/effects/unknown_label_check.rb +3 -8
  160. data/lib/rigor/effects/unknown_label_report.rb +3 -4
  161. data/lib/rigor/environment/bundle_sig_discovery.rb +6 -6
  162. data/lib/rigor/environment/class_registry.rb +3 -1
  163. data/lib/rigor/environment/failure_slot.rb +2 -2
  164. data/lib/rigor/environment/installed_gem_set.rb +85 -0
  165. data/lib/rigor/environment/lockfile_resolver.rb +51 -4
  166. data/lib/rigor/environment/missing_gem_constant_index.rb +4 -4
  167. data/lib/rigor/environment/rbs_collection_discovery.rb +5 -5
  168. data/lib/rigor/environment/rbs_coverage_report.rb +5 -5
  169. data/lib/rigor/environment/rbs_hierarchy.rb +3 -1
  170. data/lib/rigor/environment/rbs_loader.rb +388 -59
  171. data/lib/rigor/environment.rb +105 -20
  172. data/lib/rigor/flow_contribution/merger.rb +0 -2
  173. data/lib/rigor/flow_contribution.rb +11 -13
  174. data/lib/rigor/inference/acceptance.rb +153 -8
  175. data/lib/rigor/inference/block_parameter_binder.rb +2 -3
  176. data/lib/rigor/inference/body_fixpoint.rb +5 -5
  177. data/lib/rigor/inference/budget_trace.rb +1 -5
  178. data/lib/rigor/inference/captured_locals.rb +2 -3
  179. data/lib/rigor/inference/closure_escape_analyzer.rb +2 -4
  180. data/lib/rigor/inference/coverage_scanner.rb +4 -6
  181. data/lib/rigor/inference/dynamic_origin.rb +1 -1
  182. data/lib/rigor/inference/element_read_widening.rb +168 -0
  183. data/lib/rigor/inference/expression_typer.rb +763 -143
  184. data/lib/rigor/inference/fork_map.rb +5 -7
  185. data/lib/rigor/inference/hkt_reducer.rb +2 -3
  186. data/lib/rigor/inference/hkt_registry.rb +4 -7
  187. data/lib/rigor/inference/index_write_widening.rb +1 -5
  188. data/lib/rigor/inference/macro_block_self_type.rb +1 -4
  189. data/lib/rigor/inference/method_dispatcher/block_folding.rb +2 -9
  190. data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +1 -1
  191. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +305 -19
  192. data/lib/rigor/inference/method_dispatcher/data_folding.rb +1 -1
  193. data/lib/rigor/inference/method_dispatcher/file_folding.rb +1 -1
  194. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +2 -2
  195. data/lib/rigor/inference/method_dispatcher/json_folding.rb +1 -1
  196. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -1
  197. data/lib/rigor/inference/method_dispatcher/math_folding.rb +41 -3
  198. data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -5
  199. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +61 -47
  200. data/lib/rigor/inference/method_dispatcher/random_folding.rb +82 -0
  201. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +165 -31
  202. data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +25 -4
  203. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +2 -8
  204. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +1 -1
  205. data/lib/rigor/inference/method_dispatcher/set_folding.rb +1 -1
  206. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +1 -1
  207. data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +1 -1
  208. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +1 -2
  209. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +4 -5
  210. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +85 -4
  211. data/lib/rigor/inference/method_dispatcher/time_folding.rb +1 -1
  212. data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +1 -2
  213. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  214. data/lib/rigor/inference/method_dispatcher.rb +12 -13
  215. data/lib/rigor/inference/method_parameter_binder.rb +4 -6
  216. data/lib/rigor/inference/multi_target_binder.rb +1 -3
  217. data/lib/rigor/inference/mutation_rejoin.rb +168 -0
  218. data/lib/rigor/inference/mutation_widening.rb +100 -51
  219. data/lib/rigor/inference/narrowing.rb +299 -46
  220. data/lib/rigor/inference/optimistic_origin.rb +1 -7
  221. data/lib/rigor/inference/origin_lookup.rb +2 -3
  222. data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
  223. data/lib/rigor/inference/pre_eval_constants.rb +6 -5
  224. data/lib/rigor/inference/precision_scanner.rb +3 -4
  225. data/lib/rigor/inference/project_patched_methods.rb +2 -2
  226. data/lib/rigor/inference/project_patched_scanner.rb +3 -3
  227. data/lib/rigor/inference/protection_scanner.rb +1 -2
  228. data/lib/rigor/inference/range_constant.rb +57 -0
  229. data/lib/rigor/inference/rbs_type_translator.rb +49 -11
  230. data/lib/rigor/inference/receiver_alias.rb +3 -3
  231. data/lib/rigor/inference/refinement_mutation.rb +72 -0
  232. data/lib/rigor/inference/scope_indexer.rb +688 -151
  233. data/lib/rigor/inference/statement_evaluator.rb +221 -50
  234. data/lib/rigor/inference/string_mutation.rb +60 -0
  235. data/lib/rigor/inference/struct_fold_safety.rb +6 -7
  236. data/lib/rigor/inference/synthetic_method_index.rb +1 -2
  237. data/lib/rigor/inference/synthetic_method_scanner.rb +4 -6
  238. data/lib/rigor/inference/version_guard.rb +28 -21
  239. data/lib/rigor/inference/void_origin.rb +3 -3
  240. data/lib/rigor/inference/void_tail_summary.rb +2 -4
  241. data/lib/rigor/language_server/buffer_table.rb +4 -4
  242. data/lib/rigor/language_server/completion_provider.rb +1 -1
  243. data/lib/rigor/language_server/debouncer.rb +1 -1
  244. data/lib/rigor/language_server/diagnostic_publisher.rb +4 -4
  245. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  246. data/lib/rigor/language_server/folding_range_provider.rb +1 -1
  247. data/lib/rigor/language_server/hover_provider.rb +1 -1
  248. data/lib/rigor/language_server/hover_renderer.rb +2 -2
  249. data/lib/rigor/language_server/incremental_sync.rb +6 -6
  250. data/lib/rigor/language_server/project_context.rb +2 -5
  251. data/lib/rigor/language_server/publish_batcher.rb +2 -2
  252. data/lib/rigor/language_server/selection_range_provider.rb +2 -2
  253. data/lib/rigor/language_server/server.rb +9 -9
  254. data/lib/rigor/language_server/signature_help_provider.rb +1 -1
  255. data/lib/rigor/language_server/uri.rb +1 -1
  256. data/lib/rigor/plugin/base.rb +90 -21
  257. data/lib/rigor/plugin/bundled_catalog.rb +167 -0
  258. data/lib/rigor/plugin/effect_attribution.rb +7 -7
  259. data/lib/rigor/plugin/effect_entry_points.rb +3 -3
  260. data/lib/rigor/plugin/fact_store.rb +6 -6
  261. data/lib/rigor/plugin/io_boundary.rb +93 -8
  262. data/lib/rigor/plugin/isolation.rb +29 -6
  263. data/lib/rigor/plugin/loader.rb +31 -12
  264. data/lib/rigor/plugin/macro/heredoc_template.rb +1 -1
  265. data/lib/rigor/plugin/macro/trait_registry.rb +1 -1
  266. data/lib/rigor/plugin/manifest.rb +23 -1
  267. data/lib/rigor/plugin/registry.rb +42 -12
  268. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +12 -1
  269. data/lib/rigor/plugin/trust_policy.rb +2 -4
  270. data/lib/rigor/plugin/type_node_resolver.rb +3 -3
  271. data/lib/rigor/plugin_gap_advisory.rb +96 -0
  272. data/lib/rigor/project_environment.rb +138 -0
  273. data/lib/rigor/protection/analysis_guard.rb +93 -14
  274. data/lib/rigor/protection/closure_kill_oracle.rb +67 -23
  275. data/lib/rigor/protection/dependency_closure.rb +5 -8
  276. data/lib/rigor/protection/diagnostic_oracle.rb +2 -2
  277. data/lib/rigor/protection/discovery_seed.rb +11 -11
  278. data/lib/rigor/protection/kill_signature.rb +2 -4
  279. data/lib/rigor/protection/measurement_integrity.rb +1 -3
  280. data/lib/rigor/protection/mutation_cache.rb +6 -9
  281. data/lib/rigor/protection/mutation_scanner.rb +12 -15
  282. data/lib/rigor/protection/mutator.rb +2 -1
  283. data/lib/rigor/protection/test_suite_oracle.rb +5 -5
  284. data/lib/rigor/rbs_extended/conformance_checker.rb +4 -3
  285. data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
  286. data/lib/rigor/rbs_extended/reporter.rb +36 -10
  287. data/lib/rigor/rbs_extended.rb +61 -21
  288. data/lib/rigor/reflection/constant_path.rb +126 -0
  289. data/lib/rigor/reflection.rb +26 -10
  290. data/lib/rigor/runtime/jit.rb +6 -8
  291. data/lib/rigor/scope/discovery_index.rb +30 -0
  292. data/lib/rigor/scope.rb +203 -4
  293. data/lib/rigor/sig_gen/generator.rb +115 -22
  294. data/lib/rigor/sig_gen/layout_index.rb +3 -4
  295. data/lib/rigor/sig_gen/meta_class_shape.rb +3 -5
  296. data/lib/rigor/sig_gen/observation_collector.rb +17 -15
  297. data/lib/rigor/sig_gen/path_mapper.rb +4 -6
  298. data/lib/rigor/sig_gen/rbs_validity.rb +4 -4
  299. data/lib/rigor/sig_gen/renderer.rb +4 -5
  300. data/lib/rigor/sig_gen/skip_reason_catalog.rb +111 -0
  301. data/lib/rigor/sig_gen/superclass_spelling.rb +27 -0
  302. data/lib/rigor/sig_gen/type_elaborator.rb +1 -3
  303. data/lib/rigor/sig_gen/writer.rb +23 -6
  304. data/lib/rigor/signature_path_audit.rb +153 -6
  305. data/lib/rigor/source/literals.rb +0 -23
  306. data/lib/rigor/source/node_children.rb +0 -2
  307. data/lib/rigor/source/node_locator.rb +5 -11
  308. data/lib/rigor/source/node_walker.rb +2 -5
  309. data/lib/rigor/triage/catalogue.rb +1 -3
  310. data/lib/rigor/triage.rb +3 -5
  311. data/lib/rigor/type/accepts_result.rb +24 -6
  312. data/lib/rigor/type/combinator.rb +68 -5
  313. data/lib/rigor/type/data_class.rb +2 -2
  314. data/lib/rigor/type/data_instance.rb +4 -4
  315. data/lib/rigor/type/float_range.rb +128 -0
  316. data/lib/rigor/type/hash_shape.rb +5 -5
  317. data/lib/rigor/type/integer_range.rb +13 -8
  318. data/lib/rigor/type/nominal.rb +8 -2
  319. data/lib/rigor/type/refined.rb +3 -3
  320. data/lib/rigor/type/struct_class.rb +3 -3
  321. data/lib/rigor/type/struct_instance.rb +4 -4
  322. data/lib/rigor/type.rb +1 -0
  323. data/lib/rigor/type_node/generic.rb +1 -1
  324. data/lib/rigor/type_node/range_literal.rb +25 -0
  325. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  326. data/lib/rigor/type_node.rb +1 -0
  327. data/lib/rigor/version.rb +1 -1
  328. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +0 -4
  329. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +0 -1
  330. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +0 -2
  331. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -0
  332. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +0 -4
  333. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +3 -5
  334. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +1 -4
  335. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +1 -0
  336. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +33 -30
  337. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +0 -1
  338. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -3
  339. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +28 -2
  340. data/plugins/rigor-actionpack/sig/action_controller.rbs +71 -0
  341. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +0 -4
  342. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
  343. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +0 -1
  344. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +0 -2
  345. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +1 -1
  346. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +1 -0
  347. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +3 -4
  348. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +1 -2
  349. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +1 -2
  350. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +42 -7
  351. data/plugins/rigor-activerecord/sig/active_record/framework.rbs +123 -0
  352. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +1 -0
  353. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +9 -2
  354. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +119 -14
  355. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +1 -0
  356. data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +1 -0
  357. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +9 -7
  358. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +7 -3
  359. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +1 -0
  360. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +8 -6
  361. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +11 -7
  362. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -11
  363. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +9 -4
  364. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +1 -0
  365. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +0 -5
  366. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +0 -1
  367. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +0 -1
  368. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +8 -1
  369. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +36 -4
  370. data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +1 -0
  371. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +8 -6
  372. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +6 -3
  373. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +1 -0
  374. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -1
  375. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +0 -3
  376. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +1 -0
  377. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +0 -5
  378. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -2
  379. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +0 -1
  380. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +0 -1
  381. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +1 -0
  382. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +0 -12
  383. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +1 -3
  384. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +0 -1
  385. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +1 -0
  386. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/acronyms.rb +5 -5
  387. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -10
  388. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +3 -3
  389. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +2 -2
  390. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +2 -2
  391. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +2 -2
  392. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +9 -10
  393. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +2 -3
  394. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -0
  395. data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +1 -0
  396. data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +13 -1
  397. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +128 -3
  398. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +0 -3
  399. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +0 -3
  400. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -5
  401. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +2 -3
  402. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +1 -0
  403. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +0 -3
  404. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +1 -0
  405. data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +1 -0
  406. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +1 -9
  407. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +1 -0
  408. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +0 -4
  409. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/effects.rb +1 -1
  410. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +1 -1
  411. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +0 -1
  412. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +0 -2
  413. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +1 -0
  414. data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -0
  415. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +2 -5
  416. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +1 -4
  417. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +3 -9
  418. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +4 -4
  419. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +1 -2
  420. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +4 -5
  421. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +7 -5
  422. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +5 -5
  423. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +1 -0
  424. data/sig/rigor/analysis/reachability/scan_cache.rbs +12 -0
  425. data/sig/rigor/cache.rbs +2 -3
  426. data/sig/rigor/environment.rbs +5 -0
  427. data/sig/rigor/inference.rbs +3 -10
  428. data/sig/rigor/plugin/io_boundary.rbs +4 -0
  429. data/sig/rigor/plugin/manifest.rbs +2 -1
  430. data/sig/rigor/reflection.rbs +3 -1
  431. data/sig/rigor/scope.rbs +24 -3
  432. data/sig/rigor/sig_gen/skip_reason_catalog.rbs +14 -0
  433. data/sig/rigor/type.rbs +27 -1
  434. data/sig/rigor.rbs +1 -0
  435. data/skills/rigor-ask/SKILL.md +8 -6
  436. data/skills/rigor-plugin-author/references/02-walker-and-types.md +13 -2
  437. data/skills/rigor-project-init/SKILL.md +6 -3
  438. data/skills/rigor-project-init/references/02-configure.md +10 -6
  439. data/skills/rigor-project-init/references/06-agent-contract.md +67 -0
  440. data/skills/rigor-type-oracle/SKILL.md +228 -0
  441. data/skills/rigor-type-oracle/references/01-oracle-commands.md +261 -0
  442. data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +52 -0
  443. data/skills/rigor-type-oracle/references/03-gap-protocol.md +128 -0
  444. data/skills/rigor-unused-adjudicate/SKILL.md +8 -5
  445. metadata +26 -3
  446. data/lib/rigor/cli/probe_environment.rb +0 -85
  447. data/sig/rigor/inference/builtins/numeric_catalog.rbs +0 -3
@@ -61,9 +61,33 @@ module Rigor
61
61
 
62
62
  COMMENT_LINE = /\A\s*#/
63
63
 
64
- private_constant :ECHOED_ANNOTATION, :DECLARATION_LINE, :COMMENT_LINE
64
+ # The RBS annotation the synthesizer writes on every member whose type upstream DEFAULTED rather
65
+ # than read off an annotation (issue #823). Its meaning is normative in
66
+ # `docs/type-specification/rbs-extended.md`: the declaration states the member's presence and its
67
+ # parameters, and says nothing about what it returns, so the engine infers the return from the body
68
+ # instead of adopting the placeholder. `Rigor::RbsExtended.inferred_return?` is the reader.
69
+ INFERRED_RETURN_ANNOTATION = "rigor:v1:inferred-return"
65
70
 
66
- # @param require_magic_comment [Boolean] when `false` (the default since ADR-93 WD1), the magic
71
+ # The stand-in type handed to upstream's `Writer#default_type`, and the whole reason this plugin can
72
+ # tell a defaulted type from an authored one. `RBS::Inline::Writer` substitutes `default_type`
73
+ # wherever the author wrote nothing — `return_type || default_type` in `RubyDef#method_overloads`,
74
+ # `attribute_type || default_type` in `RubyAttr#rbs` — and leaves an authored type alone. Rendering
75
+ # with a distinctive stand-in therefore makes "the author did not write this" a fact in the output
76
+ # rather than a guess about it: a member whose return renders as this name was defaulted, and one
77
+ # that renders `untyped` was WRITTEN `untyped` by its author (`#: (String) -> untyped`), which is a
78
+ # real contract and stays one.
79
+ #
80
+ # `default_type` is upstream's own public accessor for exactly this substitution, and `Writer.write`
81
+ # yields the writer to configure it, so nothing here reaches into upstream internals. The stand-in
82
+ # never survives into the contributed RBS — {#mark_inferred_returns} rewrites every occurrence back
83
+ # to `untyped` — which is not cosmetic: an undeclared type alias makes `RBS::DefinitionBuilder` raise
84
+ # `NoTypeFoundError` for the whole class, the same failure ADR-93 WD4's Finding 5 measured on
85
+ # `#:nodoc:`.
86
+ DEFAULTED_TYPE_NAME = :rigor__inline_defaulted
87
+
88
+ private_constant :ECHOED_ANNOTATION, :DECLARATION_LINE, :COMMENT_LINE, :DEFAULTED_TYPE_NAME
89
+
90
+ # @param require_magic_comment — when `false` (the default since ADR-93 WD1), the magic
67
91
  # comment is not required and the file is processed only if it actually carries an annotation — see
68
92
  # {#annotated?}. When `true` (the old ADR-32 WD2 gate), only files opening with
69
93
  # `# rbs_inline: enabled` are processed, and upstream's opt-in semantics apply verbatim.
@@ -95,7 +119,8 @@ module Rigor
95
119
  return nil if parsed.nil?
96
120
 
97
121
  uses, decls, rbs_decls = parsed
98
- rendered = reattach_declaration_annotations(::RBS::Inline::Writer.write(uses, decls, rbs_decls))
122
+ rendered = mark_inferred_returns(render_with_defaulted_marker(uses, decls, rbs_decls))
123
+ rendered = reattach_declaration_annotations(rendered)
99
124
  return nil if rendered.nil? || rendered.strip.empty?
100
125
 
101
126
  notices = unhonoured_annotations(result)
@@ -109,6 +134,106 @@ module Rigor
109
134
 
110
135
  private
111
136
 
137
+ # Upstream's writer, told to substitute {DEFAULTED_TYPE_NAME} instead of `untyped` wherever the
138
+ # author wrote no type. See that constant for why the distinction is worth a stand-in.
139
+ def render_with_defaulted_marker(uses, decls, rbs_decls)
140
+ marker = ::RBS::Types::Alias.new(
141
+ name: ::RBS::TypeName.new(namespace: ::RBS::Namespace.empty, name: DEFAULTED_TYPE_NAME),
142
+ args: [],
143
+ location: nil
144
+ )
145
+ ::RBS::Inline::Writer.write(uses, decls, rbs_decls) { |writer| writer.default_type = marker }
146
+ end
147
+
148
+ # Issue #823 — the sibling rule. ADR-93 WD1 gates synthesis on a file that carries an annotation;
149
+ # inside such a file upstream emits a full `def f: (untyped x) -> untyped` skeleton for every
150
+ # unannotated `def`, and Rigor trusts an accepted signature over body inference. So one `# @rbs`
151
+ # anywhere in a file used to retype every OTHER method in it to `untyped` — the same mechanism
152
+ # ADR-93 WD1 measured project-wide before the file gate landed (mail 26 → 42 diagnostics), just
153
+ # scoped to the annotated file. An annotation on one method must not change the typing of its
154
+ # siblings in either direction.
155
+ #
156
+ # The skeleton is kept — that is what PR #779's member-level drop got wrong, because a partially
157
+ # declared class reads to RBS as a fully declared one (32 `call.undefined-method` and 12
158
+ # `call.wrong-arity` on `new`, and 44 classes to `Dynamic[top]` behind cross-file references that
159
+ # stopped resolving). What changes is what the skeleton CLAIMS: a defaulted type slot is rewritten
160
+ # back to `untyped` and its member is annotated {INFERRED_RETURN_ANNOTATION}, so the declaration
161
+ # keeps the class's full method surface, `new`'s arity and every cross-file name, and states nothing
162
+ # about the return. The engine reads the annotation in `RbsDispatch` and falls through to the same
163
+ # body-inference tier an undeclared method takes.
164
+ #
165
+ # Two properties make the rewrite safe rather than clever:
166
+ #
167
+ # - the stand-in is a token this plugin injected, so replacing it is exact — it can only appear
168
+ # where upstream substituted it (a comment echoing the same word would merely read `untyped`);
169
+ # - the lines to annotate come from `RBS::Parser` reading the writer's own output, not from a
170
+ # pattern over it, and the replacement is length-preserving per line, so the parser's line numbers
171
+ # still address the same members after it.
172
+ #
173
+ # Marking is best-effort and the rewrite is not: if the probe parse is skipped or fails, the file
174
+ # still ships with today's `untyped` skeleton rather than an unresolvable alias (see
175
+ # {DEFAULTED_TYPE_NAME}). That asymmetry is why the encoding guard is shaped the way it is. Invalid
176
+ # UTF-8 does not reach here today — the RDoc-directive scan above raises on it first, and {#call}'s
177
+ # rescue routes the file to WD6 — but the guarded parse is the one `RbsLoader.add_virtual_rbs`
178
+ # documents as HANG-prone on pre-4.1 rbs lexers, and a hang escapes every rescue there is. So the
179
+ # probe is skipped for a byte sequence it could hang on, while the substitution runs over bytes and
180
+ # therefore always completes.
181
+ def mark_inferred_returns(rendered)
182
+ return rendered if rendered.nil? || !rendered.b.include?(DEFAULTED_TYPE_NAME.to_s)
183
+
184
+ marked_lines = rendered.valid_encoding? ? defaulted_member_lines(rendered) : Set.new
185
+ scrubbed = rendered.b.gsub(DEFAULTED_TYPE_NAME.to_s, "untyped").force_encoding(rendered.encoding)
186
+ return scrubbed if marked_lines.empty?
187
+
188
+ scrubbed.lines.each_with_index.flat_map do |line, index|
189
+ next line unless marked_lines.include?(index + 1)
190
+
191
+ ["#{line[/\A[ \t]*/]}%a{#{INFERRED_RETURN_ANNOTATION}}\n", line]
192
+ end.join
193
+ end
194
+
195
+ # The 1-based line of every member in `rendered` whose return type (or, for an attribute, whose
196
+ # type) is the defaulted stand-in. A member's `location.start_line` is the line its `def` /
197
+ # `attr_*` keyword sits on, which is where the annotation has to go.
198
+ def defaulted_member_lines(rendered)
199
+ buffer = ::RBS::Buffer.new(name: "(rigor: inline defaulted-type probe)", content: rendered)
200
+ _, _directives, decls = ::RBS::Parser.parse_signature(buffer)
201
+ collect_defaulted_member_lines(decls, Set.new)
202
+ rescue ::StandardError
203
+ # The writer's own output failing to parse is a condition the loader already handles (ADR-32 WD6
204
+ # fail-soft drops the entry). Declining to mark here keeps that the only failure.
205
+ Set.new
206
+ end
207
+
208
+ def collect_defaulted_member_lines(nodes, lines)
209
+ nodes.each do |node|
210
+ next unless node.respond_to?(:members)
211
+
212
+ node.members.each do |member|
213
+ line = defaulted_member_line(member)
214
+ lines << line if line
215
+ end
216
+ collect_defaulted_member_lines(node.members.grep(::RBS::AST::Declarations::Base), lines)
217
+ end
218
+ lines
219
+ end
220
+
221
+ def defaulted_member_line(member)
222
+ case member
223
+ when ::RBS::AST::Members::MethodDefinition
224
+ return nil unless member.overloads.any? { |o| defaulted_type?(o.method_type.type.return_type) }
225
+ when ::RBS::AST::Members::Attribute
226
+ return nil unless defaulted_type?(member.type)
227
+ else
228
+ return nil
229
+ end
230
+ member.location&.start_line
231
+ end
232
+
233
+ def defaulted_type?(type)
234
+ type.is_a?(::RBS::Types::Alias) && type.name.name == DEFAULTED_TYPE_NAME
235
+ end
236
+
112
237
  # Re-attaches a class- or module-level `%a{…}` that upstream's writer dropped (#452).
113
238
  #
114
239
  # `RBS::Inline::Writer` emits a member's annotations as real annotation lines and a *declaration's*
@@ -20,9 +20,6 @@ module Rigor
20
20
 
21
21
  module_function
22
22
 
23
- # @param path [String]
24
- # @param root [Prism::Node]
25
- # @return [Array<Diagnostic>]
26
23
  def diagnose(path:, root:)
27
24
  diagnostics = []
28
25
  ScopeWalker.collect_scopes(root).each do |outer|
@@ -45,13 +45,11 @@ module Rigor
45
45
  # ADR-52 slice 5a — every `let` / `subject` name declared anywhere in the file, across all describe
46
46
  # scopes. Feeds the plugin's `dynamic_return file_methods:` gate: the engine only consults the rule
47
47
  # for a call whose name appears here; the precise line-scoped resolution stays in `let_block_at`.
48
- # @return [Array<Symbol>]
49
48
  def let_names
50
49
  @records.flat_map { |rec| rec.lets.keys }.uniq
51
50
  end
52
51
 
53
52
  # Resolves a `let` name at the given line by walking records innermost to outermost.
54
- # @return [Prism::BlockNode, nil]
55
53
  def let_block_at(line, name)
56
54
  name_sym = name.to_sym
57
55
  records_at(line).reverse.each do |rec|
@@ -62,7 +60,6 @@ module Rigor
62
60
 
63
61
  # Resolves the `describe`-anchor constant name at the given line. The innermost describe with a
64
62
  # constant anchor wins; describe-with-String anchors are skipped.
65
- # @return [String, nil]
66
63
  def describe_const_at(line)
67
64
  records_at(line).reverse.each do |rec|
68
65
  return rec.describe_const if rec.describe_const
@@ -30,12 +30,8 @@ module Rigor
30
30
 
31
31
  module_function
32
32
 
33
- # @param block_node [Prism::BlockNode]
34
- # @param describe_const [String, nil]
35
- # @param factory_index [Object, nil] something responding to `find(factory_name)` and returning an
33
+ # @param factory_index — something responding to `find(factory_name)` and returning an
36
34
  # entry that responds to `model_class`.
37
- # @param environment [Rigor::Environment, nil]
38
- # @return [Rigor::Type, nil]
39
35
  def resolve(block_node, describe_const:, factory_index:, environment:)
40
36
  tail = body_tail(block_node)
41
37
  return nil if tail.nil?
@@ -38,13 +38,12 @@ module Rigor
38
38
  module MatcherAnalyzer
39
39
  module_function
40
40
 
41
- # @param call_node [Prism::CallNode] the call whose contribution we're computing. Returns nil when
41
+ # @param call_node the call whose contribution we're computing. Returns nil when
42
42
  # the call shape does not match `expect(local).to matcher`.
43
- # @param environment [Rigor::Environment, nil] the surrounding environment used to resolve a
43
+ # @param environment the surrounding environment used to resolve a
44
44
  # matcher's class-name argument to a `Type::Nominal`. When nil, class-name resolution falls back
45
45
  # to a bare `Nominal[<name>]` carrier (sound — the receiver constant may be a user class not in
46
46
  # RBS).
47
- # @return [Rigor::FlowContribution, nil]
48
47
  def contribution_for(call_node, environment:)
49
48
  verb = assertion_verb(call_node)
50
49
  return nil if verb.nil?
@@ -48,6 +48,7 @@ module Rigor
48
48
  class Rspec < Rigor::Plugin::Base
49
49
  manifest(
50
50
  id: "rspec",
51
+ target_gems: ["rspec-core"],
51
52
  version: "0.3.0",
52
53
  description: "Validates RSpec `let` / `subject` declarations within each scope; " \
53
54
  "narrows expect(x).to <matcher> assertions downstream in `it` bodies; " \
@@ -40,9 +40,6 @@ module Rigor
40
40
  # The matcher violation for a single CallNode, or nil when the node is not a
41
41
  # `have_http_status(<int|symbol>)` matcher or its argument is statically valid. ADR-37: the engine
42
42
  # owns the walk, so this is per-node logic — no traversal here.
43
- #
44
- # @param call_node [Prism::Node]
45
- # @return [Violation, nil]
46
43
  def violation_for(call_node)
47
44
  return nil unless call_to_matcher?(call_node)
48
45
 
@@ -39,6 +39,7 @@ module Rigor
39
39
  class RspecRails < Rigor::Plugin::Base
40
40
  manifest(
41
41
  id: "rspec-rails",
42
+ target_gems: ["rspec-rails"],
42
43
  version: "0.1.0",
43
44
  description: "Validates rspec-rails behavioral matchers (have_http_status floor)."
44
45
  )
@@ -8,6 +8,7 @@ module Rigor
8
8
  class SassC < Base
9
9
  manifest(
10
10
  id: "sassc",
11
+ target_gems: ["sassc"],
11
12
  version: "0.1.0",
12
13
  description: "Models SassC native bindings, nominal pointer typedefs, and struct unions.",
13
14
  signature_paths: ["sig"]
@@ -72,23 +72,15 @@ module Rigor
72
72
 
73
73
  module_function
74
74
 
75
- # @param path [String]
76
- # @param root [Prism::Node]
77
- # @param model_index [Hash{String => Hash}, nil] the `:model_index` fact value — the flat
75
+ # @param model_index — the `:model_index` fact value — the flat
78
76
  # `class_name => { table:, columns:, associations:, ... }` Hash `rigor-activerecord` publishes
79
77
  # (ADR-9's "the value is data, not objects" contract; see `Activerecord#index_to_published_hash`).
80
78
  # When nil the analyzer falls silent.
81
- # @return [Array<Diagnostic>]
82
79
  # The matcher violations for a single call node (0..1), or `[]` when it is not a shoulda matcher,
83
80
  # sits outside any `describe <ModelConst>` block, or the model is unknown. ADR-37: the engine owns
84
81
  # the walk; the model anchor comes from the node-rule `NodeContext` ancestors (the innermost
85
82
  # enclosing `describe`-with-constant — nested const describes override, `describe ".method"`
86
83
  # string blocks inherit), exactly as the former recursive anchor propagation did.
87
- #
88
- # @param matcher_call [Prism::Node]
89
- # @param ancestors [Array<Prism::Node>]
90
- # @param model_index [Hash{String => Hash}, nil]
91
- # @return [Array<Violation>]
92
84
  def violations_for(matcher_call:, ancestors:, model_index:)
93
85
  return [] if model_index.nil?
94
86
  return [] unless matcher_invocation?(matcher_call)
@@ -47,6 +47,7 @@ module Rigor
47
47
  class ShouldaMatchers < Rigor::Plugin::Base
48
48
  manifest(
49
49
  id: "shoulda-matchers",
50
+ target_gems: ["shoulda-matchers"],
50
51
  version: "0.1.0",
51
52
  description: "Validates shoulda-matchers matchers (validate_presence_of / belong_to / " \
52
53
  "have_many / have_db_column / ...) against :model_index from " \
@@ -34,10 +34,6 @@ module Rigor
34
34
 
35
35
  # The worker-call violations for a single call node (0..2), or `[]` when the node is not a
36
36
  # `<Worker>.perform_*` entry call on a known worker. ADR-37: the engine owns the walk.
37
- #
38
- # @param call_node [Prism::Node]
39
- # @param worker_index [WorkerIndex]
40
- # @return [Array<Violation>]
41
37
  def violations_for(call_node:, worker_index:)
42
38
  return [] unless call_node.is_a?(Prism::CallNode) && entry_call?(call_node)
43
39
 
@@ -52,7 +52,7 @@ module Rigor
52
52
 
53
53
  module_function
54
54
 
55
- # @param marker_modules [Array<String>] the modules a worker includes — the plugin's own
55
+ # @param marker_modules the modules a worker includes — the plugin's own
56
56
  # `worker_marker_modules:` config, so one project setting drives the arity check and these rows.
57
57
  def attributions(marker_modules)
58
58
  marker_modules.flat_map do |marker|
@@ -51,7 +51,7 @@ module Rigor
51
51
  @schedule_paths = schedule_paths
52
52
  end
53
53
 
54
- # @return [Array<String>] the class names named by a `class:` key in a schedule entry, sorted and
54
+ # @return the class names named by a `class:` key in a schedule entry, sorted and
55
55
  # unique. NOT yet intersected with the discovered workers — the caller does that.
56
56
  def worker_names
57
57
  names = Set.new
@@ -33,7 +33,6 @@ module Rigor
33
33
  @marker_modules = marker_modules.to_set { |name| strip_root(name.to_s) }
34
34
  end
35
35
 
36
- # @return [WorkerIndex]
37
36
  def discover
38
37
  candidates = []
39
38
  ruby_files_under(@search_paths).each do |path|
@@ -39,8 +39,6 @@ module Rigor
39
39
  # never `"::WelcomeWorker"`; see {WorkerDiscoverer}), while a QUERY may legitimately arrive rooted:
40
40
  # `::WelcomeWorker.perform_async(1)` renders its receiver as `"::WelcomeWorker"`. The root marker is
41
41
  # dropped here, once, so no caller needs a `find(name) || find("::#{name}")` retry (#621).
42
- #
43
- # @return [Entry, nil]
44
42
  def find(class_name)
45
43
  @by_name[strip_leading_namespace(class_name.to_s)]
46
44
  end
@@ -54,6 +54,7 @@ module Rigor
54
54
  class Sidekiq < Rigor::Plugin::Base
55
55
  manifest(
56
56
  id: "sidekiq",
57
+ target_gems: ["sidekiq"],
57
58
  # Bumped 2026-08-16 — publishes `:reachability_roots` for `rigor unused` (ADR-102 WD3): the workers
58
59
  # a schedule file enqueues by name, which no `perform_async` call site writes down.
59
60
  # Bumped 2026-09-01 (#534 item 4) — the enqueue methods return the jid `String`.
@@ -51,6 +51,7 @@ module Rigor
51
51
  class Sinatra < Rigor::Plugin::Base
52
52
  manifest(
53
53
  id: "sinatra",
54
+ target_gems: ["sinatra"],
54
55
  version: "0.1.0",
55
56
  description: "Recognises Sinatra's class-level route DSL via ADR-16 Tier A.",
56
57
  block_as_methods: [
@@ -37,8 +37,7 @@ module Rigor
37
37
  # run, so the same Prism node object is seen in both the `dynamic_return` rule and
38
38
  # `diagnostics_for_file`.
39
39
  module AbsurdRecognizer
40
- # @param call_node [Prism::CallNode]
41
- # @return [Boolean] true when `call_node` is `T.absurd(x)`.
40
+ # @return true when `call_node` is `T.absurd(x)`.
42
41
  def self.absurd_call?(call_node)
43
42
  return false unless call_node.is_a?(Prism::CallNode)
44
43
  return false unless call_node.name == :absurd
@@ -50,9 +49,7 @@ module Rigor
50
49
  arguments&.size == 1
51
50
  end
52
51
 
53
- # @param call_node [Prism::CallNode]
54
- # @param scope [Rigor::Scope, nil]
55
- # @return [Boolean] true when the discriminant has been narrowed to `bot` (the branch is
52
+ # @return true when the discriminant has been narrowed to `bot` (the branch is
56
53
  # unreachable, so `T.absurd` is correct). The caller suppresses the `absurd-reachable` diagnostic
57
54
  # in this case.
58
55
  def self.exhaustive?(call_node, scope)
@@ -53,10 +53,7 @@ module Rigor
53
53
 
54
54
  module_function
55
55
 
56
- # @param call_node [Prism::CallNode]
57
- # @param scope [Rigor::Scope]
58
- # @param plugin_id [String] used for the contribution's `provenance.source_family`.
59
- # @return [Rigor::FlowContribution, nil]
56
+ # @param plugin_id — used for the contribution's `provenance.source_family`.
60
57
  def recognize(call_node:, scope:, plugin_id:)
61
58
  return nil unless TypeTranslator.sorbet_t_namespaced?(call_node.receiver)
62
59
  return nil unless SORBET_ASSERTIONS.include?(call_node.name)
@@ -23,7 +23,6 @@ module Rigor
23
23
  @frozen_after_build = false
24
24
  end
25
25
 
26
- # @param signature [MethodSignature]
27
26
  def record(signature)
28
27
  raise "Catalog already finalised" if @frozen_after_build
29
28
 
@@ -31,10 +30,9 @@ module Rigor
31
30
  @entries[key] = signature
32
31
  end
33
32
 
34
- # @param class_name [String] the class / module that carries the mixin (`class Post; include Foo;
33
+ # @param class_name the class / module that carries the mixin (`class Post; include Foo;
35
34
  # end` records under `"Post"`).
36
- # @param kind [:include, :extend]
37
- # @param module_name [String] the textual name of the mixed-in module as it appeared at the
35
+ # @param module_name the textual name of the mixed-in module as it appeared at the
38
36
  # include / extend site (`"Foo"`, `"Foo::Bar"`, `"::Foo"`).
39
37
  def record_mixin(class_name:, kind:, module_name:)
40
38
  raise "Catalog already finalised" if @frozen_after_build
@@ -55,13 +53,11 @@ module Rigor
55
53
  freeze
56
54
  end
57
55
 
58
- # @return [MethodSignature, nil]
59
56
  def lookup(class_name:, method_name:, kind:)
60
57
  @entries[key_for(class_name, method_name, kind)]
61
58
  end
62
59
 
63
- # @param class_name [String]
64
- # @return [Hash{Symbol => Array<String>}] frozen mapping `{ include: [...], extend: [...] }`.
60
+ # @return frozen mapping `{ include: [...], extend: [...] }`.
65
61
  # Returns {EMPTY_MIXINS} when no mixins were recorded.
66
62
  def mixins_for(class_name)
67
63
  @mixins[class_name] || EMPTY_MIXINS
@@ -77,8 +73,6 @@ module Rigor
77
73
  # static assertion vocabulary), and the precise `(class, kind)` lookup stays in the rule block.
78
74
  # Computed fresh per call — the plugin memoises the resolved set, and `freeze!` freezes the
79
75
  # catalog itself so a lazy memo ivar here would raise.
80
- #
81
- # @return [Array<Symbol>]
82
76
  def method_names
83
77
  @entries.keys.map { |key| key[1] }.uniq
84
78
  end
@@ -26,10 +26,10 @@ module Rigor
26
26
 
27
27
  module_function
28
28
 
29
- # @param root [Prism::Node] the file's program node.
30
- # @param catalog [Catalog] mutable; signatures are recorded into it.
31
- # @param path [String] file path used for diagnostic provenance.
32
- # @return [Array<ParseError>] errors observed during the walk; empty when the file is sig-clean.
29
+ # @param root the file's program node.
30
+ # @param catalog mutable; signatures are recorded into it.
31
+ # @param path file path used for diagnostic provenance.
32
+ # @return errors observed during the walk; empty when the file is sig-clean.
33
33
  def walk(root:, catalog:, path:)
34
34
  state = State.new(catalog: catalog, path: path)
35
35
  walk_node(root, state, lexical_path: [], in_singleton_class: false)
@@ -44,8 +44,7 @@ module Rigor
44
44
 
45
45
  module_function
46
46
 
47
- # @param sig_call [Prism::CallNode] the `sig { ... }` / `sig do ... end` call.
48
- # @return [ParseResult, ParseError]
47
+ # @param sig_call the `sig { ... }` / `sig do ... end` call.
49
48
  def parse(sig_call)
50
49
  return ParseError.new(reason: :no_block, node: sig_call) if sig_call.block.nil?
51
50
 
@@ -30,8 +30,8 @@ module Rigor
30
30
 
31
31
  module_function
32
32
 
33
- # @param contents [String] raw file contents.
34
- # @return [Symbol] one of {VALID_LEVELS}; defaults to {DEFAULT_LEVEL} for sigil-less or
33
+ # @param contents raw file contents.
34
+ # @return one of {VALID_LEVELS}; defaults to {DEFAULT_LEVEL} for sigil-less or
35
35
  # malformed-sigil files.
36
36
  def detect(contents)
37
37
  return DEFAULT_LEVEL if contents.nil? || contents.empty?
@@ -52,14 +52,13 @@ module Rigor
52
52
  DEFAULT_LEVEL
53
53
  end
54
54
 
55
- # @param level [Symbol]
56
- # @return [Boolean] true when `# typed: ignore`. The harvest pipeline calls this to short-circuit
55
+ # @return true when `# typed: ignore`. The harvest pipeline calls this to short-circuit
57
56
  # walking the file's AST.
58
57
  def ignored?(level)
59
58
  level == :ignore
60
59
  end
61
60
 
62
- # @return [Boolean] true when `level` is at or above the `# typed: true` mark. Used by the
61
+ # @return true when `level` is at or above the `# typed: true` mark. Used by the
63
62
  # `enforce_sigil` config gate (default `true`): with the gate on, only files marked `:true` /
64
63
  # `:strict` / `:strong` contribute their sigs to the catalog. The `:false` (and sigil-less)
65
64
  # levels still get walked (so RBI files outside the project can be loaded regardless), but their
@@ -52,8 +52,7 @@ module Rigor
52
52
 
53
53
  module_function
54
54
 
55
- # @param node [Prism::Node, nil]
56
- # @return [Rigor::Type] never `nil`; unrecognised forms degrade to `Type::Combinator.untyped`.
55
+ # @return never `nil`; unrecognised forms degrade to `Type::Combinator.untyped`.
57
56
  def translate(node)
58
57
  return Rigor::Type::Combinator.untyped if node.nil?
59
58
 
@@ -67,7 +66,6 @@ module Rigor
67
66
  end
68
67
  end
69
68
 
70
- # @param node [Prism::ConstantReadNode]
71
69
  def translate_constant_read(node)
72
70
  name = node.name.to_s
73
71
  return Rigor::Type::Combinator.untyped if name.empty?
@@ -75,7 +73,6 @@ module Rigor
75
73
  Rigor::Type::Combinator.nominal_of(name)
76
74
  end
77
75
 
78
- # @param node [Prism::ConstantPathNode]
79
76
  def translate_constant_path(node)
80
77
  name = constant_path_name(node)
81
78
  return degraded if name.nil?
@@ -233,7 +230,12 @@ module Rigor
233
230
 
234
231
  pairs << [element.key.unescaped.to_sym, translate(element.value)]
235
232
  end
236
- Rigor::Type::Combinator.hash_shape_of(pairs)
233
+ # `.to_h`: `hash_shape_of` validates its argument is a Hash and raises `ArgumentError` on the
234
+ # array-of-pairs form, so `sig { returns({name: String}) }` degraded to `Dynamic[top]` through
235
+ # the plugin's rescue instead of producing a shape. Found by #673, when relaxing
236
+ # `call.argument-type-mismatch` to check the positional arguments of keyword-bearing
237
+ # signatures made the call site visible.
238
+ Rigor::Type::Combinator.hash_shape_of(pairs.to_h)
237
239
  end
238
240
 
239
241
  # Renders a constant-path node (`Foo::Bar`, `::Foo::Bar`) as a `::`-joined String. Mirrors the
@@ -57,6 +57,7 @@ module Rigor
57
57
  class Sorbet < Rigor::Plugin::Base
58
58
  manifest(
59
59
  id: "sorbet",
60
+ target_gems: ["sorbet-runtime"],
60
61
  version: "0.1.0",
61
62
  description: "Ingests Sorbet `sig` blocks as method-signature contributions.",
62
63
  config_schema: {
@@ -442,11 +443,10 @@ module Rigor
442
443
  EMPTY_CATALOG_BUNDLE = { catalog: Catalog.new.freeze!, sigil_by_path: {}, parse_errors_by_path: {} }.freeze
443
444
  private_constant :EMPTY_CATALOG_BUNDLE
444
445
 
445
- # @param root [String] directory or single file.
446
- # @param catalog [Catalog]
447
- # @param sigil_by_path [Hash{String=>Symbol}] accumulator: harvested file → detected sigil level.
448
- # @param parse_errors_by_path [Hash{String=>Array<Hash>}] accumulator: file `{kind:,line:,column:}` tuples.
449
- # @param extensions [Array<String>] file extensions to accept (e.g. `[".rb"]` for project source,
446
+ # @param root directory or single file.
447
+ # @param sigil_by_path — accumulator: harvested file → detected sigil level.
448
+ # @param parse_errors_by_path accumulator: file → `{kind:,line:,column:}` tuples.
449
+ # @param extensions file extensions to accept (e.g. `[".rb"]` for project source,
450
450
  # `[".rbi"]` for Sorbet RBI tree).
451
451
  def harvest_path(root, catalog, sigil_by_path, parse_errors_by_path, extensions)
452
452
  absolute = canonicalize(root)
@@ -43,6 +43,7 @@ module Rigor
43
43
  class Statesman < Rigor::Plugin::Base
44
44
  manifest(
45
45
  id: "statesman",
46
+ target_gems: ["statesman"],
46
47
  version: "0.1.0",
47
48
  description: "Validates state-machine transition references against declared states.",
48
49
  config_schema: {
@@ -0,0 +1,12 @@
1
+ module Rigor
2
+ module Analysis
3
+ module Reachability
4
+ class ScanCache
5
+ def self.open: (untyped, ?target_ruby: untyped) -> Rigor::Analysis::Reachability::ScanCache
6
+ def self.source_digest: () -> String
7
+ def initialize: (path: untyped, header: untyped) -> void
8
+ def save: () -> nil
9
+ end
10
+ end
11
+ end
12
+ end
data/sig/rigor/cache.rbs CHANGED
@@ -16,9 +16,8 @@ module Rigor
16
16
  def self.fetch: (loader: untyped, store: untyped) -> untyped
17
17
 
18
18
  # The generation cap this producer declares to `Cache::Store#evict!`
19
- # (issue #151). `rigor sig-gen` infers the literal `2` from the base
20
- # body; the contract is the wider `Integer`, since a subclass may
21
- # override it.
19
+ # (issue #151). The base body answers `2`; the contract is the wider
20
+ # `Integer`, since the cap is a knob a subclass may turn.
22
21
  def self.generation_cap: () -> Integer
23
22
  end
24
23
  end
@@ -33,6 +33,7 @@ module Rigor
33
33
  def constant_for_name: (String | Symbol name) -> Type::t?
34
34
  def class_known?: (String | Symbol name) -> bool
35
35
  def rbs_module?: (String | Symbol name) -> bool
36
+ def singleton_extended_modules: (String | Symbol name) -> Array[String]
36
37
  def class_ordering: (String | Symbol lhs, String | Symbol rhs) -> ordering
37
38
  def reflection: () -> untyped?
38
39
  def missing_rbs_gem_owner: (String | Symbol root_constant_name) -> String?
@@ -53,11 +54,14 @@ module Rigor
53
54
  attr_reader libraries: Array[String]
54
55
  attr_reader signature_paths: Array[String | _ToPath]
55
56
  attr_reader cache_store: untyped?
57
+ # sig-gen gap: #160 — sig-gen emits no `attr_reader` for the loader's readers; typed as the initializer stores it.
58
+ attr_reader deferred_signature_paths: Array[Pathname]
56
59
 
57
60
  def self.default: () -> RbsLoader
58
61
  def self.reset_default!: () -> void
59
62
  def self.build_env_for: (libraries: Array[String], signature_paths: Array[String | _ToPath]) -> untyped
60
63
  def self.vendored_gem_sig_paths: () -> Array[Pathname]
64
+ def self.capability_role_sig_paths: () -> ([Pathname] | [])
61
65
  def self.vendored_gem_names: () -> Array[String]
62
66
 
63
67
  def initialize: (?libraries: Array[String], ?signature_paths: Array[String | _ToPath], ?cache_store: untyped?) -> void
@@ -72,6 +76,7 @@ module Rigor
72
76
  def project_declared_class?: (String | Symbol class_name) -> bool
73
77
  def class_ordering: (String | Symbol lhs, String | Symbol rhs) -> ordering
74
78
  def ancestor_names_for: (String | Symbol class_name) -> Array[String]
79
+ def singleton_extended_module_names: (String | Symbol class_name) -> Array[String]
75
80
  def constant_type: (String name) -> Type::t?
76
81
  def constant_names: () -> Array[String]
77
82
  def each_known_class_name: () { (String name) -> void } -> untyped