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
@@ -19,7 +19,7 @@ specification](../type-specification/README.md), the spec binds.
19
19
  | What about types that may or may not match? | Gradual consistency (`~`) | The `Dynamic[T]` carrier and the trinary certainty `yes / no / maybe` |
20
20
  | How are user types identified? | Nominal vs structural | **Nominal-first hybrid** — classes by name, plus structural facets (`interface`, `HashShape`, capability roles) |
21
21
  | How are generics expressed? | Parametric polymorphism (System F-style, but predicative) | RBS generics `class Array[Elem]`, method generics `def map: [U] () { (Elem) -> U } -> Array[U]` |
22
- | How is "x is a non-empty string" expressed? | Refinement / predicate subtyping | First-class refinement carriers (`non-empty-string`, `int<min, max>`, …) |
22
+ | How is "x is a non-empty string" expressed? | Refinement / predicate subtyping | First-class refinement carriers (`non-empty-string`, `Integer[min..max]`, …) |
23
23
  | How does `if x.is_a?(String)` change `x`'s type? | Occurrence typing / flow-sensitive narrowing | Edge-aware narrowing with trinary certainty |
24
24
  | What about side effects? | Effect systems | The engine's effect model (mutation, exception, escape) — internal, not user-visible |
25
25
  | Soundness or completeness? | Pick one (or neither) | **Neither in full** — Rigor optimises for no-false-positives, with a robustness-principle bias |
@@ -528,7 +528,7 @@ refinements with reserved names:
528
528
  | `non-empty-string` | `s : String, s.size >= 1` | refinement on `String` |
529
529
  | `numeric-string` | `s : String, s =~ /\A[+-]?\d+(\.\d+)?\z/` | refinement on `String` |
530
530
  | `literal-string` | "provably built from literals" | refinement on `String` |
531
- | `int<min, max>` | `n : Integer, min <= n <= max` | range carrier |
531
+ | `Integer[min..max]` | `n : Integer, min <= n <= max` | range carrier |
532
532
  | `non-zero-int` | `n : Integer, n != 0` | refinement on `Integer` |
533
533
  | `positive-int` | `n : Integer, n > 0` | refinement on `Integer` |
534
534
  | `non-empty-array[T]` | `arr : Array[T], arr.size >= 1` | refinement on `Array[T]` |
@@ -1491,7 +1491,7 @@ here so you can stop looking:
1491
1491
  and Rigor does not synthesise it.
1492
1492
  - **Full dependent types.** No `Vec[n, T]` with `n : Integer`.
1493
1493
  Type-checking is decidable but inference is not; integer-range
1494
- refinements (`int<min, max>`) cover the most common practical
1494
+ refinements (`Integer[min..max]`) cover the most common practical
1495
1495
  need without crossing the line.
1496
1496
  - **Row polymorphism as a user-quantifiable axis.** `HashShape`
1497
1497
  carries open-vs-closed semantics internally but does not
@@ -1581,7 +1581,7 @@ they map to the sections of this appendix:
1581
1581
  matching and exhaustiveness."
1582
1582
  - Rondon, Kawaguchi & Jhala. "Liquid Types." *PLDI 2008.* The
1583
1583
  refinement-types-with-SMT framework that informs the
1584
- `int<min, max>` carrier (Rigor uses a much weaker, decidable
1584
+ `Integer[min..max]` carrier (Rigor uses a much weaker, decidable
1585
1585
  fragment).
1586
1586
  - Lucassen & Gifford. "Polymorphic Effect Systems."
1587
1587
  *POPL 1988.* Origin of effect systems.
@@ -101,7 +101,7 @@ positive, an array that is provably non-empty.
101
101
  | --- | --- | --- |
102
102
  | `non-empty-string` | `\`${string}${string}\`` (template literal trick) or branded `NonEmptyString` | Awkward in TS; Rigor produces it from `unless s.empty?` automatically. |
103
103
  | `positive-int` | branded `PositiveInt` | TS users tend to skip the brand — Rigor narrows from `n > 0`. |
104
- | `int<1, 9>` | union of literal types `1 \| 2 \| 3 \| ... \| 9` | Rigor's range carrier handles arbitrary bounds without exploding. |
104
+ | `Integer[1..9]` | union of literal types `1 \| 2 \| 3 \| ... \| 9` | Rigor's range carrier handles arbitrary bounds without exploding. |
105
105
  | `numeric-string` | (none useful) | TS has no equivalent; Rigor narrows from regex matches against numeric patterns. |
106
106
  | `non-empty-array[T]` | `[T, ...T[]]` (tuple-with-rest) | TS has the encoding but few APIs use it; Rigor produces it from `unless arr.empty?`. |
107
107
 
data/docs/llms.txt CHANGED
@@ -42,6 +42,8 @@ Skill with `rigor skill <name>`. (The canonical web copy is
42
42
  - `15-type-protection-coverage` — `rigor coverage --protection`.
43
43
  - `16-rbs-extended-annotations` — the `%a{rigor:v1:…}` annotations.
44
44
  - `17-driving-improvement` — the `rigor-next-steps`-driven improvement loop.
45
+ - `18-removing-dead-code` — working `rigor unused` as a campaign on an old codebase.
46
+ - `19-effect-labels` — `rigor effects`, the `.rigor-effects.yml` snapshot, and `%a{pure}`.
45
47
 
46
48
  ## Handbook (read with `rigor docs <name>`)
47
49
 
@@ -45,6 +45,7 @@ the `paths:` list from the configuration file.
45
45
  | `--baseline=PATH` | Load a baseline file, overriding config. |
46
46
  | `--no-baseline` | Ignore any configured baseline. |
47
47
  | `--baseline-strict` | Fail the run on any baseline drift — a CI gate. |
48
+ | `--fail-on=SEVERITY` | Exit non-zero when a diagnostic at or above `SEVERITY` (`error`, the default; `warning`; or `info`) survives baseline filtering — raises the exit-status bar above the default `:error`-only reading for CI gates that want it, without changing `--format json`'s `success` / `error_count` fields. |
48
49
  | `--treat-all-as-inline-rbs` | Force-load `rigor-rbs-inline` with `require_magic_comment: false`, so every analysed file is treated as inline-RBS without the `# rbs_inline: enabled` comment (ADR-32). |
49
50
  | `--bleeding-edge[=ids]` | Adopt the bleeding-edge overlay for this run, overriding the configured [`bleeding_edge:`](03-configuration.md) selection (ADR-50 § WD2). Bare adopts every queued feature; `--bleeding-edge=a,b` adopts only the named feature ids. Inspect it with [`rigor show-bleedingedge`](#rigor-show-bleedingedge). |
50
51
  | `--no-bleeding-edge` | Ignore any configured `bleeding_edge:` selection for this run (adopt none). |
@@ -52,7 +53,9 @@ the `paths:` list from the configuration file.
52
53
  | `--tmp-file=PATH --instead-of=PATH` | Editor mode: analyse `PATH` using the buffer in `--tmp-file`. Both required together. Alone, only the buffer's own file produces diagnostics; add `--incremental` for whole-project scope (see below). |
53
54
 
54
55
  Exit `0` when no error-severity diagnostics remain, `1` when
55
- any are reported, `64` on a usage error.
56
+ any are reported, `64` on a usage error. `--fail-on` raises that
57
+ bar to `warning` or `info` for callers (CI gates, `make check`)
58
+ that want the stricter reading.
56
59
 
57
60
  ### Editor mode scope
58
61
 
@@ -135,6 +138,17 @@ truncation explicit. `--trace` records fail-soft fallbacks,
135
138
  after the rows of a line table in text output. The editor-mode
136
139
  `--tmp-file` / `--instead-of` pair is accepted as on `check`.
137
140
 
141
+ The four probe commands — `type-of`, `type-scan`, `trace` and
142
+ `annotate` — build their environment fresh on every invocation
143
+ and never read or write the persistent cache. That is why none
144
+ of them takes `--no-cache`: the flag would have nothing to skip.
145
+ A probe therefore types against the environment `rigor check
146
+ --no-cache` analyses with. The environment a cached (default)
147
+ `rigor check` builds is meant to be identical, and Rigor gates
148
+ the two builds against each other — but if you are chasing a
149
+ disagreement between a probe and a `check` run, comparing
150
+ against `rigor check --no-cache` removes that variable.
151
+
138
152
  ## `rigor trace`
139
153
 
140
154
  Replay HOW the engine typed a file, step by step, as a
@@ -429,6 +443,12 @@ rigor explain [rule]
429
443
  or a family wildcard (`call`, `flow`, `def`, `assert`, `dump`).
430
444
  `--format=json` is available. Exit `64` for an unknown rule.
431
445
 
446
+ It also answers the `sig.skipped.*` identifiers `rigor sig-gen`
447
+ prints when it declines to write a signature — `rigor explain
448
+ sig.skipped.untyped-return` says what the skip means and what to do
449
+ instead. Those are not diagnostic rules, so they are rendered without
450
+ a severity or a suppression line.
451
+
432
452
  ## `rigor diff`
433
453
 
434
454
  Compare the current diagnostics against a saved baseline JSON
@@ -481,6 +501,8 @@ not overwrite) is never a silent absence: under `--format=json`
481
501
  it is a `skipped` row of the `candidates` array with its
482
502
  `sig.skipped.*` identifier in `skip_reason`, and in text mode a
483
503
  one-line stderr summary counts the skipped methods per reason.
504
+ `rigor explain sig.skipped.untyped-return` (or any of the other skip
505
+ identifiers) explains what the reason means and what to do about it.
484
506
 
485
507
  ## `rigor lsp`
486
508
 
@@ -577,6 +599,12 @@ see. That is why this is a separate command and never a `rigor check`
577
599
  diagnostic — see
578
600
  [ADR-102](../adr/102-unused-code-reachability-report.md).
579
601
 
602
+ `rigor unused` refuses `--incremental` and exits non-zero rather
603
+ than quietly running a full pass. Reachability is only sound over a
604
+ whole-project run: with files served from the incremental cache a
605
+ constant would be reported as unused merely because the file that
606
+ references it was not re-scanned. Re-run without the flag.
607
+
580
608
  Reachability is computed from **roots**, not by counting references,
581
609
  so a cluster of classes that only reference each other is still
582
610
  reported. Roots are the declarations in files matching
@@ -1112,7 +1140,7 @@ operational knobs read the environment instead.
1112
1140
  | `RIGOR_RACTOR_WORKERS=N` | Worker count for parallel analysis. Sits between the CLI flag and the config key in precedence: `--workers=N` > `RIGOR_RACTOR_WORKERS` > `parallel.workers:` > `0` (sequential). |
1113
1141
  | `RIGOR_POOL_BACKEND=ractor` | Opt back into the (off-by-default) Ractor worker pool instead of the active fork-based pool ([ADR-15](../adr/15-ractor-concurrency.md)). Only relevant with a non-zero worker count; the fork pool is the supported backend. |
1114
1142
  | `RIGOR_LSP_POOL_MIN_BATCH=N` | Fewest buffers an [`rigor lsp`](#rigor-lsp) batch must carry before analysis is dispatched across the worker pool rather than run in-process (default `16`). Lower it if your project's per-file analysis is expensive enough that pooling pays off sooner. |
1115
- | `RIGOR_PLUGIN_ISOLATION=none\|process\|ruby_box` | How a plugin's direct calls into its target library are isolated. Default `process`. See [Using plugins § Isolation strategy](07-plugins.md). `RIGOR_BOX` is a legacy alias for `ruby_box`. |
1143
+ | `RIGOR_PLUGIN_ISOLATION=none\|process\|ruby_box` | How a plugin's direct calls into its target library are isolated. Overrides the `plugins_isolation:` configuration key. Default `process`. See [Using plugins § Isolation strategy](07-plugins.md). `RIGOR_BOX` is a legacy alias for `ruby_box`. |
1116
1144
  | `RIGOR_STRICT_VALIDATION=1` | Force full-content cache validation for one run (the same as `cache.validation: digest`, and winning over it) — re-hash every file's content instead of trusting its stat metadata. Use it if a filesystem's timestamps or inode numbers cannot be trusted. See [Caching § How a file is checked for changes](12-caching.md#how-a-file-is-checked-for-changes). |
1117
1145
  | `RIGOR_DISABLE_YJIT=1` | Opt out of Rigor's deferred YJIT enablement. Rigor turns YJIT on partway through any long run so short runs never pay the JIT warm-up; this variable leaves it off entirely. Diagnostics and allocations are identical either way — the effect is wall-time only. |
1118
1146
  | `RIGOR_YJIT_DEADLINE=<seconds>` | Advanced: tune how long a run must last before deferred YJIT enables (default `5.0`). Lower it if your runs are long and you want the JIT sooner; raise it to protect short runs. Ignored when `RIGOR_DISABLE_YJIT=1` is set or YJIT is unavailable. |
@@ -1126,8 +1154,8 @@ diagnostics about Rigor's own inference cutoffs and memory — see
1126
1154
 
1127
1155
  | Code | Meaning |
1128
1156
  | --- | --- |
1129
- | `0` | Success — no error-severity diagnostics. |
1130
- | `1` | Diagnostics found, or a per-command failure (parse error, missing file, new diagnostics on `diff`, effect drift on `effects check`). |
1157
+ | `0` | Success — no diagnostic at or above the exit threshold (`error` by default; `rigor check --fail-on=SEVERITY` lowers it to `warning` or `info`). |
1158
+ | `1` | Diagnostics found at or above the threshold, or a per-command failure (parse error, missing file, new diagnostics on `diff`, effect drift on `effects check`). |
1131
1159
  | `64` | Usage error — unknown command, bad flag, malformed argument, or a value in `.rigor.yml` the loader cannot proceed on. |
1132
1160
 
1133
1161
  `rigor triage` is the exception: it is advisory and always
@@ -93,6 +93,16 @@ rigor: severity_overrides: "flow.bogus" is not a recognized rule id; the overrid
93
93
  rigor: bundler.lockfile: "./missing/Gemfile.lock" does not exist
94
94
  ```
95
95
 
96
+ One warning covers the mirror-image mistake — a path that loads, but only
97
+ half of what you wanted. A bundled plugin ships its RBS *and* a manifest
98
+ recording which of those classes it declares only partially; `plugins:`
99
+ loads both, while pointing `signature_paths:` at the plugin's `sig/` loads
100
+ only the RBS, so calls your own code defines get reported as undefined:
101
+
102
+ ```
103
+ rigor: signature_paths: "…/plugins/rigor-activerecord/sig" loads the signatures of the bundled plugin "rigor-activerecord", which `plugins:` does not name — … Add "rigor-activerecord" to `plugins:` instead of naming its `sig/` in `signature_paths:`.
104
+ ```
105
+
96
106
  The unrecognised-key check covers **top-level** keys, and skips
97
107
  the namespaces reserved for other implementations (see below).
98
108
  A typo *inside* a group — `cache: { pth: … }` — is caught by
@@ -170,6 +180,7 @@ explicitly with `bundler.bundle_path:`, or supply signatures another way:
170
180
  | `cache.max_bytes` | Integer or `null` | `268435456` (256 MB) | LRU eviction cap for the cache directory; `null` disables eviction. See [Caching § Size and eviction](12-caching.md#size-and-eviction). |
171
181
  | `cache.validation` | String | `"auto"` | How the cache checks whether a file is unchanged: `auto` behaves as `digest` when a CI environment is detected and as `stat` otherwise; `stat` compares size + nanosecond timestamps + inode and only re-hashes a file whose stat moved; `digest` re-hashes every file's content every run. Both keep the content hash as the sole change authority — `stat` just skips the hash when the stat proves a file untouched. See [Caching § How a file is checked for changes](12-caching.md#how-a-file-is-checked-for-changes). The `RIGOR_STRICT_VALIDATION=1` environment variable forces `digest` for one run and wins over this key; `RIGOR_CI_DETECT=0` disables the CI detection. |
172
182
  | `parallel.workers` | Integer | `0` | Parallel worker processes for per-file analysis (fork-based pool today; ADR-15); `0` is sequential. CLI `--workers` and `RIGOR_RACTOR_WORKERS` take precedence. Applies to `--incremental` re-checks as well as full runs. |
183
+ | `plugins_isolation` | String | `null` | How a plugin's call into its target library is isolated — `process` (default) or `none`. `RIGOR_PLUGIN_ISOLATION` overrides it for one invocation; `ruby_box` is that variable only. See [Using plugins](07-plugins.md). |
173
184
  | `plugins_io.network` | String | `"disabled"` | Plugin network policy — `disabled` or `allowlist`. |
174
185
  | `plugins_io.allowed_paths` | Array | `[]` | Filesystem paths plugins may read. |
175
186
  | `plugins_io.allowed_url_hosts` | Array | `[]` | URL hosts plugins may fetch from when `network: allowlist`. |
@@ -85,6 +85,17 @@ carries no `documentation_url`.
85
85
  Plugins may contribute further families and rules; `rigor
86
86
  explain` lists whatever the active configuration loads.
87
87
 
88
+ `flow.unreachable-branch` and `flow.always-truthy-condition` fold
89
+ version guards — `RUBY_VERSION` / `RUBY_ENGINE` comparisons, and
90
+ `X::VERSION` for a default gem of the running Ruby — against the
91
+ Ruby interpreter running `rigor`, never `target_ruby`. The
92
+ diagnostic set is therefore host-dependent: the same file can fold
93
+ a different arm on Ruby 3.3 than on Ruby 4.0, and a project whose
94
+ CI pins a different Ruby than your workstation should expect the CI
95
+ run's result, not yours. See
96
+ [Version-guard condition folding](../type-specification/control-flow-analysis.md#version-guard-condition-folding)
97
+ for the exact foldable set.
98
+
88
99
  ## Evidence tier
89
100
 
90
101
  Every rule in the catalogue above carries an **evidence tier** —
@@ -74,23 +74,33 @@ A plugin may want to read a file (a schema dump) or reach the
74
74
  network. Those are gated by the `plugins_io:` config keys —
75
75
  the network is `disabled` by default, and a plugin can read
76
76
  only the paths you list. See
77
- [Configuration](03-configuration.md).
77
+ [Configuration](03-configuration.md). If a plugin's read falls
78
+ outside every configured path — a path spelled through a symlink
79
+ alias where the read roots hold the real path (macOS' `/tmp` is
80
+ one), or a genuinely out-of-tree file — Rigor surfaces a
81
+ `plugin_trust.read-refused` `:info` diagnostic naming the plugin,
82
+ the refused path and the nearest read root instead of failing
83
+ silently. Spell the path the way the diagnostic's read root spells
84
+ it, or add it under `plugins_io.allowed_paths:`.
78
85
 
79
86
  ### Isolation strategy
80
87
 
81
88
  A few plugins call into their target library directly (for
82
89
  example to ask ActiveSupport's real inflector how to pluralise a
83
90
  class name). That call runs under an **isolation strategy**, set
84
- with the `RIGOR_PLUGIN_ISOLATION` environment variable:
91
+ with the `plugins_isolation:` configuration key or the
92
+ `RIGOR_PLUGIN_ISOLATION` environment variable:
85
93
 
86
94
  | Value | Behaviour |
87
95
  | --- | --- |
88
96
  | `process` (default) | Run the call in a forked, crash-contained worker, so the target library's monkey-patches and any crash never contaminate Rigor. Falls back to `none` where `fork` is unavailable (Windows / JRuby). |
89
97
  | `none` | Load the library into Rigor's own process and call it directly. |
90
- | `ruby_box` | Run inside an experimental `Ruby::Box` sandbox. This needs the `RUBY_BOX=1` start flag, so the `rigor` launcher re-execs itself with it set when you select this strategy. |
98
+ | `ruby_box` | Run inside an experimental `Ruby::Box` sandbox. This needs the `RUBY_BOX=1` start flag, so the `rigor` launcher re-execs itself with it set when you select this strategy. **Environment variable only** — the configuration file is read long after Ruby has booted, so `plugins_isolation: ruby_box` is reported as a configuration error instead. |
91
99
 
92
- The legacy `RIGOR_BOX` environment variable is a back-compat
93
- alias for `RIGOR_PLUGIN_ISOLATION=ruby_box`. The default
100
+ The environment variable wins over `plugins_isolation:`, so you can
101
+ override a project's committed choice for one invocation. The legacy
102
+ `RIGOR_BOX` environment variable is a back-compat alias for
103
+ `RIGOR_PLUGIN_ISOLATION=ruby_box`. The default
94
104
  (`process`) is the right choice for almost everyone; the variable
95
105
  exists for the rare platform where forking is unavailable or
96
106
  where you want stronger containment.
@@ -8,9 +8,9 @@ agent works inside a project that has Rigor available.
8
8
  Skills are optional. Everything they do, you can do by hand with the
9
9
  commands in this manual; a skill drives the workflow end to end.
10
10
 
11
- ## Start here — two skills to remember
11
+ ## Start here — three skills to remember
12
12
 
13
- You only ever need to remember two skills; the rest are reached through
13
+ You only ever need to remember three skills; the rest are reached through
14
14
  them.
15
15
 
16
16
  - **`rigor-next-steps`** — *"what should we do next?"* The single entry
@@ -31,6 +31,20 @@ them.
31
31
  code, runs `rigor check` / `annotate` / `type-of` — then answers from
32
32
  the page or the inferred type. You never have to remember the command,
33
33
  just the question. Available at any point.
34
+ - **`rigor-type-oracle`** — *"before you write a type, ask Rigor."* The
35
+ one to remember while **writing** rather than while planning. Any time
36
+ a type is about to be written or asserted — RBS under `sig/`, an inline
37
+ `#:` / `# @rbs` annotation, a Sorbet `sig`, a YARD `@param` /
38
+ `@return`, a type named in a doc sentence or a review comment, a nil
39
+ check justified by "this should be an `X`" — the type comes from
40
+ [`rigor type-of`](02-cli-reference.md#rigor-type-of) /
41
+ [`annotate`](02-cli-reference.md#rigor-annotate) /
42
+ [`sig-gen`](02-cli-reference.md#rigor-sig-gen), not from reading the
43
+ code. A type nobody obtained from Rigor is a guess, and where Rigor has
44
+ no answer (`Dynamic[top]`, `untyped`, a skipped method) the gap is
45
+ reported rather than filled in. It also ships the paragraph to keep in
46
+ your `AGENTS.md` / `CLAUDE.md`, so the rule holds in every agent
47
+ session and not only when the skill triggers.
34
48
 
35
49
  If you do not know which skill you need, start with `rigor-next-steps`.
36
50
 
@@ -72,6 +86,13 @@ is not repeated here.)
72
86
  - **`rigor-monkeypatch-resolve`** — resolves an `undefined-method`
73
87
  cluster that is really your project's own monkey-patches by wiring the
74
88
  defining files into `pre_eval:`.
89
+ - **`rigor-type-oracle`** — sources every type an agent writes from
90
+ Rigor (`type-of` / `annotate` / `sig-gen`) instead of from reading the
91
+ code, and reports the gaps rather than filling them. It is triggered by
92
+ the *event* of a type being about to be written, so `rigor skill
93
+ describe` lists it but never routes to it — reach for it (or install
94
+ its `AGENTS.md` paragraph) whenever an agent documents or annotates
95
+ your code. Introduced above under "Start here".
75
96
 
76
97
  ### Integration and operations
77
98
 
data/docs/manual/11-ci.md CHANGED
@@ -187,6 +187,15 @@ Rigor's severities map per format ([ADR-51](../adr/51-ci-diagnostic-output-forma
187
187
  The exit code is unchanged by `--format` — `0` when there are no errors,
188
188
  `1` otherwise — so the job still gates the pipeline. `--format json`
189
189
  remains available for any other tool that wants the raw diagnostic stream.
190
+ A job that wants warnings (or `info` notes) to gate the pipeline too, not
191
+ just errors, raises the bar with `rigor check --fail-on=warning` (or
192
+ `--fail-on=info`) instead of parsing the diagnostic stream itself.
193
+
194
+ A third status, `70`, means Rigor itself died mid-run — a
195
+ `SystemStackError` or `NoMemoryError` inside the analysis — and the report
196
+ it left behind (an empty `triage.json`, say) is not an account of your
197
+ code. Treat it as a failed job and report it as a Rigor defect, never as a
198
+ clean check.
190
199
 
191
200
  ## Gating effect drift
192
201
 
@@ -337,6 +337,14 @@ ones a type actually catches.
337
337
  > point Rigor at it with `bundler.bundle_path:`. Until you do, these
338
338
  > holes keep the generic `engine_gap` cause instead of `add_rbs` —
339
339
  > the label is missing, never wrong.
340
+ >
341
+ > A project with **no `Gemfile.lock`** is not left out: Rigor falls
342
+ > back to the gems it can see installed — the project's Bundler
343
+ > install tree if one resolves, otherwise the running Ruby's gems —
344
+ > and attributes constants against those. Ownership is still
345
+ > established by reading the gem's own entry file, so the fallback
346
+ > widens which gems can be claimed and never whether an unowned
347
+ > constant is.
340
348
 
341
349
  Provenance is precision-additive only: it never changes a type, fires
342
350
  no diagnostic, and never affects severity or the protection ratio.
@@ -122,7 +122,12 @@ The right-hand side of `return:`, `param:`, `assert*`, and
122
122
 
123
123
  Refinement payloads support the parameterised forms
124
124
  `non-empty-array[Integer]`, `non-empty-hash[Symbol, Integer]`,
125
- and the bounded-integer form `int<min, max>`. Type-argument
125
+ and the bounded numeric forms `Integer[1..10]` and
126
+ `Float[0.0...1.0]`, written with a Ruby range literal (`1...10`,
127
+ `1..`, `..10`; the PHPStan-style `int<1, 10>` still parses but is
128
+ deprecated and reports `dynamic.rbs-extended.deprecated-form` with
129
+ the spelling to write), plus the Float names `non-nan-float` and
130
+ `finite-float`. Type-argument
126
131
  positions also accept Symbol / String literal tokens and unions
127
132
  of them — `pick_of[T, :name | :email]`,
128
133
  `Pick[T, "name" | "email"]` — each lifted to a `Constant<value>`.
@@ -80,14 +80,16 @@ partition `declared` — they sum to 1497. **`reachable only from tests`
80
80
  is a subset of `reachable`**, not a fifth bucket, so do not add it in.
81
81
 
82
82
  Read `roots` first. A root is a declaration something outside your code
83
- reaches — an entry point, a route, a framework convention.
84
- Reachability is computed *from* roots, so a thin root set inflates
85
- everything below it. The pathological case is a Rails application with
86
- `0 from plugins`: nothing in Ruby source names a controller, so every
87
- controller you own appears dead. There is no published healthy ratio
88
- to compare against; what you are checking is whether the number is
89
- plausible for your framework, and `0 from plugins` on a framework app
90
- never is.
83
+ reaches — an entry point, a route, a framework convention. The
84
+ `(… from plugins, …)` parenthetical is omitted entirely when no plugin
85
+ contributed a root, so a bare `roots: N` with no such note is itself
86
+ the signal. Reachability is computed *from* roots, so a thin root set
87
+ inflates everything below it. The pathological case is a Rails
88
+ application with no `from plugins` note at all: nothing in Ruby source
89
+ names a controller, so every controller you own appears dead. There is
90
+ no published healthy ratio to compare against; what you are checking
91
+ is whether the number is plausible for your framework, and a missing
92
+ `from plugins` note on a framework app never is.
91
93
 
92
94
  ## Getting the number down
93
95
 
@@ -93,6 +93,24 @@ so *can* fold a condition — and `Mime::NullType`, the value `format`
93
93
  returns when there is no format, answers `nil?` with `true` while
94
94
  being a real object. Typing it needs a nil-aware answer.
95
95
 
96
+ ## Framework constants resolve
97
+
98
+ The plugin ships a small bundled signature naming the
99
+ `ActionController` namespace and the errors controllers rescue —
100
+ `ParameterMissing`, `UnpermittedParameters`, `RoutingError`,
101
+ `BadRequest`, `UnknownFormat`, `InvalidAuthenticityToken` and their
102
+ siblings. A `rescue ActionController::ParameterMissing => e` now types
103
+ `e` as that class instead of leaving it opaque.
104
+
105
+ The signature names **only** those. `ActionController::Base` and
106
+ `ActionController::API` are left undeclared on purpose: every
107
+ controller in the app inherits from one of them, and an incomplete
108
+ declaration of a superclass turns every member it omits — `render`,
109
+ `before_action`, `head` — into a report on working code. For the same
110
+ reason `ActionController::Parameters` and the `ActionDispatch` readers
111
+ above stay undeclared; their leniency is what makes the `params`
112
+ typing safe.
113
+
96
114
  ## Limitations
97
115
 
98
116
  - **Implicit-self helpers only.** `*_path` / `*_url` calls with an
@@ -76,6 +76,19 @@ Chained query methods keep the element type, and iteration
76
76
  scope invoked on a typed relation (`User.where(...).published`)
77
77
  never surfaces a false `call.undefined-method`.
78
78
 
79
+ If the project also installs `activerecord` through
80
+ `rbs collection install`, the collection declares
81
+ `ActiveRecord::Relation` without a type parameter while the
82
+ plugin declares `ActiveRecord::Relation[Elem]`, and RBS cannot
83
+ hold both. The plugin's declaration stands down: relation call
84
+ sites still type as `ActiveRecord::Relation[Model]`, but calls
85
+ into a relation resolve against the collection's declaration,
86
+ so the plugin's element typing (`.first` as `Model?`, for
87
+ example) is unavailable, and the run reports one
88
+ `rbs.coverage.plugin-signature-stood-down` info row naming both
89
+ files. Nothing is broken; the plugin's typing returns only when
90
+ the collection stops declaring the class.
91
+
79
92
  `User.table_name` types as `String`, and as the exact string
80
93
  only when your source says the name: a literal
81
94
  `self.table_name = "people"` on the class or on an STI ancestor,
@@ -118,6 +131,21 @@ than none, so `Blog::Post`'s column, alias and association checks
118
131
  stand down entirely rather than run against a table that might not
119
132
  be the real one.
120
133
 
134
+ ## Framework constants resolve
135
+
136
+ The bundled signatures also name Active Record's exception hierarchy
137
+ (`ActiveRecordError` and the classes apps rescue: `RecordNotFound`,
138
+ `RecordInvalid`, `RecordNotSaved`, `StatementInvalid`,
139
+ `RecordNotUnique`, `StaleObjectError`, …), the `ActiveModel`
140
+ namespace, and `Arel`. `rescue ActiveRecord::RecordNotFound => e` types
141
+ `e` instead of leaving it opaque.
142
+
143
+ None of them declares a method surface — the declaration buys constant
144
+ resolution and asserts nothing else, so `e.record` and every other
145
+ member left out stays lenient rather than reported.
146
+ `ActiveRecord::Base` is deliberately **not** declared: closing it would
147
+ close every model in the project.
148
+
121
149
  ## Limitations
122
150
 
123
151
  - **Direct-superclass match only.** `class Admin < User` where
@@ -129,8 +129,29 @@ singletons `.days_in_month`, `.days_in_year`, `.rfc3339`, `.use_zone`,
129
129
  `.find_zone` / `.find_zone!` and `.zone_default`.
130
130
 
131
131
  Where a return cannot honestly be named it is widened rather than
132
- guessed: `#in_time_zone` answers an `ActiveSupport::TimeWithZone`, which
133
- this bundle does not model, so it reads `untyped`.
132
+ guessed: `#in_time_zone` answers an `ActiveSupport::TimeWithZone` under a
133
+ zone and the receiver's own class when none is configured, so it reads
134
+ `untyped` rather than picking one of the two.
135
+
136
+ `ActiveSupport::TimeWithZone` itself **is** modelled, as a subclass of
137
+ `Time` — which is what `Time.current` and the whole `1.hour.ago` family
138
+ answer:
139
+
140
+ ```ruby
141
+ Time.current.time_zone # the zone (untyped)
142
+ Time.current.comparable_time # Time
143
+ Time.current.to_fs(:db) # String — Time's own surface, inherited
144
+ 1.hour.ago.time_zone # the same class from the Duration family
145
+ Time.now.time_zone # still call.undefined-method — a plain
146
+ # Time really does not have it
147
+ ```
148
+
149
+ Rails overrides `TimeWithZone#is_a?` to answer true for `::Time` and
150
+ forwards everything it does not define to the wrapped `Time`, so the
151
+ subclass says what a `Time` return said and adds the four readers a TWZ
152
+ has of its own. A `Time | TimeWithZone` union was measured instead and
153
+ rejected: it fires nothing, but types the whole downstream chain
154
+ `Dynamic[top]`.
134
155
 
135
156
  What is left out is twelve names, measured against a real
136
157
  `require "active_support/all"`: ten instance and two singleton, every one
@@ -145,6 +166,15 @@ code that does will see them reported.
145
166
  `Date` and `DateTime` are extended by the same ActiveSupport modules and
146
167
  do **not** carry this yet — `Date.current.past?` still reports.
147
168
 
169
+ ## `ActiveSupport::Concern` resolves
170
+
171
+ `extend ActiveSupport::Concern` is the first line of every concern in a
172
+ Rails app, and the constant used to resolve to nothing. The bundle now
173
+ names the module, along with the three members `extend` puts on the
174
+ extender — `included`, `prepended` and `class_methods` — so
175
+ `included do … end` keeps resolving rather than becoming a report on the
176
+ newly-named module. Everything else `Concern` responds to stays lenient.
177
+
148
178
  ## No diagnostics, no config
149
179
 
150
180
  The plugin emits no diagnostics and has no configuration knobs. It
@@ -45,7 +45,48 @@ plugin id/version + config), so an unchanged second run skips the parse.
45
45
  | Rule | Severity | Fires when |
46
46
  | --- | --- | --- |
47
47
  | `plugin.rbs-inline.source-rbs-synthesis-failed` | info | rbs-inline could not parse a file; analysis falls back to no inline-RBS contribution and the diagnostic carries the upstream error |
48
- | `plugin.rbs-inline.source-rbs-annotation-not-honoured` | info | an annotation parsed successfully but contributed nothing — the file's other annotations still apply. Today this means the `# @rbs module-self: Foo` spelling; see below |
48
+ | `plugin.rbs-inline.source-rbs-annotation-not-honoured` | info | an annotation parsed successfully but contributed nothing — the file's other annotations still apply. Two causes: a member your `sig/` also declares (see [Precedence](#precedence)), and the `# @rbs module-self: Foo` spelling (see below) |
49
+
50
+ ## Precedence
51
+
52
+ When a method is declared **both** in `sig/` and by an inline
53
+ annotation, **the `.rbs` wins, per member.** The inline signature for
54
+ that one method is dropped; every other annotation in the file still
55
+ binds, and the class keeps its method surface.
56
+
57
+ ```ruby
58
+ # lib/demo.rb # sig/demo.rbs
59
+ class Demo # class Demo
60
+ # @rbs (Integer) -> String # def shared: (String) -> Integer ← this one wins
61
+ def shared(v) = v.to_s # def only_sig: () -> String
62
+ # end
63
+ # @rbs (Integer) -> Integer
64
+ def only_inline(v) = v + 1 # ← inline-only: still binds
65
+ end
66
+ ```
67
+
68
+ Each dropped member is reported once as
69
+ `plugin.rbs-inline.source-rbs-annotation-not-honoured`, naming the
70
+ member and the `.rbs` that won. Delete one of the two declarations to
71
+ make the inline annotation take effect.
72
+
73
+ `sig/` wins because it is the reviewed artefact — the one you diff in
74
+ review and the one `rigor sig-gen --diff` reasons about. There is no
75
+ upstream rule to defer to: rbs merges an inline `.rb` declaration and a
76
+ `.rbs` one into a single class entry and ranks neither, so Steep reports
77
+ the same overlap as a signature error and the class still fails to
78
+ build. Rigor keeps the reporting and drops the degradation
79
+ ([ADR-32](../../adr/32-rbs-inline-comment-ingestion.md) WD13) — left to
80
+ collide, one duplicated method costs the class every other method, and
81
+ each call on it, real methods and typos alike, reads `Dynamic[top]`.
82
+
83
+ Two overlaps this does **not** cover: a `.rbs` that collides with
84
+ *bundled* RBS (Ruby core, stdlib, a gem's signatures) is quarantined
85
+ file-by-file instead, reported as `rbs.coverage.quarantined-signature`;
86
+ and two `.rbs` files declaring the same member still fail the class's
87
+ definition build and surface as `rbs.coverage.definition-build-failed` —
88
+ neither side of that pair is more reviewed than the other, so there is
89
+ nothing to prefer.
49
90
 
50
91
  ## Which inline-RBS dialect Rigor reads
51
92
 
@@ -236,9 +236,9 @@ module Rigor
236
236
  # Walk the current diagnostic stream and report bucket-level drift. Each baseline bucket becomes one
237
237
  # DriftRow regardless of whether the current run still matches it.
238
238
  #
239
- # @param diagnostics [Array<Diagnostic>] current run's diagnostic stream (PRE-filter — pass the raw
239
+ # @param diagnostics current run's diagnostic stream (PRE-filter — pass the raw
240
240
  # `result.diagnostics` from `Runner#run`, not the post-baseline surface).
241
- # @return [Array<DriftRow>] one entry per baseline bucket, in baseline-file order.
241
+ # @return one entry per baseline bucket, in baseline-file order.
242
242
  def audit(diagnostics)
243
243
  counts = Hash.new(0)
244
244
  diagnostics.each do |diag|
@@ -53,7 +53,7 @@ module Rigor
53
53
  NODE_CLASSES = [Prism::IfNode, Prism::UnlessNode].freeze
54
54
  RULE_WALK_GATES = [:loop_or_block].freeze
55
55
 
56
- # @return [Array<Result>] one entry per qualifying predicate. Empty when the tree carries no firing
56
+ # @return one entry per qualifying predicate. Empty when the tree carries no firing
57
57
  # predicates.
58
58
  def initialize(scope_index)
59
59
  @scope_index = scope_index
@@ -30,9 +30,6 @@ module Rigor
30
30
  module DeadVersionGuardArms
31
31
  module_function
32
32
 
33
- # @param diagnostics [Array<Rigor::Analysis::Diagnostic>]
34
- # @param root [Prism::Node]
35
- # @return [Array<Rigor::Analysis::Diagnostic>]
36
33
  def filter(diagnostics, root)
37
34
  # The scan is a whole-file walk, so it is paid only when there is something to drop. A file with
38
35
  # no diagnostics — the overwhelming majority — never walks.
@@ -44,8 +41,7 @@ module Rigor
44
41
  diagnostics.reject { |diagnostic| arms.any? { |arm| covers?(arm, diagnostic) } }
45
42
  end
46
43
 
47
- # @param root [Prism::Node]
48
- # @return [Array<Prism::Location>] the source ranges of every dead version-guard arm
44
+ # @return the source ranges of every dead version-guard arm
49
45
  def scan(root)
50
46
  arms = []
51
47
  collect(root, arms)
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "prism"
4
4
 
5
+ require_relative "rule_walk"
5
6
  require_relative "../../source/constant_path"
6
7
  require_relative "../../source/node_children"
7
8
 
@@ -17,7 +18,11 @@ module Rigor
17
18
  #
18
19
  # Skipped on purpose:
19
20
  #
20
- # - Singleton-method bodies (`def self.foo`). Their ivars live on the class object, not on instances.
21
+ # - Singleton-method bodies, in either spelling: `def self.foo`, and (issue #909) any `def` lexically
22
+ # inside a `class << self` body. Their ivars live on the class object, not on instances.
23
+ # - Bodies of an anonymous-class factory block (`Class.new do … end`, `Module.new`, `Struct.new`,
24
+ # `Data.define`). The class they define is not the enclosing one, so its ivars are a third store
25
+ # again (issue #909).
21
26
  # - Class-body ivar writes outside any def — the `Module#@var` surface is a separate slice the engine
22
27
  # doesn't yet model.
23
28
  # - Nested classes / modules / defs inside a method body are barriers, mirroring the indexer's
@@ -33,7 +38,7 @@ module Rigor
33
38
  # shared full DFS that prune becomes the `:inside_def` gate; the enclosing class / module name stack
34
39
  # the legacy walk threaded as `qualified_prefix` is now `context.qualified_prefix`.
35
40
  NODE_CLASSES = [Prism::DefNode].freeze
36
- RULE_WALK_GATES = [:inside_def].freeze
41
+ RULE_WALK_GATES = %i[inside_def detached_ivar_facet].freeze
37
42
 
38
43
  # Returns `Hash[class_name (String) => Hash[ivar_name (Symbol) => Array<{node:, type:}>]]`. Empty
39
44
  # when the tree has no qualifying writes.
@@ -66,6 +71,9 @@ module Rigor
66
71
 
67
72
  def walk(node, qualified_prefix)
68
73
  return unless node.is_a?(Prism::Node)
74
+ # The legacy walk's mirror of {RuleWalk}'s `detached_ivar_facet` context (issue #909); the two
75
+ # walks share the predicate so the permanent equivalence spec keeps holding.
76
+ return if RuleWalk.detached_ivar_facet?(node)
69
77
 
70
78
  case node
71
79
  when Prism::ClassNode, Prism::ModuleNode
@@ -25,7 +25,7 @@ module Rigor
25
25
  Prism::CallNode, Prism::DefNode, Prism::IfNode, Prism::UnlessNode
26
26
  ].freeze
27
27
 
28
- # @param node_diagnostics [#call] maps a `Prism::Node` to the array of diagnostics the main pass emits
28
+ # @param node_diagnostics maps a `Prism::Node` to the array of diagnostics the main pass emits
29
29
  # for it.
30
30
  def initialize(node_diagnostics)
31
31
  @node_diagnostics = node_diagnostics