rigortype 0.3.7 → 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 (450) 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 +96 -15
  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 +39 -4
  22. data/docs/manual/03-configuration.md +11 -0
  23. data/docs/manual/04-diagnostics.md +12 -1
  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 +69 -13
  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 +100 -29
  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 +316 -40
  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 +430 -77
  76. data/lib/rigor/analysis/runner/project_pre_passes.rb +12 -6
  77. data/lib/rigor/analysis/runner/run_snapshots.rb +13 -3
  78. data/lib/rigor/analysis/runner.rb +200 -43
  79. data/lib/rigor/analysis/worker_session.rb +36 -12
  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 +166 -30
  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 +70 -0
  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_environment_marshal_patch.rb +39 -0
  97. data/lib/rigor/cache/rbs_known_class_names.rb +0 -3
  98. data/lib/rigor/cache/store.rb +85 -33
  99. data/lib/rigor/cli/annotate_command.rb +5 -6
  100. data/lib/rigor/cli/check_command.rb +57 -10
  101. data/lib/rigor/cli/check_invocation.rb +6 -11
  102. data/lib/rigor/cli/check_runner_factory.rb +1 -5
  103. data/lib/rigor/cli/coverage_command.rb +1 -1
  104. data/lib/rigor/cli/coverage_mutation.rb +12 -3
  105. data/lib/rigor/cli/coverage_scan.rb +1 -5
  106. data/lib/rigor/cli/diff_command.rb +1 -1
  107. data/lib/rigor/cli/doc_links.rb +3 -3
  108. data/lib/rigor/cli/docs_command.rb +2 -2
  109. data/lib/rigor/cli/doctor_command.rb +85 -38
  110. data/lib/rigor/cli/effects_command.rb +2 -2
  111. data/lib/rigor/cli/effects_diff_renderer.rb +3 -3
  112. data/lib/rigor/cli/effects_snapshot_command.rb +1 -1
  113. data/lib/rigor/cli/explain_command.rb +34 -1
  114. data/lib/rigor/cli/fused_protection_report.rb +7 -1
  115. data/lib/rigor/cli/lsp_command.rb +1 -1
  116. data/lib/rigor/cli/mcp_command.rb +1 -1
  117. data/lib/rigor/cli/measurement_integrity_warning.rb +4 -5
  118. data/lib/rigor/cli/mutation_fork_scan.rb +6 -6
  119. data/lib/rigor/cli/mutation_protection_report.rb +7 -1
  120. data/lib/rigor/cli/plugin_command.rb +1 -1
  121. data/lib/rigor/cli/plugins_command.rb +2 -1
  122. data/lib/rigor/cli/prism_colorizer.rb +2 -2
  123. data/lib/rigor/cli/protection_fork_scan.rb +6 -6
  124. data/lib/rigor/cli/show_bleedingedge_command.rb +1 -1
  125. data/lib/rigor/cli/sig_gen_command.rb +31 -2
  126. data/lib/rigor/cli/skill_command.rb +1 -1
  127. data/lib/rigor/cli/skill_deep_probe.rb +4 -4
  128. data/lib/rigor/cli/skill_describe.rb +36 -35
  129. data/lib/rigor/cli/trace_command.rb +4 -4
  130. data/lib/rigor/cli/trace_renderer.rb +5 -6
  131. data/lib/rigor/cli/triage_command.rb +1 -1
  132. data/lib/rigor/cli/type_of_command.rb +10 -6
  133. data/lib/rigor/cli/type_scan_command.rb +4 -4
  134. data/lib/rigor/cli/unused_command.rb +11 -3
  135. data/lib/rigor/cli/upgrade_command.rb +1 -1
  136. data/lib/rigor/cli.rb +68 -6
  137. data/lib/rigor/config_audit.rb +30 -5
  138. data/lib/rigor/configuration/severity_profile.rb +6 -6
  139. data/lib/rigor/configuration.rb +43 -4
  140. data/lib/rigor/effects/attribution.rb +1 -3
  141. data/lib/rigor/effects/config_envelopes.rb +6 -8
  142. data/lib/rigor/effects/definition_lines.rb +25 -6
  143. data/lib/rigor/effects/effect_table.rb +0 -1
  144. data/lib/rigor/effects/entry_points.rb +0 -2
  145. data/lib/rigor/effects/envelope_check.rb +8 -8
  146. data/lib/rigor/effects/envelope_index.rb +5 -8
  147. data/lib/rigor/effects/framework_units.rb +4 -6
  148. data/lib/rigor/effects/identity.rb +4 -6
  149. data/lib/rigor/effects/inline_anchor.rb +7 -7
  150. data/lib/rigor/effects/label_intent.rb +3 -4
  151. data/lib/rigor/effects/liskov_check.rb +8 -8
  152. data/lib/rigor/effects/method_key.rb +1 -1
  153. data/lib/rigor/effects/plugin_facts.rb +4 -6
  154. data/lib/rigor/effects/propagator.rb +2 -3
  155. data/lib/rigor/effects/scanner.rb +1 -1
  156. data/lib/rigor/effects/signature_sources.rb +3 -5
  157. data/lib/rigor/effects/snapshot.rb +10 -10
  158. data/lib/rigor/effects/snapshot_diff.rb +1 -1
  159. data/lib/rigor/effects/unit_scan.rb +10 -10
  160. data/lib/rigor/effects/unknown_label_check.rb +3 -8
  161. data/lib/rigor/effects/unknown_label_report.rb +3 -4
  162. data/lib/rigor/environment/bundle_sig_discovery.rb +6 -6
  163. data/lib/rigor/environment/class_registry.rb +3 -1
  164. data/lib/rigor/environment/failure_slot.rb +28 -0
  165. data/lib/rigor/environment/installed_gem_set.rb +85 -0
  166. data/lib/rigor/environment/lockfile_resolver.rb +51 -4
  167. data/lib/rigor/environment/missing_gem_constant_index.rb +4 -4
  168. data/lib/rigor/environment/rbs_collection_discovery.rb +5 -5
  169. data/lib/rigor/environment/rbs_coverage_report.rb +5 -5
  170. data/lib/rigor/environment/rbs_hierarchy.rb +3 -1
  171. data/lib/rigor/environment/rbs_loader.rb +613 -113
  172. data/lib/rigor/environment.rb +189 -27
  173. data/lib/rigor/flow_contribution/merger.rb +0 -2
  174. data/lib/rigor/flow_contribution.rb +11 -13
  175. data/lib/rigor/inference/acceptance.rb +153 -8
  176. data/lib/rigor/inference/block_parameter_binder.rb +2 -3
  177. data/lib/rigor/inference/body_fixpoint.rb +5 -5
  178. data/lib/rigor/inference/budget_trace.rb +1 -5
  179. data/lib/rigor/inference/captured_locals.rb +2 -3
  180. data/lib/rigor/inference/closure_escape_analyzer.rb +2 -4
  181. data/lib/rigor/inference/coverage_scanner.rb +4 -6
  182. data/lib/rigor/inference/dynamic_origin.rb +1 -1
  183. data/lib/rigor/inference/element_read_widening.rb +168 -0
  184. data/lib/rigor/inference/expression_typer.rb +763 -143
  185. data/lib/rigor/inference/fork_map.rb +5 -7
  186. data/lib/rigor/inference/hkt_reducer.rb +2 -3
  187. data/lib/rigor/inference/hkt_registry.rb +19 -10
  188. data/lib/rigor/inference/hkt_sugar_translator.rb +15 -21
  189. data/lib/rigor/inference/index_write_widening.rb +1 -5
  190. data/lib/rigor/inference/macro_block_self_type.rb +1 -4
  191. data/lib/rigor/inference/method_dispatcher/block_folding.rb +2 -9
  192. data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +1 -1
  193. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +305 -19
  194. data/lib/rigor/inference/method_dispatcher/data_folding.rb +1 -1
  195. data/lib/rigor/inference/method_dispatcher/file_folding.rb +1 -1
  196. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +2 -2
  197. data/lib/rigor/inference/method_dispatcher/json_folding.rb +1 -1
  198. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -1
  199. data/lib/rigor/inference/method_dispatcher/math_folding.rb +41 -3
  200. data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -5
  201. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +61 -47
  202. data/lib/rigor/inference/method_dispatcher/random_folding.rb +82 -0
  203. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +165 -31
  204. data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +25 -4
  205. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +2 -8
  206. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +1 -1
  207. data/lib/rigor/inference/method_dispatcher/set_folding.rb +1 -1
  208. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +1 -1
  209. data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +1 -1
  210. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +1 -2
  211. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +4 -5
  212. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +85 -4
  213. data/lib/rigor/inference/method_dispatcher/time_folding.rb +1 -1
  214. data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +1 -2
  215. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  216. data/lib/rigor/inference/method_dispatcher.rb +12 -13
  217. data/lib/rigor/inference/method_parameter_binder.rb +4 -6
  218. data/lib/rigor/inference/multi_target_binder.rb +1 -3
  219. data/lib/rigor/inference/mutation_rejoin.rb +168 -0
  220. data/lib/rigor/inference/mutation_widening.rb +100 -51
  221. data/lib/rigor/inference/narrowing.rb +299 -46
  222. data/lib/rigor/inference/optimistic_origin.rb +1 -7
  223. data/lib/rigor/inference/origin_lookup.rb +2 -3
  224. data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
  225. data/lib/rigor/inference/pre_eval_constants.rb +6 -5
  226. data/lib/rigor/inference/precision_scanner.rb +3 -4
  227. data/lib/rigor/inference/project_patched_methods.rb +2 -2
  228. data/lib/rigor/inference/project_patched_scanner.rb +3 -3
  229. data/lib/rigor/inference/protection_scanner.rb +1 -2
  230. data/lib/rigor/inference/range_constant.rb +57 -0
  231. data/lib/rigor/inference/rbs_type_translator.rb +49 -11
  232. data/lib/rigor/inference/receiver_alias.rb +3 -3
  233. data/lib/rigor/inference/refinement_mutation.rb +72 -0
  234. data/lib/rigor/inference/scope_indexer.rb +688 -151
  235. data/lib/rigor/inference/statement_evaluator.rb +221 -50
  236. data/lib/rigor/inference/string_mutation.rb +60 -0
  237. data/lib/rigor/inference/struct_fold_safety.rb +6 -7
  238. data/lib/rigor/inference/synthetic_method_index.rb +1 -2
  239. data/lib/rigor/inference/synthetic_method_scanner.rb +4 -6
  240. data/lib/rigor/inference/version_guard.rb +28 -21
  241. data/lib/rigor/inference/void_origin.rb +3 -3
  242. data/lib/rigor/inference/void_tail_summary.rb +2 -4
  243. data/lib/rigor/language_server/buffer_table.rb +4 -4
  244. data/lib/rigor/language_server/completion_provider.rb +1 -1
  245. data/lib/rigor/language_server/debouncer.rb +1 -1
  246. data/lib/rigor/language_server/diagnostic_publisher.rb +4 -4
  247. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  248. data/lib/rigor/language_server/folding_range_provider.rb +1 -1
  249. data/lib/rigor/language_server/hover_provider.rb +1 -1
  250. data/lib/rigor/language_server/hover_renderer.rb +2 -2
  251. data/lib/rigor/language_server/incremental_sync.rb +6 -6
  252. data/lib/rigor/language_server/project_context.rb +2 -5
  253. data/lib/rigor/language_server/publish_batcher.rb +2 -2
  254. data/lib/rigor/language_server/selection_range_provider.rb +2 -2
  255. data/lib/rigor/language_server/server.rb +9 -9
  256. data/lib/rigor/language_server/signature_help_provider.rb +1 -1
  257. data/lib/rigor/language_server/uri.rb +1 -1
  258. data/lib/rigor/plugin/base.rb +90 -21
  259. data/lib/rigor/plugin/bundled_catalog.rb +167 -0
  260. data/lib/rigor/plugin/effect_attribution.rb +7 -7
  261. data/lib/rigor/plugin/effect_entry_points.rb +3 -3
  262. data/lib/rigor/plugin/fact_store.rb +6 -6
  263. data/lib/rigor/plugin/io_boundary.rb +93 -8
  264. data/lib/rigor/plugin/isolation.rb +29 -6
  265. data/lib/rigor/plugin/loader.rb +31 -12
  266. data/lib/rigor/plugin/macro/heredoc_template.rb +1 -1
  267. data/lib/rigor/plugin/macro/trait_registry.rb +1 -1
  268. data/lib/rigor/plugin/manifest.rb +23 -1
  269. data/lib/rigor/plugin/registry.rb +67 -14
  270. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +12 -1
  271. data/lib/rigor/plugin/trust_policy.rb +2 -4
  272. data/lib/rigor/plugin/type_node_resolver.rb +3 -3
  273. data/lib/rigor/plugin_gap_advisory.rb +96 -0
  274. data/lib/rigor/project_environment.rb +138 -0
  275. data/lib/rigor/protection/analysis_guard.rb +93 -14
  276. data/lib/rigor/protection/closure_kill_oracle.rb +67 -23
  277. data/lib/rigor/protection/dependency_closure.rb +5 -8
  278. data/lib/rigor/protection/diagnostic_oracle.rb +2 -2
  279. data/lib/rigor/protection/discovery_seed.rb +11 -11
  280. data/lib/rigor/protection/kill_signature.rb +2 -4
  281. data/lib/rigor/protection/measurement_integrity.rb +1 -3
  282. data/lib/rigor/protection/mutation_cache.rb +6 -9
  283. data/lib/rigor/protection/mutation_scanner.rb +12 -15
  284. data/lib/rigor/protection/mutator.rb +2 -1
  285. data/lib/rigor/protection/test_suite_oracle.rb +5 -5
  286. data/lib/rigor/rbs_extended/conformance_checker.rb +4 -3
  287. data/lib/rigor/rbs_extended/envelope_scanner.rb +12 -13
  288. data/lib/rigor/rbs_extended/hkt_directives.rb +16 -1
  289. data/lib/rigor/rbs_extended/reporter.rb +122 -16
  290. data/lib/rigor/rbs_extended.rb +67 -22
  291. data/lib/rigor/reflection/constant_path.rb +126 -0
  292. data/lib/rigor/reflection.rb +26 -10
  293. data/lib/rigor/runtime/jit.rb +6 -8
  294. data/lib/rigor/scope/discovery_index.rb +30 -0
  295. data/lib/rigor/scope.rb +203 -4
  296. data/lib/rigor/sig_gen/generator.rb +181 -102
  297. data/lib/rigor/sig_gen/layout_index.rb +3 -4
  298. data/lib/rigor/sig_gen/meta_class_shape.rb +3 -5
  299. data/lib/rigor/sig_gen/observation_collector.rb +17 -15
  300. data/lib/rigor/sig_gen/path_mapper.rb +4 -6
  301. data/lib/rigor/sig_gen/rbs_validity.rb +4 -4
  302. data/lib/rigor/sig_gen/renderer.rb +17 -11
  303. data/lib/rigor/sig_gen/skip_reason_catalog.rb +111 -0
  304. data/lib/rigor/sig_gen/superclass_spelling.rb +27 -0
  305. data/lib/rigor/sig_gen/type_elaborator.rb +1 -3
  306. data/lib/rigor/sig_gen/writer.rb +23 -6
  307. data/lib/rigor/signature_path_audit.rb +153 -6
  308. data/lib/rigor/source/literals.rb +0 -23
  309. data/lib/rigor/source/node_children.rb +0 -2
  310. data/lib/rigor/source/node_locator.rb +5 -11
  311. data/lib/rigor/source/node_walker.rb +2 -5
  312. data/lib/rigor/triage/catalogue.rb +1 -3
  313. data/lib/rigor/triage.rb +3 -5
  314. data/lib/rigor/type/accepts_result.rb +24 -6
  315. data/lib/rigor/type/combinator.rb +68 -5
  316. data/lib/rigor/type/data_class.rb +2 -2
  317. data/lib/rigor/type/data_instance.rb +4 -4
  318. data/lib/rigor/type/float_range.rb +128 -0
  319. data/lib/rigor/type/hash_shape.rb +5 -5
  320. data/lib/rigor/type/integer_range.rb +13 -8
  321. data/lib/rigor/type/nominal.rb +8 -2
  322. data/lib/rigor/type/refined.rb +3 -3
  323. data/lib/rigor/type/struct_class.rb +3 -3
  324. data/lib/rigor/type/struct_instance.rb +4 -4
  325. data/lib/rigor/type.rb +1 -0
  326. data/lib/rigor/type_node/generic.rb +1 -1
  327. data/lib/rigor/type_node/range_literal.rb +25 -0
  328. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  329. data/lib/rigor/type_node.rb +1 -0
  330. data/lib/rigor/version.rb +1 -1
  331. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +0 -4
  332. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +0 -1
  333. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +0 -2
  334. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -0
  335. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +0 -4
  336. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +3 -5
  337. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +1 -4
  338. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +1 -0
  339. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +33 -30
  340. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +0 -1
  341. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -3
  342. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +28 -2
  343. data/plugins/rigor-actionpack/sig/action_controller.rbs +71 -0
  344. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +0 -4
  345. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
  346. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +0 -1
  347. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +0 -2
  348. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +1 -1
  349. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +1 -0
  350. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +3 -4
  351. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +1 -2
  352. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +1 -2
  353. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +42 -7
  354. data/plugins/rigor-activerecord/sig/active_record/framework.rbs +123 -0
  355. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +1 -0
  356. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +9 -2
  357. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +119 -14
  358. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +1 -0
  359. data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +1 -0
  360. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +9 -7
  361. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +7 -3
  362. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +1 -0
  363. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +8 -6
  364. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +11 -7
  365. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -11
  366. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +9 -4
  367. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +1 -0
  368. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +0 -5
  369. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +0 -1
  370. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +0 -1
  371. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +8 -1
  372. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +36 -4
  373. data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +1 -0
  374. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +8 -6
  375. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +6 -3
  376. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +1 -0
  377. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -1
  378. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +0 -3
  379. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +1 -0
  380. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +0 -5
  381. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -2
  382. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +0 -1
  383. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +0 -1
  384. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +1 -0
  385. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +0 -12
  386. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +1 -3
  387. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +0 -1
  388. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +1 -0
  389. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/acronyms.rb +5 -5
  390. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -10
  391. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +3 -3
  392. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +2 -2
  393. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +2 -2
  394. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +2 -2
  395. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +9 -10
  396. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +2 -3
  397. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -0
  398. data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +1 -0
  399. data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +13 -1
  400. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +128 -3
  401. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +0 -3
  402. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +0 -3
  403. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -5
  404. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +2 -3
  405. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +1 -0
  406. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +0 -3
  407. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +1 -0
  408. data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +1 -0
  409. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +1 -9
  410. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +1 -0
  411. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +0 -4
  412. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/effects.rb +1 -1
  413. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +1 -1
  414. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +0 -1
  415. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +0 -2
  416. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +1 -0
  417. data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -0
  418. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +2 -5
  419. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +1 -4
  420. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +3 -9
  421. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +4 -4
  422. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +1 -2
  423. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +4 -5
  424. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +7 -5
  425. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +5 -5
  426. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +1 -0
  427. data/sig/rigor/analysis/reachability/scan_cache.rbs +12 -0
  428. data/sig/rigor/cache.rbs +2 -3
  429. data/sig/rigor/environment.rbs +11 -0
  430. data/sig/rigor/inference.rbs +3 -10
  431. data/sig/rigor/plugin/io_boundary.rbs +4 -0
  432. data/sig/rigor/plugin/manifest.rbs +2 -1
  433. data/sig/rigor/reflection.rbs +3 -1
  434. data/sig/rigor/scope.rbs +24 -3
  435. data/sig/rigor/sig_gen/skip_reason_catalog.rbs +14 -0
  436. data/sig/rigor/type.rbs +27 -1
  437. data/sig/rigor.rbs +1 -0
  438. data/skills/rigor-ask/SKILL.md +8 -6
  439. data/skills/rigor-plugin-author/references/02-walker-and-types.md +13 -2
  440. data/skills/rigor-project-init/SKILL.md +6 -3
  441. data/skills/rigor-project-init/references/02-configure.md +10 -6
  442. data/skills/rigor-project-init/references/06-agent-contract.md +67 -0
  443. data/skills/rigor-type-oracle/SKILL.md +228 -0
  444. data/skills/rigor-type-oracle/references/01-oracle-commands.md +261 -0
  445. data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +52 -0
  446. data/skills/rigor-type-oracle/references/03-gap-protocol.md +128 -0
  447. data/skills/rigor-unused-adjudicate/SKILL.md +8 -5
  448. metadata +28 -3
  449. data/lib/rigor/cli/probe_environment.rb +0 -85
  450. data/sig/rigor/inference/builtins/numeric_catalog.rbs +0 -3
@@ -42,9 +42,16 @@ module Rigor
42
42
  # prepare row is appended to every sequential run, it would refuse every run for the life of the
43
43
  # process.
44
44
  # - `:rbs_build` — the analysis ran to completion and every rule fired, over a type universe missing one
45
- # class (`rbs.coverage.definition-build-failed`) or all of them
46
- # (`rbs.coverage.environment-build-failed`). A degradation the user causes and the diagnostic itself
47
- # reports; a project can sit on it for a release while it fixes its `sig/`.
45
+ # class (`rbs.coverage.definition-build-failed`), all of them
46
+ # (`rbs.coverage.environment-build-failed`), or only the HKT registrations the `type` aliases and the
47
+ # loaded plugins' manifests would have contributed (`rbs.coverage.hkt-scan-failed`, issues #784 and
48
+ # #791). The first two are degradations
49
+ # the user causes and the diagnostic itself reports; a project can sit on one for a release while it
50
+ # fixes its `sig/`. The third is NOT the user's: post-#783 the scan raising is an analyzer defect, and
51
+ # the overlay stage is a defect in Rigor or in a plugin the user installed, never in their `sig/`. It
52
+ # still belongs here rather than under `:check_rule`, because the question this classification
53
+ # answers is "may a consumer still read the run's diagnostics?" — and it may: every rule fired. What
54
+ # differs is whether the run is a valid measurement OF RIGOR, which is {.analyzer_defect?}'s question.
48
55
  #
49
56
  # The two consumer tiers therefore differ on purpose. The ADR-69 kill oracles arm `:check_rule` only:
50
57
  # refusing a run they could still have measured is the same "manufactures work" error as scoring an
@@ -63,10 +70,11 @@ module Rigor
63
70
  PLUGIN_SOURCE_FAMILY = :plugin_loader
64
71
  PLUGIN_RULE = "runtime-error"
65
72
 
66
- # The `rbs.coverage.*` rules that mean declared types went missing from this run — an env-wide
67
- # collapse and its per-class sibling (#696). Ordered widest consequence last, the way the two rows sit
68
- # in `docs/type-specification/diagnostic-policy.md`.
73
+ # The `rbs.coverage.*` rules that mean declared types went missing from this run — the implicit-HKT
74
+ # scan over `type` aliases (#784), a per-class definition build (#696), and the env-wide collapse.
75
+ # Ordered widest consequence last, the way the rows sit in `docs/type-specification/diagnostic-policy.md`.
69
76
  RBS_BUILD_FAILURE_RULES = %w[
77
+ rbs.coverage.hkt-scan-failed
70
78
  rbs.coverage.definition-build-failed
71
79
  rbs.coverage.environment-build-failed
72
80
  ].freeze
@@ -75,10 +83,55 @@ module Rigor
75
83
  # `:rbs_build` both leave a readable run behind — see the class doc.
76
84
  DISCARDS_FILE_ANALYSIS_REASON = :check_rule
77
85
 
86
+ # The `:rbs_build` rules whose cause is Rigor, not the user's `sig/` (issue #784). Readable for the
87
+ # user — every rule fired — but NOT a valid measurement of Rigor itself, so a harness that scores
88
+ # Rigor's behaviour should treat one of these as a crash finding the way it treats `:check_rule`, or
89
+ # a mutant that re-breaks the HKT scan (#776 was one) scores as a measurement over a silently degraded
90
+ # universe. Both Rigor-measuring harnesses consult it (#790): `tool/mutation`'s fuzz crash detector
91
+ # and {Protection::AnalysisGuard}, the ADR-69 kill oracles' refusal. Neither reads it alone — the row
92
+ # is severity-stamped like any other, so `severity_overrides:` can drop it from a run's diagnostics
93
+ # entirely, and each harness also reads {Rigor::Environment#hkt_scan_failure}, the pre-severity record
94
+ # the row is derived from. Both also replace their Environment after a defect: the registry build is
95
+ # memoised and the failure slot is first-write-wins, so an Environment that degraded once stays
96
+ # degraded for every later mutant.
97
+ ANALYZER_DEFECT_RULES = %w[
98
+ rbs.coverage.hkt-scan-failed
99
+ ].freeze
100
+
78
101
  module_function
79
102
 
80
- # @param diagnostic [Rigor::Analysis::Diagnostic]
81
- # @return [Symbol, nil] `:check_rule`, `:plugin`, `:rbs_build`, or nil for an ordinary diagnostic.
103
+ # The message `Runner#analyze_file_body` / `WorkerSession#analyze_body` fold a raised `StandardError`
104
+ # into. Built here, not at either rescue site, so the two twins cannot drift (issue #665) — and so
105
+ # the appended crash frame is derived identically on the sequential and pooled paths. Keeps the
106
+ # {CHECK_RULE_MESSAGE_PREFIX} prefix every consumer matches on; the frame is a trailing hint.
107
+ def check_rule_message(error)
108
+ base = "#{CHECK_RULE_MESSAGE_PREFIX}: #{error.class}: #{error.message}"
109
+ frame = crash_frame(error)
110
+ frame ? "#{base} (#{frame})" : base
111
+ end
112
+
113
+ # The first `lib/rigor/` backtrace frame — the raise site, path made repo-relative so it reads the
114
+ # same whether Rigor runs from a checkout or an installed gem. A LOCATION, not an attribution: a
115
+ # bundled or third-party plugin also lives under `lib/rigor/<plugin>/`, so a frame here says where
116
+ # the raise was, never whose defect it is. Falls back to the raw top frame when the crash is entirely
117
+ # inside a dependency, and to nil when there is no backtrace at all.
118
+ def crash_frame(error)
119
+ frames = error.backtrace
120
+ return nil if frames.nil? || frames.empty?
121
+
122
+ relativize_frame(frames.find { |f| f.include?("/lib/rigor/") } || frames.first)
123
+ end
124
+
125
+ # The repo-relative half of {.crash_frame}, pulled out on its own so a second caller — the #784
126
+ # `rbs.coverage.hkt-scan-failed` diagnostic, which stores its raw frame across a `Marshal` boundary
127
+ # (the fork pool) rather than deriving it fresh from a live `Exception` — can relativize the frame it
128
+ # already has without re-deriving `.crash_frame`'s "which frame" choice. Nil-safe, and a no-op (`sub`
129
+ # never matches) on a frame with no `lib/rigor/` segment at all.
130
+ def relativize_frame(frame)
131
+ frame&.sub(%r{\A.*/(lib/rigor/)}, '\1')
132
+ end
133
+
134
+ # @return `:check_rule`, `:plugin`, `:rbs_build`, or nil for an ordinary diagnostic.
82
135
  def reason(diagnostic)
83
136
  return :check_rule if diagnostic.message.to_s.start_with?(CHECK_RULE_MESSAGE_PREFIX)
84
137
  return :plugin if plugin_isolation_row?(diagnostic)
@@ -90,17 +143,20 @@ module Rigor
90
143
  # True when `diagnostic` is the rescue row that REPLACED a file's analysis — the only shape after which
91
144
  # the run's diagnostics say nothing about the code. NOT a general "something went wrong" predicate; see
92
145
  # the class doc for why `:plugin` and `:rbs_build` are excluded.
93
- #
94
- # @param diagnostic [Rigor::Analysis::Diagnostic]
95
146
  def discards_file_analysis?(diagnostic)
96
147
  reason(diagnostic) == DISCARDS_FILE_ANALYSIS_REASON
97
148
  end
98
149
 
150
+ # True when `diagnostic` reports a failure inside Rigor that left the run readable but invalid as a
151
+ # measurement of Rigor — see {ANALYZER_DEFECT_RULES}. Orthogonal to {.discards_file_analysis?}: the
152
+ # user-facing tier reads the run; the Rigor-measuring tier should refuse it (the mutation fuzz does,
153
+ # the ADR-69 kill oracle does not yet — #790).
154
+ def analyzer_defect?(diagnostic)
155
+ ANALYZER_DEFECT_RULES.include?(diagnostic.rule)
156
+ end
157
+
99
158
  # A one-line "<reason> at <path>:<line>: <message>" for a failure message, so whoever reads the raise
100
159
  # sees which shape fired and where without re-deriving it.
101
- #
102
- # @param diagnostic [Rigor::Analysis::Diagnostic]
103
- # @return [String]
104
160
  def describe(diagnostic)
105
161
  "#{reason(diagnostic) || :unknown} at #{diagnostic.path}:#{diagnostic.line}: #{diagnostic.message}"
106
162
  end
@@ -25,7 +25,7 @@ module Rigor
25
25
  @mutex = Mutex.new
26
26
  end
27
27
 
28
- # @return [Array<Entry>] frozen snapshot of the recorded boundary-cross events. Each entry is a Data
28
+ # @return frozen snapshot of the recorded boundary-cross events. Each entry is a Data
29
29
  # with `class_name` (String), `method_name` (Symbol), `gem_name` (String), and `rbs_display` (String
30
30
  # — the authoritative-side type's human-facing form, embedded into the diagnostic message).
31
31
  def entries
@@ -17,8 +17,6 @@ module Rigor
17
17
  module Builder
18
18
  module_function
19
19
 
20
- # @param dependencies [Rigor::Configuration::Dependencies]
21
- # @return [Index]
22
20
  def build(dependencies)
23
21
  return Index::EMPTY if dependencies.empty?
24
22
 
@@ -30,8 +30,6 @@ module Rigor
30
30
 
31
31
  module_function
32
32
 
33
- # @param entry [Rigor::Configuration::Dependencies::Entry]
34
- # @return [Resolved, Unresolvable]
35
33
  def resolve(entry)
36
34
  spec = locate_gem_spec(entry.gem)
37
35
  return Unresolvable.new(gem_name: entry.gem, reason: :not_in_bundle) if spec.nil?
@@ -13,19 +13,19 @@ module Rigor
13
13
  attr_reader :resolved_gems, :unresolvable, :method_catalog, :budget_exceeded,
14
14
  :class_to_gem, :budget_overrun_strategy, :gem_modes
15
15
 
16
- # @param method_catalog [Hash{[String, Symbol] => Symbol}] the flat
16
+ # @param method_catalog — => Symbol}] the flat
17
17
  # `(class_name, method_name) → :instance | :singleton` table produced by {Walker.walk}, aggregated
18
18
  # across every resolved gem in the run. The Index itself stays gem-agnostic — the per-gem
19
19
  # attribution that slice 3's cache descriptor needs lives on `Resolved`, not here.
20
- # @param budget_exceeded [Array<String>] gem names whose {Walker} run hit the per-gem catalog cap
20
+ # @param budget_exceeded — gem names whose {Walker} run hit the per-gem catalog cap
21
21
  # (slice 4). The Runner consumes this list to emit one `dynamic.dependency-source.budget-exceeded`
22
22
  # warning per gem.
23
- # @param class_to_gem [Hash<String, String>] reverse lookup `class_name → gem_name` (slice 5b). Built
23
+ # @param class_to_gem — reverse lookup `class_name → gem_name` (slice 5b). Built
24
24
  # first-write-wins: when two opt-in gems re-open the same class, the first gem owns it. The
25
25
  # dispatcher consults this map under the `:dependency_silence` budget overrun strategy so call
26
26
  # sites on a budget-exceeded gem's classes degrade to `Dynamic[top]` instead of falling through to
27
27
  # the user-class fallback.
28
- # @param gem_modes [Hash<String, Symbol>] per-gem mode table (`gem_name → :disabled | :when_missing |
28
+ # @param gem_modes — per-gem mode table (`gem_name → :disabled | :when_missing |
29
29
  # :full`). ADR-10 slice 5c consults this through {#mode_for} to identify call sites where
30
30
  # gem-source and RBS both contribute under `mode: :full`. The map is keyed on `gem_name` (not
31
31
  # class) because re-opened classes belong to the first gem they appeared in per `class_to_gem`;
@@ -56,7 +56,7 @@ module Rigor
56
56
  end
57
57
  private :normalize_catalog
58
58
 
59
- # @return [String, nil] the gem that owns `class_name` (first-write-wins); `nil` when the class isn't
59
+ # @return the gem that owns `class_name` (first-write-wins); `nil` when the class isn't
60
60
  # in any opt-in gem's catalog.
61
61
  def gem_for(class_name)
62
62
  @class_to_gem[class_name]
@@ -28,8 +28,7 @@ module Rigor
28
28
  module ReturnTypeHeuristic
29
29
  module_function
30
30
 
31
- # @param def_node [Prism::DefNode]
32
- # @return [Rigor::Type, nil] heuristic return type, or `nil` when the body's tail expression doesn't
31
+ # @return heuristic return type, or `nil` when the body's tail expression doesn't
33
32
  # match any of the recognised shapes.
34
33
  def extract(def_node)
35
34
  body = def_node.body
@@ -55,12 +55,12 @@ module Rigor
55
55
 
56
56
  module_function
57
57
 
58
- # @param gem_dir [String, Pathname] absolute path to the gem's installation directory.
59
- # @param roots [Array<String>] subdirectory names within the gem to walk (defaults to `["lib"]` per
58
+ # @param gem_dir — absolute path to the gem's installation directory.
59
+ # @param roots — subdirectory names within the gem to walk (defaults to `["lib"]` per
60
60
  # `Configuration::Dependencies::Entry`).
61
- # @param budget [Integer, Float] per-gem catalog cap (method-definition count). When unset, defaults
61
+ # @param budget — per-gem catalog cap (method-definition count). When unset, defaults
62
62
  # to `UNBOUNDED` for backwards-compatible test paths.
63
- # @return [Outcome] frozen wrapper carrying the catalog (`Hash{[class_name, method_name] =>
63
+ # @return frozen wrapper carrying the catalog (`Hash{[class_name, method_name] =>
64
64
  # :instance | :singleton}`) and a `truncated?` flag set when the walker stopped harvesting because
65
65
  # the budget was reached. Methods of identical name on the same class with different kinds (rare;
66
66
  # private API mostly) carry the kind that wins the per-class first walk.
@@ -52,17 +52,16 @@ module Rigor
52
52
  # otherwise rebuild (the probe loaded the plugins to key the entry, so it already knows it).
53
53
  Served = Data.define(:table, :sources, :registry, :plugin_facts)
54
54
 
55
- # @param configuration [Rigor::Configuration] with effects already enabled by the caller.
56
- # @param cache_root [String, nil]
55
+ # @param configuration — with effects already enabled by the caller.
57
56
  def initialize(configuration:, cache_root:)
58
57
  @configuration = configuration
59
58
  @cache_root = cache_root
60
59
  end
61
60
 
62
- # @param paths [Array<String>] the analysed set — `configuration.paths` unioned with any path
61
+ # @param paths — the analysed set — `configuration.paths` unioned with any path
63
62
  # arguments, exactly what {CLI::EffectsCommand#analyze} hands the runner, because the analysed
64
63
  # set is part of the diagnostics key.
65
- # @return [Served, nil] nil to decline, on any failure whatsoever.
64
+ # @return nil to decline, on any failure whatsoever.
66
65
  def serve(paths)
67
66
  return nil if @cache_root.nil? || !@configuration.effects_check?
68
67
 
@@ -19,8 +19,7 @@ module Rigor
19
19
 
20
20
  module_function
21
21
 
22
- # @param parse_result [Prism::ParseResult]
23
- # @return [Boolean] true when the parsed source looks like an ERB template (parse errors expected;
22
+ # @return true when the parsed source looks like an ERB template (parse errors expected;
24
23
  # analysis should skip).
25
24
  def template?(parse_result)
26
25
  source = parse_result.source.source
@@ -18,9 +18,14 @@ module Rigor
18
18
  ].freeze
19
19
 
20
20
  class Target < Data.define(:kind, :name)
21
+ # #775 — a target is an immutable value keyed by its name, and the local target is built on every
22
+ # binding (`Scope#with_local` invalidates through it) and every local-fact read: ~350k times on
23
+ # the lib self-check for a few thousand distinct names. One shared instance per name.
21
24
  def self.local(name)
22
- new(kind: :local, name: name.to_sym)
25
+ name = name.to_sym
26
+ @local_targets[name] ||= new(kind: :local, name: name)
23
27
  end
28
+ @local_targets = {}
24
29
 
25
30
  def initialize(kind:, name:)
26
31
  super(kind: kind.to_sym, name: name)
@@ -120,6 +120,22 @@ module Rigor
120
120
  appeared.freeze
121
121
  end
122
122
 
123
+ # Issue #639 — the SYMMETRIC reading of {appeared_classes}: the names whose declaration in a file
124
+ # appeared OR VANISHED. Appearance alone answered the miss side, which was the only side recording a
125
+ # `class:` edge; a bare reference that RESOLVED now records one too, and what invalidates it is the
126
+ # declaration going away. `paths` MUST therefore include REMOVED files — a removed file has no
127
+ # after-state, so its whole before-set vanished — which is the same reason
128
+ # {changed_constant_publications} diffs over `changed + removed`.
129
+ def changed_class_declarations(paths, decls_before, decls_after)
130
+ moved = Set.new
131
+ paths.each do |path|
132
+ before = decls_before[path] || Set.new
133
+ after = decls_after[path] || Set.new
134
+ moved.merge(after ^ before)
135
+ end
136
+ moved.freeze
137
+ end
138
+
123
139
  # Issue #644 — the qualified constant names whose PUBLICATION could have moved in this edit: the names
124
140
  # whose per-file census descriptor differs between the before- and after-state of any file in `paths`.
125
141
  # The value-constant twin of {appeared_classes}, and deliberately NOT its analogue — a class either
@@ -42,22 +42,22 @@ module Rigor
42
42
  end
43
43
  end
44
44
 
45
- # @param paths [Array<String>, nil] explicit analysis roots; nil (the default) uses the configuration's
45
+ # @param paths — explicit analysis roots; nil (the default) uses the configuration's
46
46
  # `paths:`.
47
- # @param environment [Rigor::Environment, nil] optional shared environment to thread into each internal
47
+ # @param environment — optional shared environment to thread into each internal
48
48
  # Runner. Long-lived callers and specs can use this to avoid rebuilding the same RBS universe for
49
49
  # every baseline / recheck / oracle run.
50
- # @param cache_store [Rigor::Cache::Store, nil] ADR-85 WD1 — the persistent cache each internal Runner
50
+ # @param cache_store — ADR-85 WD1 — the persistent cache each internal Runner
51
51
  # exposes to the RBS-env and plugin-producer tiers. A cross-process `--incremental` recheck otherwise
52
52
  # rebuilt a fresh runner with no store, so every plugin `#prepare` producer (the ADR-9/#74/ADR-60 WD3
53
53
  # record-and-validate caches) recomputed per invocation — 86% of a Rails warm incremental. Threading
54
54
  # the store lets those producers serve from disk. `nil` (the default) preserves the pre-#85 behaviour
55
55
  # the specs assert; the whole-run ADR-45 result cache stays disabled on these runs
56
56
  # (`Runner#run_result_cacheable?` excludes `record_dependencies` / `analyze_only`).
57
- # @param plugin_requirer [#call, nil] optional gem-require hook threaded into each internal Runner
57
+ # @param plugin_requirer — optional gem-require hook threaded into each internal Runner
58
58
  # (mirrors {Runner}'s parameter). nil (the default, and what the CLI passes) uses `Kernel.require`;
59
59
  # embedders and specs inject a fake so a test plugin registers without touching the real load path.
60
- # @param workers [Integer] ADR-46 — the resolved fork-pool worker count threaded into every internal
60
+ # @param workers — ADR-46 — the resolved fork-pool worker count threaded into every internal
61
61
  # analyzer, so a `--incremental` recheck's closure re-analysis parallelises exactly like the standard
62
62
  # `check` path (the recon's audit: `--workers` / `RIGOR_RACTOR_WORKERS` / `parallel.workers:` were
63
63
  # silently ignored because `build_runner` passed no `workers:`). 0 (the default, and what the specs and
@@ -112,7 +112,7 @@ module Rigor
112
112
  # and a missing invalidation edge is impossible by construction — the reason this is a table diff
113
113
  # and not the caller→callee edge recording #204 first sketched.
114
114
  @param_table = {}
115
- reset_effect_state
115
+ reset_carried_state
116
116
  # ADR-88 WD1 — the plugin fact-surface digest computed for THIS invocation (nil until a
117
117
  # `#run_incremental` pass runs / a plugin-free project) and the reporting flags a caller (the CLI
118
118
  # banner + `--cache-stats`) reads after `#run_incremental`. `@last_runner` is the analysis runner the
@@ -172,8 +172,9 @@ module Rigor
172
172
  # ADR-103 WD13 / #382 — a baseline collects every file, so its collections are the whole world.
173
173
  @effect_collections = runner.effect_collections_by_path
174
174
  @effects_identity = current_effects_identity
175
- @cache = per_file(diagnostics)
175
+ @cache = per_file(runner.per_file_diagnostics)
176
176
  @digests = @analyzed.to_h { |path| [path, pack_digest(path)] }
177
+ @run_level_rows = runner.run_level_rows
177
178
  diagnostics
178
179
  end
179
180
 
@@ -197,12 +198,13 @@ module Rigor
197
198
  # The freshly collected table is handed to the runner so the run seeds from the SAME table the diff
198
199
  # was decided on (and the collector runs once per recheck, not twice).
199
200
  runner = build_runner(analyze_only: analyze_set, record_dependencies: true,
200
- param_inferred_types: fresh_params)
201
+ param_inferred_types: fresh_params,
202
+ restored_run_level_rows: @run_level_rows)
201
203
  fresh = run_runner(runner).diagnostics
202
204
  @last_runner = runner # ADR-88 WD1 — the post-hoc fact-surface fingerprint reads this prepared registry.
203
205
  reused = (current & previous) - affected.to_a
204
206
  merged = fresh + reused.flat_map { |path| @cache[path] || [] }
205
- absorb(runner, fresh, current, analyze_set, removed)
207
+ absorb(runner, current, analyze_set, removed)
206
208
  @param_table = fresh_params
207
209
  Recheck.new(diagnostics: merged, changed: changed.to_set, added: added.to_set,
208
210
  removed: removed.to_set, affected: affected, reused: reused.to_set)
@@ -407,10 +409,13 @@ module Rigor
407
409
  end
408
410
 
409
411
  # The current project file set (cheap directory expansion, no analysis), used to detect files added /
410
- # removed since the last run.
412
+ # removed since the last run. Also where an editor buffer's binding is re-spelled onto the set, since
413
+ # this is where the run first learns how it spells its own members.
411
414
  def current_files
412
415
  runner = build_runner
413
- @paths ? runner.analysis_file_set(@paths) : runner.analysis_file_set
416
+ files = @paths ? runner.analysis_file_set(@paths) : runner.analysis_file_set
417
+ rebind_buffer(files)
418
+ files
414
419
  end
415
420
 
416
421
  # Verification engine (the `--verify-incremental` gate): with NO source edit, re-analyze `subset` fresh
@@ -422,7 +427,8 @@ module Rigor
422
427
  affected = subset.to_set
423
428
  # ADR-67 WD6c lift — seed the subset run from the baseline's own table so the verification engine
424
429
  # exercises the exact seeds the served cache entries were computed under (and skips a re-collect).
425
- runner = build_runner(analyze_only: affected, param_inferred_types: @param_table)
430
+ runner = build_runner(analyze_only: affected, param_inferred_types: @param_table,
431
+ restored_run_level_rows: @run_level_rows)
426
432
  fresh = run_runner(runner).diagnostics
427
433
  reused = @analyzed - affected.to_a
428
434
  fresh + reused.flat_map { |path| @cache[path] || [] }
@@ -559,6 +565,9 @@ module Rigor
559
565
  restore_structural_declarations(payload)
560
566
  @return_summaries = payload.return_summaries || {}
561
567
  @param_table = payload.param_table || {} # ADR-67 WD6c lift — the seeds the cache was built under.
568
+ # Issues #796 / #794 — absent (nil) in a snapshot written before schema 22 is impossible (the SCHEMA
569
+ # gate rejects one), so nil here means the writing run genuinely had nothing to say.
570
+ @run_level_rows = payload.run_level_rows
562
571
  # ADR-103 WD13 / #382 — the effects sidecar rides its own identity, so a snapshot whose summaries
563
572
  # were collected under a different vocabulary / catalogue / `effects:` block restores as EMPTY
564
573
  # rather than as stale rows. {#run_incremental} turns that emptiness into a full baseline, because
@@ -592,7 +601,8 @@ module Rigor
592
601
  return_summaries: marshal_safe_return_summaries,
593
602
  param_table: marshal_safe_param_table,
594
603
  effect_collections: marshal_safe_effect_collections,
595
- effects_identity: @effects_identity
604
+ effects_identity: @effects_identity,
605
+ run_level_rows: @run_level_rows
596
606
  )
597
607
  end
598
608
 
@@ -606,6 +616,15 @@ module Rigor
606
616
  @effects_identity = nil
607
617
  end
608
618
 
619
+ # The state this session carries ACROSS processes in the snapshot rather than deriving each run: the
620
+ # effects sidecar above, and (issues #796 / #794) the run-level rows the last FULL run answered — the
621
+ # `definition-build-failed` set and the HKT-scan outcome, nil until a full run has produced them, and
622
+ # handed to every narrowed run this session builds, none of which can derive them from its own files.
623
+ def reset_carried_state
624
+ reset_effect_state
625
+ @run_level_rows = nil
626
+ end
627
+
609
628
  # Whether the effects half of a restored snapshot permits reuse. Collection off is vacuously true —
610
629
  # the session keeps no collections, writes none, and the pre-#382 reuse decision stands unchanged.
611
630
  def effects_reuse_permitted?(payload)
@@ -684,18 +703,21 @@ module Rigor
684
703
  # Fold a #recheck's fresh results back into the cache + graph so the session is correct across
685
704
  # multiple edits: the analyzed set gets fresh diagnostics + digests + dependency edges, removed files
686
705
  # are evicted from every map, and the analyzed-file list advances to `current`.
687
- def absorb(runner, fresh, current, analyze_set, removed)
706
+ def absorb(runner, current, analyze_set, removed)
688
707
  removed.each { |path| forget(path) }
689
708
  @analyzed = current
690
709
  # ADR-85 WD2 — the recheck's discovery folded the restored bundles and refreshed them (changed files
691
710
  # re-walked, removed files dropped, added files built), so adopt the runner's current set wholesale.
692
711
  @seed_bundles = runner.seed_bundles
693
- fresh_by_file = per_file(fresh)
712
+ fresh_by_file = per_file(runner.per_file_diagnostics)
694
713
  analyze_set.each do |path|
695
714
  @cache[path] = fresh_by_file[path] || []
696
715
  @digests[path] = pack_digest(path)
697
716
  end
698
717
  absorb_dependency_graph(runner)
718
+ # Issues #796 / #794 — the recheck's own snapshots already carry the replayed rows folded together
719
+ # with anything its closure demanded for itself, so this is the same union the next run replays.
720
+ @run_level_rows = runner.run_level_rows
699
721
  refresh_return_summaries(runner, analyze_set)
700
722
  refresh_effect_collections(runner, analyze_set)
701
723
  end
@@ -803,20 +825,24 @@ module Rigor
803
825
  end
804
826
 
805
827
  # ADR-46 slice 3 — the consumers to re-check because a symbol that appeared in a changed file resolves
806
- # a prior missed lookup. Maps each appeared `"ClassName#method"` to the negative-dependency key it
807
- # would satisfy (`toplevel:foo` for a top-level def, `method:C#m` otherwise), then unions the recorded
808
- # negative-dependents of those keys.
828
+ # a prior missed lookup, and (issue #639) because a class DECLARATION appeared or vanished. Maps each
829
+ # appeared `"ClassName#method"` to the negative-dependency key it would satisfy (`toplevel:foo` for a
830
+ # top-level def, `method:C#m` otherwise), then unions the recorded negative-dependents of those keys.
809
831
  def negative_affected(changed, removed, new_fingerprints, new_class_decls, new_constant_decls)
810
832
  appeared_methods = Incremental.appeared_symbols(changed, @symbol_fingerprints, new_fingerprints)
811
- appeared_classes = Incremental.appeared_classes(changed, @class_decls, new_class_decls)
833
+ # Issue #639 — a class DISAPPEARING satisfies the `class:` kind too, now that a resolved bare
834
+ # reference records the edge and not only a failed one; removed files join the diff for it.
835
+ moved_classes = Incremental.changed_class_declarations(
836
+ changed + removed, @class_decls, new_class_decls
837
+ )
812
838
  # Issue #644 — a constant whose PUBLICATION moved satisfies the `constant:` kind, keyed on the same
813
- # last segment the consumer recorded. REMOVED files join the diff here and nowhere else: deleting a
814
- # second declarer restores the precise answer, and no other producer input can see that.
839
+ # last segment the consumer recorded. Removed files join this diff (as they do the class one above):
840
+ # deleting a second declarer restores the precise answer, and no other producer input can see that.
815
841
  moved_constants = Incremental.changed_constant_publications(
816
842
  changed + removed, @constant_decls, new_constant_decls
817
843
  )
818
844
  keys = appeared_methods.map { |symbol| negative_key_for(symbol) }
819
- keys.concat(appeared_classes.map { |klass| "class:#{klass.split('::').last}" })
845
+ keys.concat(moved_classes.map { |klass| "class:#{klass.split('::').last}" })
820
846
  keys.concat(moved_constants.map { |name| "constant:#{name.split('::').last}" })
821
847
  Incremental.negative_closure(keys, @negative_dependents)
822
848
  end
@@ -984,9 +1010,21 @@ module Rigor
984
1010
  @paths ? runner.run(@paths) : runner.run
985
1011
  end
986
1012
 
987
- # Group diagnostics by their file path, keeping only those whose path is an analyzed project file —
988
- # run-level streams (the gem-RBS info diagnostic, keyed on `.rigor.yml`) are recomputed fresh every
989
- # run and must not be served from the per-file cache.
1013
+ # Group the runner's PER-FILE diagnostics by path, keeping only analyzed project files.
1014
+ #
1015
+ # The argument is `Runner#per_file_diagnostics` — what `PoolCoordinator#analyze_files` returned, already
1016
+ # severity-resolved as the run's stream is (this cache serves reused files without re-stamping, so a
1017
+ # raw row would resurrect an `:off` rule and lose every override) — never the run's full stream. Every
1018
+ # run-level row is regenerated by every run and must not be served
1019
+ # from this cache, and "run-level" is a property of where a row was PRODUCED, not of where it is
1020
+ # positioned: the `.rigor.yml` rows the slice below drops are the common case, but
1021
+ # `effect.annotations-unchecked` sits at the first annotated file and the two `source-rbs-*` rows sit
1022
+ # at the source file that carried the annotation, and all three are produced once per run off the
1023
+ # environment and its reporters. Slicing the full stream cached them, and once a narrowed run's
1024
+ # environment carried the whole project's synthesized RBS (#793) each was regenerated for a file the
1025
+ # recheck did not analyse and met its cached twin in the merge — two rows, `--verify-incremental` red,
1026
+ # on the default configuration with the bundled rbs-inline plugin (#788 rounds 5 and 6; a rule-id
1027
+ # denylist missed the second pair). Feeding only the per-file stream makes the split structural.
990
1028
  def per_file(diagnostics)
991
1029
  diagnostics.group_by(&:path).slice(*@analyzed)
992
1030
  end
@@ -1001,18 +1039,51 @@ module Rigor
1001
1039
  candidates.reject { |path| stat_fresh?(path) }
1002
1040
  end
1003
1041
 
1004
- # A bound buffer's logical path is never fresh: the bytes to analyse live in the editor's temp file, and
1005
- # the recorded entry describes the file on disk. Re-analysing it when the two happen to agree costs one
1006
- # file; trusting the stat tuple would serve the editor its own stale diagnostics.
1007
1042
  def buffer_path?(path)
1008
1043
  !@buffer.nil? && path == @buffer.logical_path
1009
1044
  end
1010
1045
 
1046
+ # Editor mode (#146) — the bound buffer's logical path is fresh exactly when the EDITOR's bytes still
1047
+ # hash to the digest the snapshot recorded for the file they stand in for. The recorded stat tuple
1048
+ # describes the file on disk, which an edit in the editor never moves, so validating through it serves
1049
+ # the editor its own pre-buffer answers (#960); hashing the buffer makes the substituted file changed
1050
+ # whenever it differs, and keeps an untouched buffer out of the closure. Anything unreadable or
1051
+ # unrecorded reads as changed, the conservative direction the rest of this tier takes.
1052
+ def buffer_fresh?(path)
1053
+ recorded = Cache::FileDigest.content_digest(@digests[path])
1054
+ return false if recorded.nil?
1055
+
1056
+ Cache::FileDigest.hexdigest(@buffer.resolve(path)) == recorded
1057
+ rescue StandardError
1058
+ false
1059
+ end
1060
+
1061
+ # Editor mode (#146 / #960) — `--instead-of` is spelled the way the editor spells it (absolute, or
1062
+ # `./`-prefixed), while the analysed set is spelled the way the run's path arguments produce. A binding
1063
+ # whose logical path matches no member of that set substitutes nothing at all: every reader keeps
1064
+ # reading the file on disk and the run answers as if no buffer had been passed. Re-spell the binding
1065
+ # onto the set's own member, identified by real path, once the set is known.
1066
+ def rebind_buffer(files)
1067
+ return if @buffer.nil? || files.include?(@buffer.logical_path)
1068
+
1069
+ target = real_path(@buffer.logical_path)
1070
+ return if target.nil?
1071
+
1072
+ match = files.find { |path| real_path(path) == target }
1073
+ @buffer = BufferBinding.new(logical_path: match, physical_path: @buffer.physical_path) if match
1074
+ end
1075
+
1076
+ def real_path(path)
1077
+ File.realpath(path)
1078
+ rescue StandardError
1079
+ nil
1080
+ end
1081
+
1011
1082
  # True when `path`'s recorded stat entry proves it unchanged since the last analysis. Any stat / parse
1012
1083
  # failure (missing entry, unreadable / vanished file) reads as NOT fresh (→ re-analyse), preserving the
1013
1084
  # prior `digest(path) != recorded` "changed" semantics for a file that cannot be validated.
1014
1085
  def stat_fresh?(path)
1015
- return false if buffer_path?(path)
1086
+ return buffer_fresh?(path) if buffer_path?(path)
1016
1087
 
1017
1088
  entry = @digests[path]
1018
1089
  return false if entry.nil?
@@ -79,7 +79,6 @@ module Rigor
79
79
  #
80
80
  # Keeping the opaque decision here, rather than at each cache's call site, means a new consumer cannot
81
81
  # key on `digest` while forgetting that an opaque surface makes it meaningless.
82
- # @return [String, nil]
83
82
  def self.key_digest(registry)
84
83
  result = from_registry(registry)
85
84
  result.opaque? ? nil : result.digest.to_s
@@ -29,13 +29,11 @@ module Rigor
29
29
  # bucket on trust.
30
30
  Undecidable = Data.define(:fqn, :path, :line, :reason)
31
31
 
32
- # @param declarations [Array<Scan::Declaration>]
33
- # @param references [Array<Scan::Reference>]
34
- # @param root_fqns [Enumerable<String>] declarations that are entry points regardless of who references
32
+ # @param root_fqns — declarations that are entry points regardless of who references
35
33
  # them (config-declared globs in this slice; plugin-supplied roots are #349).
36
- # @param foreign [#call] predicate answering "is this FQN owned by something outside the project?" —
34
+ # @param foreign — predicate answering "is this FQN owned by something outside the project?" —
37
35
  # a reopened gem or stdlib class must never be a candidate (WD6). Defaults to "nothing is foreign".
38
- # @param dynamic_uses [Array<Scan::DynamicUse>] sites where a constant is reached by name at runtime.
36
+ # @param dynamic_uses — sites where a constant is reached by name at runtime.
39
37
  # A literal-argument site contributes a real reference; a dynamic one taints a namespace (WD4).
40
38
  def initialize(declarations:, references:, root_fqns: [], dynamic_uses: [], foreign: ->(_fqn) { false })
41
39
  @declarations = declarations
@@ -83,14 +83,15 @@ module Rigor
83
83
  # Loads the project's configured plugins, runs every `#prepare`, and returns the union of every
84
84
  # published `:reachability_roots` and `:reachability_references` fact.
85
85
  #
86
- # @param configuration [Rigor::Configuration] the loaded project configuration.
87
- # @param plugin_requirer [#call] how a plugin gem is brought into the process. The same seam
88
- # `Analysis::Runner` exposes, so a spec can register a plugin class without publishing a gem.
89
- # @param cache_store [Rigor::Cache::Store, nil] when given, each plugin's `#prepare` producers read
86
+ # @param configuration — the loaded project configuration.
87
+ # @param plugin_requirer — how a plugin gem is brought into the process, called with the gem name. The
88
+ # same seam `Analysis::Runner` exposes, so a spec can register a plugin class without publishing a
89
+ # gem.
90
+ # @param cache_store — when given, each plugin's `#prepare` producers read
90
91
  # and write the same ADR-60 record-and-validate slots they use under `rigor check`, instead of
91
92
  # recomputing from scratch — a routes parse or a factory discovery is a validated cache read on
92
93
  # every invocation after the first. Nil keeps the historical recompute-always behaviour.
93
- # @return [Contribution] sorted and de-duplicated. Empty whenever the project declares no plugins, no
94
+ # @return sorted and de-duplicated. Empty whenever the project declares no plugins, no
94
95
  # plugin contributes anything, or anything at all goes wrong.
95
96
  def collect(configuration:, plugin_requirer: ->(name) { require name }, cache_store: nil)
96
97
  return Contribution.empty if configuration.plugins.empty?
@@ -46,8 +46,8 @@ module Rigor
46
46
  File.fnmatch?(pattern, path, File::FNM_PATHNAME)
47
47
  end
48
48
 
49
- # @param relative_paths [Array<String>] paths relative to `root`.
50
- # @return [Array<String>] those that belong to the project itself.
49
+ # @param relative_paths — paths relative to `root`.
50
+ # @return those that belong to the project itself.
51
51
  def own(relative_paths, root)
52
52
  prefixes = submodule_prefixes(root)
53
53
  relative_paths.grep_v(VENDOR_DIRS).reject { |rel| prefixes.any? { |prefix| rel.start_with?(prefix) } }
@@ -70,11 +70,6 @@ module Rigor
70
70
  end
71
71
  end
72
72
 
73
- # @param path [String] the file's path, as the report should render it.
74
- # @param source [String] the file's bytes.
75
- # @param target_ruby [String, nil] Prism version string, threaded from the project configuration.
76
- # @return [Result, nil] nil when the file does not parse (a parse error is the analyzer's business, not
77
- # this scan's — it simply contributes nothing rather than half a file).
78
73
  # A constant name is ASCII by construction, so a byte sequence that is not valid UTF-8 cannot be one.
79
74
  # Dropping it is both correct and the only safe answer: carrying it forward crashed the whole run on
80
75
  # the first `String#sub` downstream, which is how this surfaced — `rigor unused` on Rigor's own
@@ -86,6 +81,11 @@ module Rigor
86
81
  name.valid_encoding? ? name : nil
87
82
  end
88
83
 
84
+ # @param path — the file's path, as the report should render it.
85
+ # @param source — the file's bytes.
86
+ # @param target_ruby — Prism version string, threaded from the project configuration.
87
+ # @return nil when the file does not parse (a parse error is the analyzer's business, not
88
+ # this scan's — it simply contributes nothing rather than half a file).
89
89
  def self.call(path:, source:, target_ruby: nil)
90
90
  parsed = if target_ruby
91
91
  Prism.parse(source, filepath: path,