rigortype 0.2.7 → 0.2.8

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 (414) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/docs/manual/04-diagnostics.md +7 -4
  4. data/docs/manual/06-baseline.md +5 -2
  5. data/docs/manual/15-type-protection-coverage.md +6 -4
  6. data/lib/rigor/analysis/baseline.rb +56 -96
  7. data/lib/rigor/analysis/buffer_binding.rb +13 -20
  8. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +29 -48
  9. data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +35 -54
  10. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +23 -37
  11. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +16 -23
  12. data/lib/rigor/analysis/check_rules/rule_walk.rb +48 -71
  13. data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +19 -28
  14. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +48 -67
  15. data/lib/rigor/analysis/check_rules.rb +174 -274
  16. data/lib/rigor/analysis/dependency_recorder.rb +26 -38
  17. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +13 -25
  18. data/lib/rigor/analysis/dependency_source_inference/builder.rb +17 -30
  19. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +15 -26
  20. data/lib/rigor/analysis/dependency_source_inference/index.rb +45 -76
  21. data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +23 -40
  22. data/lib/rigor/analysis/dependency_source_inference/walker.rb +50 -83
  23. data/lib/rigor/analysis/dependency_source_inference.rb +6 -10
  24. data/lib/rigor/analysis/diagnostic.rb +48 -75
  25. data/lib/rigor/analysis/erb_template_detector.rb +13 -18
  26. data/lib/rigor/analysis/fact_store.rb +12 -22
  27. data/lib/rigor/analysis/incremental.rb +47 -63
  28. data/lib/rigor/analysis/incremental_session.rb +74 -100
  29. data/lib/rigor/analysis/project_scan.rb +12 -23
  30. data/lib/rigor/analysis/result.rb +3 -5
  31. data/lib/rigor/analysis/rule_catalog.rb +67 -95
  32. data/lib/rigor/analysis/run_stats.rb +38 -61
  33. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +101 -160
  34. data/lib/rigor/analysis/runner/pool_coordinator.rb +121 -188
  35. data/lib/rigor/analysis/runner/project_pre_passes.rb +62 -96
  36. data/lib/rigor/analysis/runner/run_snapshots.rb +12 -18
  37. data/lib/rigor/analysis/runner.rb +220 -333
  38. data/lib/rigor/analysis/self_call_resolution_recorder.rb +30 -43
  39. data/lib/rigor/analysis/worker_session.rb +64 -102
  40. data/lib/rigor/ast/type_node.rb +6 -8
  41. data/lib/rigor/ast.rb +6 -10
  42. data/lib/rigor/bleeding_edge.rb +27 -35
  43. data/lib/rigor/builtins/hkt_builtins.rb +7 -14
  44. data/lib/rigor/builtins/imported_refinements.rb +106 -149
  45. data/lib/rigor/builtins/predefined_constant_refinements.rb +51 -65
  46. data/lib/rigor/builtins/regex_refinement.rb +32 -43
  47. data/lib/rigor/builtins/static_return_refinements.rb +57 -81
  48. data/lib/rigor/cache/descriptor.rb +58 -94
  49. data/lib/rigor/cache/incremental_snapshot.rb +32 -48
  50. data/lib/rigor/cache/rbs_cache_producer.rb +10 -15
  51. data/lib/rigor/cache/rbs_class_ancestor_table.rb +10 -16
  52. data/lib/rigor/cache/rbs_class_type_param_names.rb +10 -16
  53. data/lib/rigor/cache/rbs_constant_table.rb +8 -13
  54. data/lib/rigor/cache/rbs_descriptor.rb +8 -13
  55. data/lib/rigor/cache/rbs_environment.rb +11 -19
  56. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +11 -19
  57. data/lib/rigor/cache/rbs_known_class_names.rb +8 -12
  58. data/lib/rigor/cache/store.rb +96 -151
  59. data/lib/rigor/cli/annotate_command.rb +51 -88
  60. data/lib/rigor/cli/baseline_command.rb +10 -17
  61. data/lib/rigor/cli/check_command.rb +121 -199
  62. data/lib/rigor/cli/check_runner_factory.rb +8 -14
  63. data/lib/rigor/cli/ci_detector.rb +9 -14
  64. data/lib/rigor/cli/command.rb +8 -14
  65. data/lib/rigor/cli/coverage_command.rb +19 -26
  66. data/lib/rigor/cli/coverage_mutation.rb +14 -19
  67. data/lib/rigor/cli/coverage_report.rb +0 -2
  68. data/lib/rigor/cli/coverage_scan.rb +7 -11
  69. data/lib/rigor/cli/diagnostic_formats.rb +43 -62
  70. data/lib/rigor/cli/diff_command.rb +10 -20
  71. data/lib/rigor/cli/docs_command.rb +26 -40
  72. data/lib/rigor/cli/doctor_command.rb +8 -9
  73. data/lib/rigor/cli/explain_command.rb +4 -7
  74. data/lib/rigor/cli/fused_protection_renderer.rb +4 -5
  75. data/lib/rigor/cli/fused_protection_report.rb +6 -8
  76. data/lib/rigor/cli/lsp_command.rb +11 -19
  77. data/lib/rigor/cli/mcp_command.rb +4 -6
  78. data/lib/rigor/cli/mutation_protection_renderer.rb +3 -4
  79. data/lib/rigor/cli/mutation_protection_report.rb +6 -9
  80. data/lib/rigor/cli/options.rb +11 -19
  81. data/lib/rigor/cli/plugin_command.rb +18 -30
  82. data/lib/rigor/cli/plugins_command.rb +29 -51
  83. data/lib/rigor/cli/plugins_renderer.rb +12 -20
  84. data/lib/rigor/cli/prism_colorizer.rb +13 -19
  85. data/lib/rigor/cli/protection_renderer.rb +5 -7
  86. data/lib/rigor/cli/protection_report.rb +27 -17
  87. data/lib/rigor/cli/renderable.rb +4 -6
  88. data/lib/rigor/cli/show_bleedingedge_command.rb +5 -8
  89. data/lib/rigor/cli/sig_gen_command.rb +14 -26
  90. data/lib/rigor/cli/skill_command.rb +30 -47
  91. data/lib/rigor/cli/skill_describe.rb +40 -64
  92. data/lib/rigor/cli/trace_command.rb +9 -16
  93. data/lib/rigor/cli/trace_renderer.rb +35 -51
  94. data/lib/rigor/cli/triage_command.rb +6 -10
  95. data/lib/rigor/cli/triage_renderer.rb +1 -2
  96. data/lib/rigor/cli/type_of_command.rb +14 -23
  97. data/lib/rigor/cli/type_of_renderer.rb +3 -5
  98. data/lib/rigor/cli/type_scan_command.rb +6 -10
  99. data/lib/rigor/cli/type_scan_renderer.rb +3 -5
  100. data/lib/rigor/cli/type_scan_report.rb +2 -3
  101. data/lib/rigor/cli/upgrade_command.rb +2 -3
  102. data/lib/rigor/cli.rb +16 -28
  103. data/lib/rigor/config_audit.rb +35 -44
  104. data/lib/rigor/configuration/dependencies.rb +41 -70
  105. data/lib/rigor/configuration/severity_profile.rb +28 -42
  106. data/lib/rigor/configuration.rb +150 -241
  107. data/lib/rigor/environment/bundle_sig_discovery.rb +74 -118
  108. data/lib/rigor/environment/class_registry.rb +19 -26
  109. data/lib/rigor/environment/constant_type_cache_holder.rb +14 -21
  110. data/lib/rigor/environment/hkt_registry_holder.rb +6 -12
  111. data/lib/rigor/environment/lockfile_resolver.rb +31 -47
  112. data/lib/rigor/environment/rbs_collection_discovery.rb +39 -63
  113. data/lib/rigor/environment/rbs_coverage_report.rb +24 -42
  114. data/lib/rigor/environment/rbs_loader.rb +420 -518
  115. data/lib/rigor/environment/reflection.rb +28 -53
  116. data/lib/rigor/environment/reporters.rb +13 -24
  117. data/lib/rigor/environment.rb +176 -291
  118. data/lib/rigor/flow_contribution/conflict.rb +16 -27
  119. data/lib/rigor/flow_contribution/element.rb +7 -12
  120. data/lib/rigor/flow_contribution/fact.rb +33 -57
  121. data/lib/rigor/flow_contribution/merge_result.rb +6 -9
  122. data/lib/rigor/flow_contribution/merger.rb +32 -47
  123. data/lib/rigor/flow_contribution.rb +37 -55
  124. data/lib/rigor/inference/acceptance.rb +133 -219
  125. data/lib/rigor/inference/block_parameter_binder.rb +52 -82
  126. data/lib/rigor/inference/body_fixpoint.rb +22 -30
  127. data/lib/rigor/inference/budget_trace.rb +43 -63
  128. data/lib/rigor/inference/builtins/array_catalog.rb +4 -6
  129. data/lib/rigor/inference/builtins/comparable_catalog.rb +4 -6
  130. data/lib/rigor/inference/builtins/complex_catalog.rb +13 -19
  131. data/lib/rigor/inference/builtins/date_catalog.rb +38 -67
  132. data/lib/rigor/inference/builtins/encoding_catalog.rb +22 -36
  133. data/lib/rigor/inference/builtins/enumerable_catalog.rb +4 -6
  134. data/lib/rigor/inference/builtins/exception_catalog.rb +38 -57
  135. data/lib/rigor/inference/builtins/hash_catalog.rb +9 -14
  136. data/lib/rigor/inference/builtins/method_catalog.rb +29 -48
  137. data/lib/rigor/inference/builtins/numeric_catalog.rb +13 -18
  138. data/lib/rigor/inference/builtins/pathname_catalog.rb +8 -13
  139. data/lib/rigor/inference/builtins/proc_catalog.rb +38 -62
  140. data/lib/rigor/inference/builtins/random_catalog.rb +21 -32
  141. data/lib/rigor/inference/builtins/range_catalog.rb +12 -21
  142. data/lib/rigor/inference/builtins/rational_catalog.rb +9 -15
  143. data/lib/rigor/inference/builtins/re_catalog.rb +29 -48
  144. data/lib/rigor/inference/builtins/set_catalog.rb +18 -27
  145. data/lib/rigor/inference/builtins/string_catalog.rb +12 -19
  146. data/lib/rigor/inference/builtins/struct_catalog.rb +18 -30
  147. data/lib/rigor/inference/builtins/time_catalog.rb +28 -44
  148. data/lib/rigor/inference/closure_escape_analyzer.rb +44 -67
  149. data/lib/rigor/inference/coverage_scanner.rb +13 -18
  150. data/lib/rigor/inference/def_return_typer.rb +8 -14
  151. data/lib/rigor/inference/dynamic_origin.rb +23 -20
  152. data/lib/rigor/inference/expression_typer.rb +774 -1152
  153. data/lib/rigor/inference/fallback.rb +8 -12
  154. data/lib/rigor/inference/fallback_tracer.rb +4 -10
  155. data/lib/rigor/inference/flow_tracer.rb +25 -36
  156. data/lib/rigor/inference/hkt_body.rb +45 -68
  157. data/lib/rigor/inference/hkt_body_parser.rb +33 -52
  158. data/lib/rigor/inference/hkt_reducer.rb +38 -59
  159. data/lib/rigor/inference/hkt_registry.rb +50 -76
  160. data/lib/rigor/inference/indexed_narrowing.rb +55 -81
  161. data/lib/rigor/inference/macro_block_self_type.rb +21 -34
  162. data/lib/rigor/inference/method_dispatcher/array_to_h_folding.rb +11 -18
  163. data/lib/rigor/inference/method_dispatcher/block_folding.rb +49 -83
  164. data/lib/rigor/inference/method_dispatcher/call_context.rb +16 -23
  165. data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +13 -22
  166. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +307 -454
  167. data/lib/rigor/inference/method_dispatcher/data_folding.rb +24 -34
  168. data/lib/rigor/inference/method_dispatcher/file_folding.rb +22 -37
  169. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +52 -81
  170. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +51 -78
  171. data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +60 -97
  172. data/lib/rigor/inference/method_dispatcher/math_folding.rb +23 -34
  173. data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +18 -26
  174. data/lib/rigor/inference/method_dispatcher/method_folding.rb +37 -71
  175. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +114 -182
  176. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +122 -193
  177. data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +15 -24
  178. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +64 -93
  179. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +31 -45
  180. data/lib/rigor/inference/method_dispatcher/set_folding.rb +6 -9
  181. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +356 -511
  182. data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +22 -32
  183. data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +15 -21
  184. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +66 -82
  185. data/lib/rigor/inference/method_dispatcher/time_folding.rb +10 -15
  186. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +9 -13
  187. data/lib/rigor/inference/method_dispatcher.rb +378 -524
  188. data/lib/rigor/inference/method_parameter_binder.rb +81 -114
  189. data/lib/rigor/inference/multi_target_binder.rb +51 -68
  190. data/lib/rigor/inference/mutation_widening.rb +115 -158
  191. data/lib/rigor/inference/narrowing.rb +509 -727
  192. data/lib/rigor/inference/origin_lookup.rb +38 -0
  193. data/lib/rigor/inference/parameter_inference_collector.rb +100 -100
  194. data/lib/rigor/inference/precision_scanner.rb +26 -35
  195. data/lib/rigor/inference/project_patched_methods.rb +20 -32
  196. data/lib/rigor/inference/project_patched_scanner.rb +23 -37
  197. data/lib/rigor/inference/protection_scanner.rb +16 -18
  198. data/lib/rigor/inference/rbs_type_translator.rb +49 -66
  199. data/lib/rigor/inference/scope_indexer.rb +473 -817
  200. data/lib/rigor/inference/statement_evaluator.rb +706 -1111
  201. data/lib/rigor/inference/struct_fold_safety.rb +34 -48
  202. data/lib/rigor/inference/synthetic_method.rb +9 -16
  203. data/lib/rigor/inference/synthetic_method_index.rb +20 -35
  204. data/lib/rigor/inference/synthetic_method_scanner.rb +63 -103
  205. data/lib/rigor/language_server/buffer_resolution.rb +6 -9
  206. data/lib/rigor/language_server/buffer_table.rb +11 -18
  207. data/lib/rigor/language_server/completion_provider.rb +69 -116
  208. data/lib/rigor/language_server/debouncer.rb +16 -25
  209. data/lib/rigor/language_server/diagnostic_publisher.rb +26 -40
  210. data/lib/rigor/language_server/document_symbol_provider.rb +14 -19
  211. data/lib/rigor/language_server/folding_range_provider.rb +11 -19
  212. data/lib/rigor/language_server/hover_provider.rb +8 -11
  213. data/lib/rigor/language_server/hover_renderer.rb +56 -93
  214. data/lib/rigor/language_server/loop.rb +14 -19
  215. data/lib/rigor/language_server/project_context.rb +46 -74
  216. data/lib/rigor/language_server/selection_range_provider.rb +9 -12
  217. data/lib/rigor/language_server/server.rb +55 -83
  218. data/lib/rigor/language_server/signature_help_provider.rb +29 -46
  219. data/lib/rigor/language_server/synchronized_writer.rb +4 -7
  220. data/lib/rigor/language_server/uri.rb +8 -13
  221. data/lib/rigor/language_server.rb +4 -6
  222. data/lib/rigor/mcp/loop.rb +2 -3
  223. data/lib/rigor/mcp/server.rb +4 -7
  224. data/lib/rigor/mcp.rb +3 -6
  225. data/lib/rigor/plugin/access_denied_error.rb +5 -8
  226. data/lib/rigor/plugin/additional_initializer.rb +21 -31
  227. data/lib/rigor/plugin/base.rb +335 -518
  228. data/lib/rigor/plugin/blueprint.rb +14 -23
  229. data/lib/rigor/plugin/box.rb +18 -29
  230. data/lib/rigor/plugin/fact_store.rb +16 -26
  231. data/lib/rigor/plugin/inflector.rb +37 -53
  232. data/lib/rigor/plugin/io_boundary.rb +33 -56
  233. data/lib/rigor/plugin/isolation.rb +42 -55
  234. data/lib/rigor/plugin/load_error.rb +10 -15
  235. data/lib/rigor/plugin/loader.rb +30 -49
  236. data/lib/rigor/plugin/macro/block_as_method.rb +20 -32
  237. data/lib/rigor/plugin/macro/heredoc_template.rb +35 -58
  238. data/lib/rigor/plugin/macro/nested_class_template.rb +22 -36
  239. data/lib/rigor/plugin/macro/trait_registry.rb +34 -51
  240. data/lib/rigor/plugin/macro.rb +10 -15
  241. data/lib/rigor/plugin/manifest.rb +85 -144
  242. data/lib/rigor/plugin/node_context.rb +14 -22
  243. data/lib/rigor/plugin/node_rule_walk.rb +49 -74
  244. data/lib/rigor/plugin/protocol_contract.rb +25 -39
  245. data/lib/rigor/plugin/registry.rb +132 -205
  246. data/lib/rigor/plugin/services.rb +21 -33
  247. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -19
  248. data/lib/rigor/plugin/trust_policy.rb +24 -38
  249. data/lib/rigor/plugin/type_node_resolver.rb +15 -24
  250. data/lib/rigor/protection/diagnostic_oracle.rb +11 -13
  251. data/lib/rigor/protection/mutation_scanner.rb +27 -35
  252. data/lib/rigor/protection/mutator.rb +50 -70
  253. data/lib/rigor/protection/test_suite_oracle.rb +20 -27
  254. data/lib/rigor/rbs_extended/conformance_checker.rb +48 -68
  255. data/lib/rigor/rbs_extended/hkt_directives.rb +28 -54
  256. data/lib/rigor/rbs_extended/reporter.rb +24 -40
  257. data/lib/rigor/rbs_extended.rb +107 -197
  258. data/lib/rigor/reflection.rb +68 -86
  259. data/lib/rigor/scope/discovery_index.rb +14 -19
  260. data/lib/rigor/scope.rb +255 -310
  261. data/lib/rigor/sig_gen/classification.rb +6 -10
  262. data/lib/rigor/sig_gen/generator.rb +197 -323
  263. data/lib/rigor/sig_gen/layout_index.rb +12 -20
  264. data/lib/rigor/sig_gen/method_candidate.rb +12 -17
  265. data/lib/rigor/sig_gen/observation_collector.rb +38 -70
  266. data/lib/rigor/sig_gen/observed_call.rb +13 -23
  267. data/lib/rigor/sig_gen/path_mapper.rb +17 -29
  268. data/lib/rigor/sig_gen/renderer.rb +7 -13
  269. data/lib/rigor/sig_gen/type_elaborator.rb +15 -28
  270. data/lib/rigor/sig_gen/write_result.rb +8 -16
  271. data/lib/rigor/sig_gen/writer.rb +95 -174
  272. data/lib/rigor/sig_gen.rb +3 -6
  273. data/lib/rigor/signature_path_audit.rb +24 -30
  274. data/lib/rigor/source/constant_path.rb +10 -14
  275. data/lib/rigor/source/literals.rb +31 -45
  276. data/lib/rigor/source/node_locator.rb +9 -11
  277. data/lib/rigor/source/node_walker.rb +9 -13
  278. data/lib/rigor/source.rb +3 -4
  279. data/lib/rigor/testing.rb +16 -20
  280. data/lib/rigor/triage/catalogue.rb +38 -62
  281. data/lib/rigor/triage.rb +31 -52
  282. data/lib/rigor/trinary.rb +9 -13
  283. data/lib/rigor/type/acceptance_router.rb +4 -6
  284. data/lib/rigor/type/accepts_result.rb +10 -14
  285. data/lib/rigor/type/app.rb +19 -27
  286. data/lib/rigor/type/bot.rb +4 -6
  287. data/lib/rigor/type/bound_method.rb +10 -15
  288. data/lib/rigor/type/combinator.rb +165 -257
  289. data/lib/rigor/type/constant.rb +23 -34
  290. data/lib/rigor/type/data_class.rb +10 -15
  291. data/lib/rigor/type/data_instance.rb +14 -20
  292. data/lib/rigor/type/difference.rb +21 -32
  293. data/lib/rigor/type/dynamic.rb +3 -5
  294. data/lib/rigor/type/hash_shape.rb +32 -18
  295. data/lib/rigor/type/integer_range.rb +11 -16
  296. data/lib/rigor/type/intersection.rb +27 -42
  297. data/lib/rigor/type/nominal.rb +10 -15
  298. data/lib/rigor/type/plain_lattice.rb +9 -13
  299. data/lib/rigor/type/refined.rb +67 -114
  300. data/lib/rigor/type/singleton.rb +4 -6
  301. data/lib/rigor/type/struct_class.rb +11 -16
  302. data/lib/rigor/type/struct_instance.rb +15 -21
  303. data/lib/rigor/type/tuple.rb +14 -19
  304. data/lib/rigor/type/union.rb +30 -42
  305. data/lib/rigor/type_node/generic.rb +14 -26
  306. data/lib/rigor/type_node/identifier.rb +12 -19
  307. data/lib/rigor/type_node.rb +3 -12
  308. data/lib/rigor/value_semantics.rb +16 -21
  309. data/lib/rigor/version.rb +1 -1
  310. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +18 -30
  311. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +16 -29
  312. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +12 -23
  313. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +22 -39
  314. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +26 -46
  315. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +51 -87
  316. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +10 -17
  317. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +26 -39
  318. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +118 -176
  319. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +30 -51
  320. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +57 -92
  321. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +71 -108
  322. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +13 -22
  323. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +15 -25
  324. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +8 -13
  325. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +16 -24
  326. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +30 -47
  327. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +79 -129
  328. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +36 -57
  329. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +27 -41
  330. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +16 -24
  331. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +148 -235
  332. data/plugins/rigor-activerecord/lib/rigor-activerecord.rb +3 -5
  333. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +8 -14
  334. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +9 -15
  335. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +4 -7
  336. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +23 -41
  337. data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +3 -5
  338. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +6 -10
  339. data/plugins/rigor-activesupport-core-ext/lib/rigor-activesupport-core-ext.rb +8 -11
  340. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +30 -45
  341. data/plugins/rigor-devise/lib/rigor-devise.rb +3 -5
  342. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +43 -69
  343. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +26 -35
  344. data/plugins/rigor-dry-schema/lib/rigor-dry-schema.rb +3 -5
  345. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +30 -47
  346. data/plugins/rigor-dry-struct/lib/rigor-dry-struct.rb +3 -5
  347. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +67 -113
  348. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +29 -46
  349. data/plugins/rigor-dry-types/lib/rigor-dry-types.rb +3 -5
  350. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -20
  351. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +14 -23
  352. data/plugins/rigor-dry-validation/lib/rigor-dry-validation.rb +2 -3
  353. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +22 -37
  354. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +28 -46
  355. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +13 -17
  356. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +30 -47
  357. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +52 -80
  358. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +21 -31
  359. data/plugins/rigor-graphql/lib/rigor-graphql.rb +3 -5
  360. data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +11 -17
  361. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +20 -34
  362. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +45 -75
  363. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +22 -36
  364. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +22 -41
  365. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +17 -32
  366. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +6 -12
  367. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +4 -8
  368. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +17 -32
  369. data/plugins/rigor-rails/lib/rigor-rails.rb +11 -18
  370. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +58 -92
  371. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +20 -34
  372. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +20 -31
  373. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +49 -82
  374. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +55 -92
  375. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +51 -90
  376. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +17 -30
  377. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +38 -70
  378. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +42 -74
  379. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +280 -497
  380. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +54 -89
  381. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +32 -54
  382. data/plugins/rigor-rbs-inline/lib/rigor-rbs-inline.rb +8 -13
  383. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +9 -16
  384. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +24 -39
  385. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +13 -22
  386. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +34 -56
  387. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/scope_walker.rb +22 -42
  388. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +43 -75
  389. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +27 -39
  390. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/http_status_codes.rb +14 -19
  391. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +20 -34
  392. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +28 -47
  393. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +21 -37
  394. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +13 -22
  395. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +18 -31
  396. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +6 -10
  397. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +14 -29
  398. data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +23 -41
  399. data/plugins/rigor-sinatra/lib/rigor-sinatra.rb +3 -5
  400. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +23 -38
  401. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +57 -96
  402. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +22 -35
  403. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +41 -69
  404. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/method_signature.rb +6 -10
  405. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +24 -41
  406. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +29 -50
  407. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +55 -88
  408. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +159 -262
  409. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +21 -29
  410. data/plugins/rigor-statesman/lib/rigor-statesman.rb +3 -5
  411. data/plugins/rigor-typescript-utility-types/lib/rigor/plugin/typescript_utility_types.rb +23 -37
  412. data/plugins/rigor-typescript-utility-types/lib/rigor-typescript-utility-types.rb +4 -6
  413. data/sig/rigor/scope.rbs +6 -0
  414. metadata +2 -1
@@ -11,8 +11,8 @@ require_relative "command"
11
11
 
12
12
  module Rigor
13
13
  class CLI
14
- # ADR-22 — `rigor baseline {generate,regenerate,dump,drift,
15
- # prune}` subcommands, backed by `Rigor::Analysis::Baseline`.
14
+ # ADR-22 — `rigor baseline {generate,regenerate,dump,drift, prune}` subcommands, backed by
15
+ # `Rigor::Analysis::Baseline`.
16
16
  #
17
17
  # rigor baseline generate # default: rule-ID rows
18
18
  # rigor baseline generate --match-mode message
@@ -64,12 +64,9 @@ module Rigor
64
64
  write_baseline(options, verb: "wrote baseline to")
65
65
  end
66
66
 
67
- # ADR-22 slice 5 — `regenerate` is `generate --force`: the
68
- # end-of-quality-improvement-session refresh after landing
69
- # baseline-reducing fixes. It rewrites the file
70
- # unconditionally (no existence guard, no `--force` flag),
71
- # so `rigor baseline regenerate` reads cleanly as "make the
72
- # baseline match reality again".
67
+ # ADR-22 slice 5 — `regenerate` is `generate --force`: the end-of-quality-improvement-session refresh after
68
+ # landing baseline-reducing fixes. It rewrites the file unconditionally (no existence guard, no `--force` flag),
69
+ # so `rigor baseline regenerate` reads cleanly as "make the baseline match reality again".
73
70
  def run_regenerate
74
71
  options = parse_generate_options(subcommand: "regenerate")
75
72
  write_baseline(options, verb: "regenerated baseline")
@@ -126,10 +123,8 @@ module Rigor
126
123
 
127
124
  def collect_diagnostics(configuration, _options)
128
125
  cache_store = Cache::Store.new(root: configuration.cache_path)
129
- # IMPORTANT: do NOT activate the existing baseline when
130
- # generating a fresh one otherwise the new file
131
- # records the post-filter (silenced) diagnostic set,
132
- # which is empty after a successful first run.
126
+ # IMPORTANT: do NOT activate the existing baseline when generating a fresh one — otherwise the new file records
127
+ # the post-filter (silenced) diagnostic set, which is empty after a successful first run.
133
128
  configuration_for_generation = override_configuration_baseline_off(configuration)
134
129
  runner = Analysis::Runner.new(
135
130
  configuration: configuration_for_generation,
@@ -140,9 +135,8 @@ module Rigor
140
135
  end
141
136
 
142
137
  def override_configuration_baseline_off(configuration)
143
- # Synthesise a new Configuration with `baseline` explicitly
144
- # disabled. The original Configuration is frozen-ish so we
145
- # round-trip through the constructor with an override hash.
138
+ # Synthesise a new Configuration with `baseline` explicitly disabled. The original Configuration is frozen-ish
139
+ # so we round-trip through the constructor with an override hash.
146
140
  defaults = Configuration::DEFAULTS.merge(
147
141
  "paths" => configuration.paths,
148
142
  "exclude" => configuration.exclude_patterns,
@@ -206,8 +200,7 @@ module Rigor
206
200
  return
207
201
  end
208
202
 
209
- # Group by rule for readability; rules with the most
210
- # entries first.
203
+ # Group by rule for readability; rules with the most entries first.
211
204
  by_rule = rows.group_by(&:rule).sort_by { |_rule, group| -group.size }
212
205
  by_rule.each do |rule, group|
213
206
  total = group.sum(&:count)
@@ -19,17 +19,14 @@ module Rigor
19
19
  class CLI
20
20
  # Executes `rigor check` — the analyzer's primary command.
21
21
  #
22
- # The other subcommands delegate to a `CLI::Command` subclass once they
23
- # grow beyond a few lines; `check` is the largest of them, owning option
24
- # parsing, the baseline filter (ADR-22), the incremental modes (ADR-46),
25
- # cache-stats reporting, editor mode, the CI-native output formats
26
- # (ADR-51), and the diagnostic-only / heap / budget appendices. Keeping
27
- # it in its own class follows the same dispatch-vs-implementation split
28
- # the rest of the CLI uses and keeps `Rigor::CLI` focused on dispatch.
22
+ # The other subcommands delegate to a `CLI::Command` subclass once they grow beyond a few lines; `check` is the
23
+ # largest of them, owning option parsing, the baseline filter (ADR-22), the incremental modes (ADR-46), cache-stats
24
+ # reporting, editor mode, the CI-native output formats (ADR-51), and the diagnostic-only / heap / budget appendices.
25
+ # Keeping it in its own class follows the same dispatch-vs-implementation split the rest of the CLI uses and keeps
26
+ # `Rigor::CLI` focused on dispatch.
29
27
  #
30
- # The class-length budget is relaxed (as on `Rigor::CLI` itself) because
31
- # `check` aggregates several independent concerns that are clearer read
32
- # together than split across micro-classes.
28
+ # The class-length budget is relaxed (as on `Rigor::CLI` itself) because `check` aggregates several independent
29
+ # concerns that are clearer read together than split across micro-classes.
33
30
  class CheckCommand < Command # rubocop:disable Metrics/ClassLength
34
31
  # @return [Integer] CLI exit status.
35
32
  def run # rubocop:disable Metrics/AbcSize
@@ -69,9 +66,8 @@ module Rigor
69
66
 
70
67
  private
71
68
 
72
- # ADR-46 — the two incremental-analysis check modes both fully handle
73
- # the run and return an exit code (so `run` short-circuits);
74
- # returns nil for an ordinary check.
69
+ # ADR-46 — the two incremental-analysis check modes both fully handle the run and return an exit code (so `run`
70
+ # short-circuits); returns nil for an ordinary check.
75
71
  def dispatch_special_check_mode(configuration, options, cache_root)
76
72
  return run_verify_incremental(configuration) if options.fetch(:verify_incremental)
77
73
  return run_incremental_check(configuration, options, cache_root) if options.fetch(:incremental)
@@ -79,22 +75,19 @@ module Rigor
79
75
  nil
80
76
  end
81
77
 
82
- # ADR-46 — the incremental-analysis acceptance gate. Runs a baseline
83
- # analysis (recording cross-file dependencies), then re-analyzes a
84
- # representative subset of files and serves the rest from the per-file
85
- # cache (the body tier), and asserts the merged diagnostics are
86
- # byte-identical to a full `--no-cache` analysis. A mismatch means the
87
- # incremental machinery would serve a stale — manufactured —
88
- # diagnostic, the soundness failure this gate exists to catch. Prints a
89
- # one-line PASS (exit 0) or the differing diagnostics (exit 1).
78
+ # ADR-46 — the incremental-analysis acceptance gate. Runs a baseline analysis (recording cross-file dependencies),
79
+ # then re-analyzes a representative subset of files and serves the rest from the per-file cache (the body tier),
80
+ # and asserts the merged diagnostics are byte-identical to a full `--no-cache` analysis. A mismatch means the
81
+ # incremental machinery would serve a stale — manufactured — diagnostic, the soundness failure this gate exists to
82
+ # catch. Prints a one-line PASS (exit 0) or the differing diagnostics (exit 1).
90
83
  def run_verify_incremental(configuration)
91
84
  paths = @argv.empty? ? nil : @argv
92
85
  session = Analysis::IncrementalSession.new(configuration: configuration, paths: paths)
93
86
  session.baseline
94
87
  analyzed = session.analyzed_files
95
88
 
96
- # Every other file forms the re-analyzed subset, so the run exercises
97
- # BOTH the subset-analysis path and the cache-serving path.
89
+ # Every other file forms the re-analyzed subset, so the run exercises BOTH the subset-analysis path and the
90
+ # cache-serving path.
98
91
  subset = analyzed.each_with_index.select { |_, index| index.even? }.map(&:first)
99
92
  incremental = normalize_diagnostics(session.reanalyze_subset(subset))
100
93
  full = normalize_diagnostics(verify_full_diagnostics(configuration, paths))
@@ -102,14 +95,11 @@ module Rigor
102
95
  report_verify_incremental(incremental, full, subset_size: subset.size, total: analyzed.size)
103
96
  end
104
97
 
105
- # ADR-46 — cross-process incremental analysis (`--incremental`). Derives
106
- # the global fingerprint cheaply (no RBS env build), loads the disk
107
- # snapshot, and on a fingerprint hit re-analyzes only the files changed
108
- # since the last run (plus their dependents), serving the rest from the
109
- # snapshot; on a miss runs a full baseline. Persists the updated
110
- # snapshot for the next invocation. Diagnostics are identical to a full
111
- # run (the `--verify-incremental` gate enforces this); the win is
112
- # skipping per-file inference for unchanged files.
98
+ # ADR-46 — cross-process incremental analysis (`--incremental`). Derives the global fingerprint cheaply (no RBS
99
+ # env build), loads the disk snapshot, and on a fingerprint hit re-analyzes only the files changed since the last
100
+ # run (plus their dependents), serving the rest from the snapshot; on a miss runs a full baseline. Persists the
101
+ # updated snapshot for the next invocation. Diagnostics are identical to a full run (the `--verify-incremental`
102
+ # gate enforces this); the win is skipping per-file inference for unchanged files.
113
103
  def run_incremental_check(configuration, options, cache_root)
114
104
  paths = @argv.empty? ? nil : @argv
115
105
  probe = Analysis::Runner.new(configuration: configuration, cache_store: nil)
@@ -159,14 +149,11 @@ module Rigor
159
149
  1
160
150
  end
161
151
 
162
- # ADR-22 slice 5 — the `--baseline-strict` CI gate. When the
163
- # flag is set, ANY baseline drift fails the run not only
164
- # excess drift (a bucket over threshold, which already fails
165
- # via the surfaced diagnostics) but also DEFICIT drift
166
- # (`actual < count`: the baseline has grown looser than the
167
- # code and should be regenerated). A no-op, with a stderr
168
- # note, when no baseline is active — the flag never
169
- # implicitly loads a baseline the config did not name (WD2).
152
+ # ADR-22 slice 5 — the `--baseline-strict` CI gate. When the flag is set, ANY baseline drift fails the run — not
153
+ # only excess drift (a bucket over threshold, which already fails via the surfaced diagnostics) but also DEFICIT
154
+ # drift (`actual < count`: the baseline has grown looser than the code and should be regenerated). A no-op, with a
155
+ # stderr note, when no baseline is active — the flag never implicitly loads a baseline the config did not name
156
+ # (WD2).
170
157
  def baseline_strict_violation?(raw_diagnostics, configuration, options)
171
158
  return false unless options.fetch(:baseline_strict)
172
159
 
@@ -199,22 +186,17 @@ module Rigor
199
186
  @err.puts("rigor: run `rigor baseline regenerate` to refresh the baseline.")
200
187
  end
201
188
 
202
- # ADR-22 — apply the baseline filter as the LAST step of
203
- # the diagnostic pipeline (after `# rigor:disable`,
204
- # `severity_profile`, etc. — WD6). Resolution order
205
- # follows WD2 (b):
189
+ # ADR-22 — apply the baseline filter as the LAST step of the diagnostic pipeline (after `# rigor:disable`,
190
+ # `severity_profile`, etc. WD6). Resolution order follows WD2 (b):
206
191
  #
207
192
  # 1. --no-baseline on the CLI → no baseline.
208
193
  # 2. --baseline=PATH on the CLI → load that path.
209
194
  # 3. .rigor.yml's `baseline: <path>` → load that path.
210
195
  # 4. otherwise → no baseline.
211
196
  #
212
- # When the path resolves and loads successfully, the filter
213
- # replaces `result.diagnostics` with the surfaced set and
214
- # writes a one-line summary to stderr (WD7) when any
215
- # diagnostics were silenced. Load failures emit a warning
216
- # to stderr and fall through to "no baseline" (graceful
217
- # degradation).
197
+ # When the path resolves and loads successfully, the filter replaces `result.diagnostics` with the surfaced set
198
+ # and writes a one-line summary to stderr (WD7) when any diagnostics were silenced. Load failures emit a warning
199
+ # to stderr and fall through to "no baseline" (graceful degradation).
218
200
  def apply_baseline_filter(result, configuration, options)
219
201
  path = resolve_baseline_path(configuration, options)
220
202
  return result if path.nil?
@@ -260,60 +242,42 @@ module Rigor
260
242
  clear_cache: false,
261
243
  no_cache: false,
262
244
  stats: true,
263
- # ADR-15 Phase 4c — when nil, falls back to
264
- # `RIGOR_RACTOR_WORKERS` then `.rigor.yml`
265
- # `parallel.workers:` then 0 (sequential). See
266
- # `resolve_workers` for the precedence chain.
245
+ # ADR-15 Phase 4c — when nil, falls back to `RIGOR_RACTOR_WORKERS` then `.rigor.yml` `parallel.workers:` then
246
+ # 0 (sequential). See `resolve_workers` for the precedence chain.
267
247
  workers: nil,
268
248
  tmp_file: nil,
269
249
  instead_of: nil,
270
- # ADR-22 — baseline filter. `:unset` means "fall through
271
- # to `.rigor.yml`'s `baseline:` key"; a String overrides
272
- # the config; `false` (from `--no-baseline`) suppresses
273
- # any baseline that the config might name.
250
+ # ADR-22 — baseline filter. `:unset` means "fall through to `.rigor.yml`'s `baseline:` key"; a String
251
+ # overrides the config; `false` (from `--no-baseline`) suppresses any baseline that the config might name.
274
252
  baseline: :unset,
275
- # ADR-22 slice 5 — `--baseline-strict` CI gate: fail the
276
- # run on any baseline drift, in either direction.
253
+ # ADR-22 slice 5 — `--baseline-strict` CI gate: fail the run on any baseline drift, in either direction.
277
254
  baseline_strict: false,
278
- # ADR-32 WD10 carry-over — `--treat-all-as-inline-rbs`
279
- # forces the `rigor-rbs-inline` plugin into the loaded
280
- # plugin set with `require_magic_comment: false` so a
281
- # single ad-hoc `rigor check` invocation treats every
282
- # analysed file as inline-RBS without the user editing
283
- # `.rigor.yml`. Intended for single-file / ad-hoc CI use;
284
- # ordinary projects should configure the plugin in
285
- # `.rigor.yml`.
255
+ # ADR-32 WD10 carry-over — `--treat-all-as-inline-rbs` forces the `rigor-rbs-inline` plugin into the loaded
256
+ # plugin set with `require_magic_comment: false` so a single ad-hoc `rigor check` invocation treats every
257
+ # analysed file as inline-RBS without the user editing `.rigor.yml`. Intended for single-file / ad-hoc CI use;
258
+ # ordinary projects should configure the plugin in `.rigor.yml`.
286
259
  treat_all_as_inline_rbs: false,
287
- # ADR-46 — the incremental-analysis acceptance gate. Runs a
288
- # baseline analysis, re-analyzes a subset and serves the rest from
289
- # the per-file cache, and asserts the merged diagnostics are
290
- # byte-identical to a full `--no-cache` run. Exits non-zero on any
291
- # mismatch. Off by default.
260
+ # ADR-46 — the incremental-analysis acceptance gate. Runs a baseline analysis, re-analyzes a subset and serves
261
+ # the rest from the per-file cache, and asserts the merged diagnostics are byte-identical to a full
262
+ # `--no-cache` run. Exits non-zero on any mismatch. Off by default.
292
263
  verify_incremental: false,
293
- # ADR-46 — cross-process incremental analysis. With a disk snapshot
294
- # of the prior run's per-file diagnostics + dependency graph,
295
- # re-analyzes only the changed closure and serves the rest from the
296
- # snapshot. Off by default.
264
+ # ADR-46 — cross-process incremental analysis. With a disk snapshot of the prior run's per-file diagnostics +
265
+ # dependency graph, re-analyzes only the changed closure and serves the rest from the snapshot. Off by
266
+ # default.
297
267
  incremental: false,
298
- # ADR-51 WD7 — CI auto-detection. When the default `text` format is
299
- # in effect and a first-class CI is detected (GitHub Actions /
300
- # TeamCity), also emit that platform's native annotations on top of
301
- # the human output; for GitLab / reviewdog-routed CIs, print a
302
- # one-line hint. On by default; `--no-ci-detect` (or
268
+ # ADR-51 WD7 — CI auto-detection. When the default `text` format is in effect and a first-class CI is detected
269
+ # (GitHub Actions / TeamCity), also emit that platform's native annotations on top of the human output; for
270
+ # GitLab / reviewdog-routed CIs, print a one-line hint. On by default; `--no-ci-detect` (or
303
271
  # `RIGOR_CI_DETECT=0`) disables it.
304
272
  ci_detect: true,
305
- # ADR-50 § WD2 — the `--bleeding-edge[=ids]` / `--no-bleeding-edge`
306
- # CLI mirror of the `bleeding_edge:` config key. `:unset` means "no
307
- # flag use the configured selection"; `true` adopts the whole
308
- # overlay, `false` adopts none, and an Array of ids adopts only
309
- # those (see `apply_bleeding_edge_override`).
273
+ # ADR-50 § WD2 — the `--bleeding-edge[=ids]` / `--no-bleeding-edge` CLI mirror of the `bleeding_edge:` config
274
+ # key. `:unset` means "no flag — use the configured selection"; `true` adopts the whole overlay, `false`
275
+ # adopts none, and an Array of ids adopts only those (see `apply_bleeding_edge_override`).
310
276
  bleeding_edge: :unset,
311
- # Type-precision coverage block. Off by default — it is a
312
- # second precision pass over the analyzed files (the same scan
313
- # `rigor coverage` runs), so it is opt-in to keep the default
314
- # check path's cost unchanged. When set, `--format json` gains
315
- # a `coverage` object (scan_files + precision tiers) and the
316
- # text output prints a one-line coverage summary.
277
+ # Type-precision coverage block. Off by default — it is a second precision pass over the analyzed files (the
278
+ # same scan `rigor coverage` runs), so it is opt-in to keep the default check path's cost unchanged. When set,
279
+ # `--format json` gains a `coverage` object (scan_files + precision tiers) and the text output prints a
280
+ # one-line coverage summary.
317
281
  coverage: false
318
282
  }
319
283
  parser = OptionParser.new do |opts| # rubocop:disable Metrics/BlockLength
@@ -368,9 +332,8 @@ module Rigor
368
332
  "ADR-51: do not auto-emit CI-native output when a CI environment is detected") do
369
333
  options[:ci_detect] = false
370
334
  end
371
- # ADR-50 § WD2 — `=[LIST]` (not ` [LIST]`) so a bare `--bleeding-edge`
372
- # never swallows a following positional path: `rigor check
373
- # --bleeding-edge lib` adopts the whole overlay and checks `lib`.
335
+ # ADR-50 § WD2 — `=[LIST]` (not ` [LIST]`) so a bare `--bleeding-edge` never swallows a following positional
336
+ # path: `rigor check --bleeding-edge lib` adopts the whole overlay and checks `lib`.
374
337
  opts.on("--bleeding-edge=[LIST]",
375
338
  "ADR-50: adopt the bleeding-edge overlay for this run " \
376
339
  "(all features, or a comma-separated feature-id list)") do |value|
@@ -385,36 +348,26 @@ module Rigor
385
348
  options
386
349
  end
387
350
 
388
- # Surfaces the class of mistake where a configured value resolves
389
- # to nothing a typo'd or moved `signature_paths:` / bundler /
390
- # collection path, an unknown `libraries:` name, an inert
391
- # `disable:` / `severity_overrides:` rule id ({ConfigAudit}). The
392
- # loader filters each one silently, and the downstream symptom is
393
- # confusing: missing signatures turn calls into high-confidence
394
- # `call.undefined-method` firings, and an unrecognised suppression
395
- # token leaves the rule firing as if the line were never written
396
- # so a one-character mistake can read as hundreds of real errors.
397
- # Each finding is emitted to STDERR (a warning, not a hard error —
398
- # partial / optional bundles are a valid setup) and the returned
399
- # list rides into the `--format=json` payload under `config_warnings`
400
- # so CI and framework consumers can assert on it. The audits only
401
- # fire on explicit, working-setup-safe signals (see {ConfigAudit}).
351
+ # Surfaces the class of mistake where a configured value resolves to nothing — a typo'd or moved
352
+ # `signature_paths:` / bundler / collection path, an unknown `libraries:` name, an inert `disable:` /
353
+ # `severity_overrides:` rule id ({ConfigAudit}). The loader filters each one silently, and the downstream symptom
354
+ # is confusing: missing signatures turn calls into high-confidence `call.undefined-method` firings, and an
355
+ # unrecognised suppression token leaves the rule firing as if the line were never written — so a one-character
356
+ # mistake can read as hundreds of real errors. Each finding is emitted to STDERR (a warning, not a hard error —
357
+ # partial / optional bundles are a valid setup) and the returned list rides into the `--format=json` payload under
358
+ # `config_warnings` so CI and framework consumers can assert on it. The audits only fire on explicit,
359
+ # working-setup-safe signals (see {ConfigAudit}).
402
360
  def warn_unresolved_config(configuration)
403
361
  warnings = ConfigAudit.warnings(configuration)
404
362
  warnings.each { |warning| @err.puts("rigor: #{warning.message}") }
405
363
  warnings
406
364
  end
407
365
 
408
- # ADR-32 WD10 carry-over — wraps `Configuration.load` so the
409
- # CLI's `--treat-all-as-inline-rbs` flag can inject a
410
- # `rigor-rbs-inline` plugin entry with
411
- # `require_magic_comment: false` into the loaded plugin
412
- # set. Re-runs the include-aware YAML load and applies the
413
- # injection before `Configuration.new` so the new entry
414
- # follows the normal coercion path. A pre-existing
415
- # `rigor-rbs-inline` entry (by gem name or `id: rbs-inline`)
416
- # is removed first so the synthesised entry's
417
- # `require_magic_comment: false` wins unconditionally.
366
+ # ADR-32 WD10 carry-over — wraps `Configuration.load` so the CLI's `--treat-all-as-inline-rbs` flag can inject a
367
+ # `rigor-rbs-inline` plugin entry with `require_magic_comment: false` into the loaded plugin set. Re-runs the
368
+ # include-aware YAML load and applies the injection before `Configuration.new` so the new entry follows the normal
369
+ # coercion path. A pre-existing `rigor-rbs-inline` entry (by gem name or `id: rbs-inline`) is removed first so the
370
+ # synthesised entry's `require_magic_comment: false` wins unconditionally.
418
371
  def load_check_configuration(options)
419
372
  return Configuration.load(options.fetch(:config)) unless options.fetch(:treat_all_as_inline_rbs)
420
373
 
@@ -425,13 +378,11 @@ module Rigor
425
378
  Configuration.new(Configuration::DEFAULTS.merge(data))
426
379
  end
427
380
 
428
- # ADR-50 § WD2 — applies the `--bleeding-edge[=ids]` / `--no-bleeding-edge`
429
- # CLI selection over the configured `bleeding_edge:` value, mirroring the
430
- # CLI-over-config precedence `--workers` and `--no-cache` follow. `:unset`
431
- # (no flag) leaves the loaded configuration untouched; any other value is
432
- # normalised by {Configuration#with_bleeding_edge}, so the two
433
- # `SeverityProfile.resolve` sites (and the worker path, which receives the
434
- # whole frozen Configuration) see the run's selection.
381
+ # ADR-50 § WD2 — applies the `--bleeding-edge[=ids]` / `--no-bleeding-edge` CLI selection over the configured
382
+ # `bleeding_edge:` value, mirroring the CLI-over-config precedence `--workers` and `--no-cache` follow. `:unset`
383
+ # (no flag) leaves the loaded configuration untouched; any other value is normalised by
384
+ # {Configuration#with_bleeding_edge}, so the two `SeverityProfile.resolve` sites (and the worker path, which
385
+ # receives the whole frozen Configuration) see the run's selection.
435
386
  def apply_bleeding_edge_override(configuration, options)
436
387
  selection = options.fetch(:bleeding_edge)
437
388
  return configuration if selection == :unset
@@ -469,30 +420,24 @@ module Rigor
469
420
  end
470
421
  end
471
422
 
472
- # Emits the {Analysis::RunStats} summary to STDERR so it
473
- # doesn't interleave with the diagnostic stream (text or
474
- # JSON) on STDOUT. JSON consumers can pipe stdout cleanly;
475
- # interactive users still see the summary on their tty.
423
+ # Emits the {Analysis::RunStats} summary to STDERR so it doesn't interleave with the diagnostic stream (text or
424
+ # JSON) on STDOUT. JSON consumers can pipe stdout cleanly; interactive users still see the summary on their tty.
476
425
  def write_run_stats(stats)
477
426
  @err.puts("")
478
427
  stats.format(@err)
479
428
  end
480
429
 
481
- # Opt-in developer diagnostics printed after the run: the
482
- # inference-cutoff trace (RIGOR_BUDGET_TRACE) and the heap-attribution
483
- # profile (RIGOR_HEAP_PROFILE). Each gates itself, so this is a no-op
484
- # on a normal run.
430
+ # Opt-in developer diagnostics printed after the run: the inference-cutoff trace (RIGOR_BUDGET_TRACE) and the
431
+ # heap-attribution profile (RIGOR_HEAP_PROFILE). Each gates itself, so this is a no-op on a normal run.
485
432
  def write_trace_appendices
486
433
  write_budget_trace
487
434
  write_heap_profile
488
435
  end
489
436
 
490
- # Dumps the opt-in inference-cutoff counters (RIGOR_BUDGET_TRACE).
491
- # These are the hard-coded "budget" guards that silently degrade
492
- # to `Dynamic[top]` / a fallback bound counting them shows where
493
- # inference actually stopped. Process-global counters: meaningful
494
- # only on a single-process run (`--workers 0`), since they do not
495
- # cross fork boundaries.
437
+ # Dumps the opt-in inference-cutoff counters (RIGOR_BUDGET_TRACE). These are the hard-coded "budget" guards that
438
+ # silently degrade to `Dynamic[top]` / a fallback bound — counting them shows where inference actually stopped.
439
+ # Process-global counters: meaningful only on a single-process run (`--workers 0`), since they do not cross fork
440
+ # boundaries.
496
441
  def write_budget_trace
497
442
  return unless Inference::BudgetTrace.enabled?
498
443
 
@@ -508,11 +453,9 @@ module Rigor
508
453
  write_budget_distributions
509
454
  end
510
455
 
511
- # Dumps the read-only size distributions (ADR-41 Slice 2a). These
512
- # observe how large unions actually get, with no cap enforced the
513
- # data the `union_size` budget default should be chosen from. The
514
- # `over` thresholds bracket the TypeProf prior (10) and Rigor's spec
515
- # default (24).
456
+ # Dumps the read-only size distributions (ADR-41 Slice 2a). These observe how large unions actually get, with no
457
+ # cap enforced the data the `union_size` budget default should be chosen from. The `over` thresholds bracket the
458
+ # TypeProf prior (10) and Rigor's spec default (24).
516
459
  def write_budget_distributions
517
460
  summary = Inference::BudgetTrace.summarize(Inference::BudgetTrace::UNION_ARITY, over: [10, 24, 40])
518
461
  pct = summary[:percentiles]
@@ -522,14 +465,11 @@ module Rigor
522
465
  @err.puts(" unions ≥10: #{over[10]} ≥24: #{over[24]} ≥40: #{over[40]}")
523
466
  end
524
467
 
525
- # Dumps a live-heap class breakdown (RIGOR_HEAP_PROFILE) — retained
526
- # objects by class after a forced GC, ranked by total memsize. The
527
- # tool for attributing where the analyzer's resident memory goes
528
- # (ADR-41 Slice 2b): it answers whether the heap is type carriers,
529
- # RBS objects, Prism nodes, or fact-store Hashes/Strings. Walking the
530
- # whole heap is slow — a dev probe, not a normal diagnostic. Run
531
- # single-process (`--workers 0`) so the parent heap is the analysis
532
- # heap; the gem is required lazily so a normal run never loads it.
468
+ # Dumps a live-heap class breakdown (RIGOR_HEAP_PROFILE) — retained objects by class after a forced GC, ranked by
469
+ # total memsize. The tool for attributing where the analyzer's resident memory goes (ADR-41 Slice 2b): it answers
470
+ # whether the heap is type carriers, RBS objects, Prism nodes, or fact-store Hashes/Strings. Walking the whole
471
+ # heap is slow — a dev probe, not a normal diagnostic. Run single-process (`--workers 0`) so the parent heap is
472
+ # the analysis heap; the gem is required lazily so a normal run never loads it.
533
473
  def write_heap_profile
534
474
  return if ENV["RIGOR_HEAP_PROFILE"].to_s.empty?
535
475
 
@@ -543,9 +483,8 @@ module Rigor
543
483
  write_string_allocation_sites
544
484
  end
545
485
 
546
- # Loads the analysis-path dependencies lazily (so non-check commands
547
- # stay light) and starts heap-allocation tracing if requested, before
548
- # any analysis object is allocated.
486
+ # Loads the analysis-path dependencies lazily (so non-check commands stay light) and starts heap-allocation
487
+ # tracing if requested, before any analysis object is allocated.
549
488
  def load_check_dependencies
550
489
  require_relative "../analysis/runner"
551
490
  require_relative "../analysis/buffer_binding"
@@ -554,9 +493,8 @@ module Rigor
554
493
  start_heap_trace_if_requested
555
494
  end
556
495
 
557
- # Starts allocation tracing (RIGOR_HEAP_TRACE) as early as possible so
558
- # the heap profile can attribute retained Strings to their allocation
559
- # `file:line`. Very high overhead — run on a small file subset only.
496
+ # Starts allocation tracing (RIGOR_HEAP_TRACE) as early as possible so the heap profile can attribute retained
497
+ # Strings to their allocation `file:line`. Very high overhead run on a small file subset only.
560
498
  def start_heap_trace_if_requested
561
499
  return if ENV["RIGOR_HEAP_TRACE"].to_s.empty?
562
500
 
@@ -564,11 +502,9 @@ module Rigor
564
502
  ObjectSpace.trace_object_allocations_start
565
503
  end
566
504
 
567
- # When RIGOR_HEAP_TRACE is on, groups the live String objects by their
568
- # allocation site (`sourcefile:sourceline`) and prints the top sites by
569
- # count pinpointing which engine code retains the millions of strings
570
- # that dominate the large-app heap (ADR-41 Slice 2b). Strings allocated
571
- # before tracing started report `(pre-trace)`.
505
+ # When RIGOR_HEAP_TRACE is on, groups the live String objects by their allocation site (`sourcefile:sourceline`)
506
+ # and prints the top sites by count — pinpointing which engine code retains the millions of strings that dominate
507
+ # the large-app heap (ADR-41 Slice 2b). Strings allocated before tracing started report `(pre-trace)`.
572
508
  def write_string_allocation_sites
573
509
  return if ENV["RIGOR_HEAP_TRACE"].to_s.empty?
574
510
 
@@ -585,9 +521,8 @@ module Rigor
585
521
  end
586
522
  end
587
523
 
588
- # Walks the whole live heap (after a forced GC) and tallies
589
- # `{class_name => [count, memsize]}` plus the grand total. Returns
590
- # `[by_class, total]`. Slow — a dev probe only.
524
+ # Walks the whole live heap (after a forced GC) and tallies `{class_name => [count, memsize]}` plus the grand
525
+ # total. Returns `[by_class, total]`. Slow a dev probe only.
591
526
  def tally_live_heap
592
527
  require "objspace"
593
528
  GC.start
@@ -675,9 +610,8 @@ module Rigor
675
610
  write_text_result(result)
676
611
  write_coverage_summary(coverage) if coverage
677
612
  when ->(fmt) { CLI::DiagnosticFormats.supports?(fmt) }
678
- # ADR-51 — CI-native renderings (SARIF / GitHub Actions commands /
679
- # GitLab Code Quality). The `github` form is empty when there are no
680
- # diagnostics; the JSON forms always carry a document.
613
+ # ADR-51 — CI-native renderings (SARIF / GitHub Actions commands / GitLab Code Quality). The `github` form is
614
+ # empty when there are no diagnostics; the JSON forms always carry a document.
681
615
  output = CLI::DiagnosticFormats.render(result, format)
682
616
  @out.puts(output) unless output.empty?
683
617
  else
@@ -685,11 +619,9 @@ module Rigor
685
619
  end
686
620
  end
687
621
 
688
- # Runs the type-precision scan (`--coverage`) over the same file set
689
- # the check analyzed and returns a `CoverageReport`, or nil when the
690
- # flag is off. It is a second pass the same scan `rigor coverage`
691
- # runs, reused via {CoverageScan} — so it is opt-in to keep the
692
- # default check path's cost unchanged.
622
+ # Runs the type-precision scan (`--coverage`) over the same file set the check analyzed and returns a
623
+ # `CoverageReport`, or nil when the flag is off. It is a second pass — the same scan `rigor coverage` runs, reused
624
+ # via {CoverageScan} so it is opt-in to keep the default check path's cost unchanged.
693
625
  def compute_coverage(runner, configuration, options)
694
626
  return nil unless options.fetch(:coverage)
695
627
 
@@ -697,11 +629,9 @@ module Rigor
697
629
  CoverageScan.precision_report(files: files, configuration: configuration)
698
630
  end
699
631
 
700
- # The `coverage` block embedded in `--format json`. Mirrors the
701
- # `summary` of `rigor coverage --format json` (the same vocabulary
702
- # `precise_ratio`, not a separate `typed_ratio`) plus `scan_files`,
703
- # so a consumer reads one stream to learn both what fired and how
704
- # much of the analyzed surface Rigor could type.
632
+ # The `coverage` block embedded in `--format json`. Mirrors the `summary` of `rigor coverage --format json` (the
633
+ # same vocabulary `precise_ratio`, not a separate `typed_ratio`) plus `scan_files`, so a consumer reads one
634
+ # stream to learn both what fired and how much of the analyzed surface Rigor could type.
705
635
  def coverage_payload(report)
706
636
  {
707
637
  "scan_files" => report.files.size - report.parse_errors.size,
@@ -722,13 +652,10 @@ module Rigor
722
652
  "Run `rigor coverage` for the full per-file / per-tier breakdown.")
723
653
  end
724
654
 
725
- # Adds the per-rule `evidence_tier` and `documentation_url` fields
726
- # to each diagnostic in the `--format json` payload. Both are pure
727
- # functions of the rule id (the rule catalogue, ADR-61 / the
728
- # 2026-06-15 feedback §4 + §5.1), so they enrich the presentation
729
- # layer here rather than threading through every diagnostic
730
- # construction site. Only built-in rules carry catalogue metadata;
731
- # plugin / `rbs_extended` / parse-error diagnostics are left
655
+ # Adds the per-rule `evidence_tier` and `documentation_url` fields to each diagnostic in the `--format json`
656
+ # payload. Both are pure functions of the rule id (the rule catalogue, ADR-61 / the 2026-06-15 feedback §4 +
657
+ # §5.1), so they enrich the presentation layer here rather than threading through every diagnostic construction
658
+ # site. Only built-in rules carry catalogue metadata; plugin / `rbs_extended` / parse-error diagnostics are left
732
659
  # untouched (they host their own documentation and confidence).
733
660
  def enrich_json(payload)
734
661
  Array(payload["diagnostics"]).each do |diag|
@@ -745,14 +672,11 @@ module Rigor
745
672
  payload
746
673
  end
747
674
 
748
- # ADR-51 WD7 — CI auto-detection. Only augments the default human
749
- # (`text`) output: an explicit `--format` means the caller is in control
750
- # and is left untouched. For a first-class stdout-native CI (GitHub
751
- # Actions / TeamCity) the platform's annotations are emitted on top of
752
- # the text output (so the human log AND the inline surface both appear,
753
- # like PHPStan's CI-detecting table formatter). For GitLab (native but
754
- # artifact-based) and the reviewdog-routed CIs, a one-line hint goes to
755
- # stderr — but only when there are diagnostics, so a clean run stays
675
+ # ADR-51 WD7 — CI auto-detection. Only augments the default human (`text`) output: an explicit `--format` means
676
+ # the caller is in control and is left untouched. For a first-class stdout-native CI (GitHub Actions / TeamCity)
677
+ # the platform's annotations are emitted on top of the text output (so the human log AND the inline surface both
678
+ # appear, like PHPStan's CI-detecting table formatter). For GitLab (native but artifact-based) and the
679
+ # reviewdog-routed CIs, a one-line hint goes to stderr but only when there are diagnostics, so a clean run stays
756
680
  # quiet.
757
681
  def emit_ci_detected_output(result, options)
758
682
  return unless options.fetch(:ci_detect)
@@ -780,10 +704,8 @@ module Rigor
780
704
  end
781
705
  end
782
706
 
783
- # Text output adds a one-line summary so users see the
784
- # diagnostic-count immediately. The summary distinguishes
785
- # the success and failure cases and reports the affected
786
- # file count for failures.
707
+ # Text output adds a one-line summary so users see the diagnostic-count immediately. The summary distinguishes the
708
+ # success and failure cases and reports the affected file count for failures.
787
709
  def write_text_result(result)
788
710
  result.diagnostics.each { |diagnostic| @out.puts(diagnostic) }
789
711