rigortype 0.2.6 → 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 (432) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +4 -3
  3. data/docs/manual/02-cli-reference.md +2 -1
  4. data/docs/manual/04-diagnostics.md +7 -4
  5. data/docs/manual/06-baseline.md +5 -2
  6. data/docs/manual/08-skills.md +21 -0
  7. data/docs/manual/15-type-protection-coverage.md +6 -4
  8. data/lib/rigor/analysis/baseline.rb +56 -96
  9. data/lib/rigor/analysis/buffer_binding.rb +13 -20
  10. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +29 -48
  11. data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +35 -54
  12. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +23 -37
  13. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +16 -23
  14. data/lib/rigor/analysis/check_rules/rule_walk.rb +48 -71
  15. data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +19 -28
  16. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +48 -67
  17. data/lib/rigor/analysis/check_rules.rb +174 -274
  18. data/lib/rigor/analysis/dependency_recorder.rb +26 -38
  19. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +13 -25
  20. data/lib/rigor/analysis/dependency_source_inference/builder.rb +17 -30
  21. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +15 -26
  22. data/lib/rigor/analysis/dependency_source_inference/index.rb +45 -76
  23. data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +23 -40
  24. data/lib/rigor/analysis/dependency_source_inference/walker.rb +50 -83
  25. data/lib/rigor/analysis/dependency_source_inference.rb +6 -10
  26. data/lib/rigor/analysis/diagnostic.rb +48 -75
  27. data/lib/rigor/analysis/erb_template_detector.rb +13 -18
  28. data/lib/rigor/analysis/fact_store.rb +12 -22
  29. data/lib/rigor/analysis/incremental.rb +47 -63
  30. data/lib/rigor/analysis/incremental_session.rb +74 -100
  31. data/lib/rigor/analysis/project_scan.rb +12 -23
  32. data/lib/rigor/analysis/result.rb +3 -5
  33. data/lib/rigor/analysis/rule_catalog.rb +67 -95
  34. data/lib/rigor/analysis/run_stats.rb +38 -61
  35. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +101 -160
  36. data/lib/rigor/analysis/runner/pool_coordinator.rb +121 -188
  37. data/lib/rigor/analysis/runner/project_pre_passes.rb +62 -96
  38. data/lib/rigor/analysis/runner/run_snapshots.rb +12 -18
  39. data/lib/rigor/analysis/runner.rb +220 -333
  40. data/lib/rigor/analysis/self_call_resolution_recorder.rb +30 -43
  41. data/lib/rigor/analysis/worker_session.rb +64 -102
  42. data/lib/rigor/ast/type_node.rb +6 -8
  43. data/lib/rigor/ast.rb +6 -10
  44. data/lib/rigor/bleeding_edge.rb +27 -35
  45. data/lib/rigor/builtins/hkt_builtins.rb +7 -14
  46. data/lib/rigor/builtins/imported_refinements.rb +106 -149
  47. data/lib/rigor/builtins/predefined_constant_refinements.rb +51 -65
  48. data/lib/rigor/builtins/regex_refinement.rb +32 -43
  49. data/lib/rigor/builtins/static_return_refinements.rb +57 -81
  50. data/lib/rigor/cache/descriptor.rb +58 -94
  51. data/lib/rigor/cache/incremental_snapshot.rb +32 -48
  52. data/lib/rigor/cache/rbs_cache_producer.rb +10 -15
  53. data/lib/rigor/cache/rbs_class_ancestor_table.rb +10 -16
  54. data/lib/rigor/cache/rbs_class_type_param_names.rb +10 -16
  55. data/lib/rigor/cache/rbs_constant_table.rb +8 -13
  56. data/lib/rigor/cache/rbs_descriptor.rb +8 -13
  57. data/lib/rigor/cache/rbs_environment.rb +11 -19
  58. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +11 -19
  59. data/lib/rigor/cache/rbs_known_class_names.rb +8 -12
  60. data/lib/rigor/cache/store.rb +96 -151
  61. data/lib/rigor/cli/annotate_command.rb +51 -88
  62. data/lib/rigor/cli/baseline_command.rb +10 -17
  63. data/lib/rigor/cli/check_command.rb +121 -199
  64. data/lib/rigor/cli/check_runner_factory.rb +8 -14
  65. data/lib/rigor/cli/ci_detector.rb +9 -14
  66. data/lib/rigor/cli/command.rb +8 -14
  67. data/lib/rigor/cli/coverage_command.rb +45 -20
  68. data/lib/rigor/cli/coverage_mutation.rb +14 -19
  69. data/lib/rigor/cli/coverage_report.rb +0 -2
  70. data/lib/rigor/cli/coverage_scan.rb +7 -11
  71. data/lib/rigor/cli/diagnostic_formats.rb +43 -62
  72. data/lib/rigor/cli/diff_command.rb +10 -20
  73. data/lib/rigor/cli/docs_command.rb +26 -40
  74. data/lib/rigor/cli/doctor_command.rb +8 -9
  75. data/lib/rigor/cli/explain_command.rb +4 -7
  76. data/lib/rigor/cli/fused_protection_renderer.rb +4 -5
  77. data/lib/rigor/cli/fused_protection_report.rb +6 -8
  78. data/lib/rigor/cli/lsp_command.rb +11 -19
  79. data/lib/rigor/cli/mcp_command.rb +4 -6
  80. data/lib/rigor/cli/mutation_protection_renderer.rb +3 -4
  81. data/lib/rigor/cli/mutation_protection_report.rb +6 -9
  82. data/lib/rigor/cli/options.rb +11 -19
  83. data/lib/rigor/cli/plugin_command.rb +18 -30
  84. data/lib/rigor/cli/plugins_command.rb +29 -51
  85. data/lib/rigor/cli/plugins_renderer.rb +12 -20
  86. data/lib/rigor/cli/prism_colorizer.rb +13 -19
  87. data/lib/rigor/cli/protection_renderer.rb +5 -7
  88. data/lib/rigor/cli/protection_report.rb +27 -17
  89. data/lib/rigor/cli/renderable.rb +4 -6
  90. data/lib/rigor/cli/show_bleedingedge_command.rb +5 -8
  91. data/lib/rigor/cli/sig_gen_command.rb +14 -26
  92. data/lib/rigor/cli/skill_command.rb +70 -36
  93. data/lib/rigor/cli/skill_describe.rb +40 -64
  94. data/lib/rigor/cli/trace_command.rb +9 -16
  95. data/lib/rigor/cli/trace_renderer.rb +35 -51
  96. data/lib/rigor/cli/triage_command.rb +6 -10
  97. data/lib/rigor/cli/triage_renderer.rb +1 -2
  98. data/lib/rigor/cli/type_of_command.rb +14 -23
  99. data/lib/rigor/cli/type_of_renderer.rb +3 -5
  100. data/lib/rigor/cli/type_scan_command.rb +6 -10
  101. data/lib/rigor/cli/type_scan_renderer.rb +3 -5
  102. data/lib/rigor/cli/type_scan_report.rb +2 -3
  103. data/lib/rigor/cli/upgrade_command.rb +2 -3
  104. data/lib/rigor/cli.rb +16 -28
  105. data/lib/rigor/config_audit.rb +35 -44
  106. data/lib/rigor/configuration/dependencies.rb +41 -70
  107. data/lib/rigor/configuration/severity_profile.rb +28 -42
  108. data/lib/rigor/configuration.rb +150 -241
  109. data/lib/rigor/environment/bundle_sig_discovery.rb +74 -118
  110. data/lib/rigor/environment/class_registry.rb +19 -26
  111. data/lib/rigor/environment/constant_type_cache_holder.rb +14 -21
  112. data/lib/rigor/environment/hkt_registry_holder.rb +6 -12
  113. data/lib/rigor/environment/lockfile_resolver.rb +31 -47
  114. data/lib/rigor/environment/rbs_collection_discovery.rb +39 -63
  115. data/lib/rigor/environment/rbs_coverage_report.rb +24 -42
  116. data/lib/rigor/environment/rbs_loader.rb +433 -503
  117. data/lib/rigor/environment/reflection.rb +28 -53
  118. data/lib/rigor/environment/reporters.rb +13 -24
  119. data/lib/rigor/environment.rb +176 -291
  120. data/lib/rigor/flow_contribution/conflict.rb +16 -27
  121. data/lib/rigor/flow_contribution/element.rb +7 -12
  122. data/lib/rigor/flow_contribution/fact.rb +33 -57
  123. data/lib/rigor/flow_contribution/merge_result.rb +6 -9
  124. data/lib/rigor/flow_contribution/merger.rb +32 -47
  125. data/lib/rigor/flow_contribution.rb +37 -55
  126. data/lib/rigor/inference/acceptance.rb +133 -219
  127. data/lib/rigor/inference/block_parameter_binder.rb +52 -82
  128. data/lib/rigor/inference/body_fixpoint.rb +22 -30
  129. data/lib/rigor/inference/budget_trace.rb +43 -63
  130. data/lib/rigor/inference/builtins/array_catalog.rb +4 -6
  131. data/lib/rigor/inference/builtins/comparable_catalog.rb +4 -6
  132. data/lib/rigor/inference/builtins/complex_catalog.rb +13 -19
  133. data/lib/rigor/inference/builtins/date_catalog.rb +38 -67
  134. data/lib/rigor/inference/builtins/encoding_catalog.rb +22 -36
  135. data/lib/rigor/inference/builtins/enumerable_catalog.rb +4 -6
  136. data/lib/rigor/inference/builtins/exception_catalog.rb +38 -57
  137. data/lib/rigor/inference/builtins/hash_catalog.rb +9 -14
  138. data/lib/rigor/inference/builtins/method_catalog.rb +29 -48
  139. data/lib/rigor/inference/builtins/numeric_catalog.rb +13 -18
  140. data/lib/rigor/inference/builtins/pathname_catalog.rb +8 -13
  141. data/lib/rigor/inference/builtins/proc_catalog.rb +38 -62
  142. data/lib/rigor/inference/builtins/random_catalog.rb +21 -32
  143. data/lib/rigor/inference/builtins/range_catalog.rb +12 -21
  144. data/lib/rigor/inference/builtins/rational_catalog.rb +9 -15
  145. data/lib/rigor/inference/builtins/re_catalog.rb +29 -48
  146. data/lib/rigor/inference/builtins/set_catalog.rb +18 -27
  147. data/lib/rigor/inference/builtins/string_catalog.rb +12 -19
  148. data/lib/rigor/inference/builtins/struct_catalog.rb +18 -30
  149. data/lib/rigor/inference/builtins/time_catalog.rb +28 -44
  150. data/lib/rigor/inference/closure_escape_analyzer.rb +44 -67
  151. data/lib/rigor/inference/coverage_scanner.rb +13 -18
  152. data/lib/rigor/inference/def_return_typer.rb +8 -14
  153. data/lib/rigor/inference/dynamic_origin.rb +23 -20
  154. data/lib/rigor/inference/expression_typer.rb +774 -1152
  155. data/lib/rigor/inference/fallback.rb +8 -12
  156. data/lib/rigor/inference/fallback_tracer.rb +4 -10
  157. data/lib/rigor/inference/flow_tracer.rb +25 -36
  158. data/lib/rigor/inference/hkt_body.rb +45 -68
  159. data/lib/rigor/inference/hkt_body_parser.rb +33 -52
  160. data/lib/rigor/inference/hkt_reducer.rb +38 -59
  161. data/lib/rigor/inference/hkt_registry.rb +50 -76
  162. data/lib/rigor/inference/indexed_narrowing.rb +55 -81
  163. data/lib/rigor/inference/macro_block_self_type.rb +21 -34
  164. data/lib/rigor/inference/method_dispatcher/array_to_h_folding.rb +11 -18
  165. data/lib/rigor/inference/method_dispatcher/block_folding.rb +49 -83
  166. data/lib/rigor/inference/method_dispatcher/call_context.rb +16 -23
  167. data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +13 -22
  168. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +307 -454
  169. data/lib/rigor/inference/method_dispatcher/data_folding.rb +24 -34
  170. data/lib/rigor/inference/method_dispatcher/file_folding.rb +22 -37
  171. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +52 -81
  172. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +51 -78
  173. data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +60 -97
  174. data/lib/rigor/inference/method_dispatcher/math_folding.rb +23 -34
  175. data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +18 -26
  176. data/lib/rigor/inference/method_dispatcher/method_folding.rb +37 -71
  177. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +114 -182
  178. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +122 -193
  179. data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +15 -24
  180. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +64 -93
  181. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +31 -45
  182. data/lib/rigor/inference/method_dispatcher/set_folding.rb +6 -9
  183. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +356 -511
  184. data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +22 -32
  185. data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +15 -21
  186. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +66 -82
  187. data/lib/rigor/inference/method_dispatcher/time_folding.rb +10 -15
  188. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +9 -13
  189. data/lib/rigor/inference/method_dispatcher.rb +378 -524
  190. data/lib/rigor/inference/method_parameter_binder.rb +81 -114
  191. data/lib/rigor/inference/multi_target_binder.rb +51 -68
  192. data/lib/rigor/inference/mutation_widening.rb +115 -158
  193. data/lib/rigor/inference/narrowing.rb +509 -727
  194. data/lib/rigor/inference/origin_lookup.rb +38 -0
  195. data/lib/rigor/inference/parameter_inference_collector.rb +100 -100
  196. data/lib/rigor/inference/precision_scanner.rb +26 -35
  197. data/lib/rigor/inference/project_patched_methods.rb +20 -32
  198. data/lib/rigor/inference/project_patched_scanner.rb +23 -37
  199. data/lib/rigor/inference/protection_scanner.rb +16 -18
  200. data/lib/rigor/inference/rbs_type_translator.rb +49 -66
  201. data/lib/rigor/inference/scope_indexer.rb +473 -817
  202. data/lib/rigor/inference/statement_evaluator.rb +706 -1115
  203. data/lib/rigor/inference/struct_fold_safety.rb +34 -48
  204. data/lib/rigor/inference/synthetic_method.rb +9 -16
  205. data/lib/rigor/inference/synthetic_method_index.rb +20 -35
  206. data/lib/rigor/inference/synthetic_method_scanner.rb +63 -103
  207. data/lib/rigor/language_server/buffer_resolution.rb +6 -9
  208. data/lib/rigor/language_server/buffer_table.rb +11 -18
  209. data/lib/rigor/language_server/completion_provider.rb +69 -116
  210. data/lib/rigor/language_server/debouncer.rb +16 -25
  211. data/lib/rigor/language_server/diagnostic_publisher.rb +26 -40
  212. data/lib/rigor/language_server/document_symbol_provider.rb +14 -19
  213. data/lib/rigor/language_server/folding_range_provider.rb +11 -19
  214. data/lib/rigor/language_server/hover_provider.rb +8 -11
  215. data/lib/rigor/language_server/hover_renderer.rb +56 -93
  216. data/lib/rigor/language_server/loop.rb +14 -19
  217. data/lib/rigor/language_server/project_context.rb +46 -74
  218. data/lib/rigor/language_server/selection_range_provider.rb +9 -12
  219. data/lib/rigor/language_server/server.rb +55 -83
  220. data/lib/rigor/language_server/signature_help_provider.rb +29 -46
  221. data/lib/rigor/language_server/synchronized_writer.rb +4 -7
  222. data/lib/rigor/language_server/uri.rb +8 -13
  223. data/lib/rigor/language_server.rb +4 -6
  224. data/lib/rigor/mcp/loop.rb +2 -3
  225. data/lib/rigor/mcp/server.rb +4 -7
  226. data/lib/rigor/mcp.rb +3 -6
  227. data/lib/rigor/plugin/access_denied_error.rb +5 -8
  228. data/lib/rigor/plugin/additional_initializer.rb +21 -31
  229. data/lib/rigor/plugin/base.rb +335 -518
  230. data/lib/rigor/plugin/blueprint.rb +14 -23
  231. data/lib/rigor/plugin/box.rb +18 -29
  232. data/lib/rigor/plugin/fact_store.rb +16 -26
  233. data/lib/rigor/plugin/inflector.rb +37 -53
  234. data/lib/rigor/plugin/io_boundary.rb +33 -56
  235. data/lib/rigor/plugin/isolation.rb +42 -55
  236. data/lib/rigor/plugin/load_error.rb +10 -15
  237. data/lib/rigor/plugin/loader.rb +30 -49
  238. data/lib/rigor/plugin/macro/block_as_method.rb +20 -32
  239. data/lib/rigor/plugin/macro/heredoc_template.rb +35 -58
  240. data/lib/rigor/plugin/macro/nested_class_template.rb +22 -36
  241. data/lib/rigor/plugin/macro/trait_registry.rb +34 -51
  242. data/lib/rigor/plugin/macro.rb +10 -15
  243. data/lib/rigor/plugin/manifest.rb +85 -144
  244. data/lib/rigor/plugin/node_context.rb +14 -22
  245. data/lib/rigor/plugin/node_rule_walk.rb +49 -74
  246. data/lib/rigor/plugin/protocol_contract.rb +25 -39
  247. data/lib/rigor/plugin/registry.rb +132 -205
  248. data/lib/rigor/plugin/services.rb +21 -33
  249. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -19
  250. data/lib/rigor/plugin/trust_policy.rb +24 -38
  251. data/lib/rigor/plugin/type_node_resolver.rb +15 -24
  252. data/lib/rigor/protection/diagnostic_oracle.rb +11 -13
  253. data/lib/rigor/protection/mutation_scanner.rb +27 -35
  254. data/lib/rigor/protection/mutator.rb +50 -70
  255. data/lib/rigor/protection/test_suite_oracle.rb +20 -27
  256. data/lib/rigor/rbs_extended/conformance_checker.rb +48 -68
  257. data/lib/rigor/rbs_extended/hkt_directives.rb +28 -54
  258. data/lib/rigor/rbs_extended/reporter.rb +24 -40
  259. data/lib/rigor/rbs_extended.rb +107 -197
  260. data/lib/rigor/reflection.rb +68 -86
  261. data/lib/rigor/scope/discovery_index.rb +14 -19
  262. data/lib/rigor/scope.rb +255 -310
  263. data/lib/rigor/sig_gen/classification.rb +6 -10
  264. data/lib/rigor/sig_gen/generator.rb +208 -309
  265. data/lib/rigor/sig_gen/layout_index.rb +12 -20
  266. data/lib/rigor/sig_gen/method_candidate.rb +15 -15
  267. data/lib/rigor/sig_gen/observation_collector.rb +38 -70
  268. data/lib/rigor/sig_gen/observed_call.rb +13 -23
  269. data/lib/rigor/sig_gen/path_mapper.rb +17 -29
  270. data/lib/rigor/sig_gen/renderer.rb +7 -13
  271. data/lib/rigor/sig_gen/type_elaborator.rb +15 -28
  272. data/lib/rigor/sig_gen/write_result.rb +8 -16
  273. data/lib/rigor/sig_gen/writer.rb +141 -173
  274. data/lib/rigor/sig_gen.rb +3 -6
  275. data/lib/rigor/signature_path_audit.rb +24 -30
  276. data/lib/rigor/source/constant_path.rb +10 -14
  277. data/lib/rigor/source/literals.rb +31 -45
  278. data/lib/rigor/source/node_locator.rb +9 -11
  279. data/lib/rigor/source/node_walker.rb +9 -13
  280. data/lib/rigor/source.rb +3 -4
  281. data/lib/rigor/testing.rb +16 -20
  282. data/lib/rigor/triage/catalogue.rb +38 -62
  283. data/lib/rigor/triage.rb +31 -52
  284. data/lib/rigor/trinary.rb +9 -13
  285. data/lib/rigor/type/acceptance_router.rb +4 -6
  286. data/lib/rigor/type/accepts_result.rb +10 -14
  287. data/lib/rigor/type/app.rb +19 -27
  288. data/lib/rigor/type/bot.rb +4 -6
  289. data/lib/rigor/type/bound_method.rb +10 -15
  290. data/lib/rigor/type/combinator.rb +165 -257
  291. data/lib/rigor/type/constant.rb +23 -34
  292. data/lib/rigor/type/data_class.rb +10 -15
  293. data/lib/rigor/type/data_instance.rb +14 -20
  294. data/lib/rigor/type/difference.rb +21 -32
  295. data/lib/rigor/type/dynamic.rb +3 -5
  296. data/lib/rigor/type/hash_shape.rb +32 -18
  297. data/lib/rigor/type/integer_range.rb +11 -16
  298. data/lib/rigor/type/intersection.rb +27 -42
  299. data/lib/rigor/type/nominal.rb +10 -15
  300. data/lib/rigor/type/plain_lattice.rb +9 -13
  301. data/lib/rigor/type/refined.rb +67 -114
  302. data/lib/rigor/type/singleton.rb +4 -6
  303. data/lib/rigor/type/struct_class.rb +11 -16
  304. data/lib/rigor/type/struct_instance.rb +15 -21
  305. data/lib/rigor/type/tuple.rb +14 -19
  306. data/lib/rigor/type/union.rb +30 -42
  307. data/lib/rigor/type_node/generic.rb +14 -26
  308. data/lib/rigor/type_node/identifier.rb +12 -19
  309. data/lib/rigor/type_node.rb +3 -12
  310. data/lib/rigor/value_semantics.rb +16 -21
  311. data/lib/rigor/version.rb +1 -1
  312. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +18 -30
  313. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +16 -29
  314. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +12 -23
  315. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +22 -39
  316. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +26 -46
  317. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +51 -87
  318. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +10 -17
  319. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +26 -39
  320. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +118 -176
  321. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +30 -51
  322. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +57 -92
  323. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +107 -83
  324. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +13 -22
  325. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +15 -25
  326. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +8 -13
  327. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +16 -24
  328. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +30 -47
  329. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +79 -129
  330. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +36 -57
  331. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +27 -41
  332. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +16 -24
  333. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +148 -236
  334. data/plugins/rigor-activerecord/lib/rigor-activerecord.rb +3 -5
  335. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +8 -14
  336. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +9 -15
  337. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +4 -7
  338. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +23 -41
  339. data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +3 -5
  340. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +6 -10
  341. data/plugins/rigor-activesupport-core-ext/lib/rigor-activesupport-core-ext.rb +8 -11
  342. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +30 -45
  343. data/plugins/rigor-devise/lib/rigor-devise.rb +3 -5
  344. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +43 -69
  345. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +26 -35
  346. data/plugins/rigor-dry-schema/lib/rigor-dry-schema.rb +3 -5
  347. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +30 -47
  348. data/plugins/rigor-dry-struct/lib/rigor-dry-struct.rb +3 -5
  349. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +67 -113
  350. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +29 -46
  351. data/plugins/rigor-dry-types/lib/rigor-dry-types.rb +3 -5
  352. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -20
  353. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +14 -23
  354. data/plugins/rigor-dry-validation/lib/rigor-dry-validation.rb +2 -3
  355. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +22 -37
  356. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +28 -46
  357. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +13 -17
  358. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +30 -47
  359. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +52 -80
  360. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +21 -31
  361. data/plugins/rigor-graphql/lib/rigor-graphql.rb +3 -5
  362. data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +20 -36
  363. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +23 -30
  364. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +45 -75
  365. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +22 -36
  366. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +22 -41
  367. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +17 -32
  368. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +6 -12
  369. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +4 -8
  370. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +17 -32
  371. data/plugins/rigor-rails/lib/rigor-rails.rb +11 -18
  372. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +58 -92
  373. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +20 -34
  374. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +20 -31
  375. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +49 -82
  376. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +55 -92
  377. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +51 -90
  378. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +17 -30
  379. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +38 -70
  380. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +42 -74
  381. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +280 -497
  382. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +54 -89
  383. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +32 -54
  384. data/plugins/rigor-rbs-inline/lib/rigor-rbs-inline.rb +8 -13
  385. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +9 -16
  386. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +24 -39
  387. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +13 -22
  388. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +34 -56
  389. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/scope_walker.rb +22 -42
  390. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +43 -75
  391. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +27 -39
  392. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/http_status_codes.rb +14 -19
  393. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +20 -34
  394. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +28 -47
  395. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +21 -37
  396. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +13 -22
  397. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +18 -31
  398. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +6 -10
  399. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +14 -29
  400. data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +23 -41
  401. data/plugins/rigor-sinatra/lib/rigor-sinatra.rb +3 -5
  402. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +23 -38
  403. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +57 -96
  404. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +22 -35
  405. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +41 -69
  406. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/method_signature.rb +6 -10
  407. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +24 -41
  408. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +29 -50
  409. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +55 -88
  410. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +182 -328
  411. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +21 -29
  412. data/plugins/rigor-statesman/lib/rigor-statesman.rb +3 -5
  413. data/plugins/rigor-typescript-utility-types/lib/rigor/plugin/typescript_utility_types.rb +23 -37
  414. data/plugins/rigor-typescript-utility-types/lib/rigor-typescript-utility-types.rb +4 -6
  415. data/sig/rigor/scope.rbs +6 -0
  416. data/skills/rigor-ask/SKILL.md +21 -1
  417. data/skills/rigor-baseline-reduce/SKILL.md +16 -0
  418. data/skills/rigor-ci-setup/SKILL.md +96 -249
  419. data/skills/rigor-doctor/SKILL.md +39 -49
  420. data/skills/rigor-doctor/references/01-checks.md +52 -0
  421. data/skills/rigor-editor-setup/SKILL.md +14 -0
  422. data/skills/rigor-mcp-setup/SKILL.md +14 -0
  423. data/skills/rigor-monkeypatch-resolve/SKILL.md +15 -0
  424. data/skills/rigor-plugin-author/SKILL.md +16 -0
  425. data/skills/rigor-plugin-review/SKILL.md +174 -0
  426. data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +214 -0
  427. data/skills/rigor-plugin-tune/SKILL.md +21 -2
  428. data/skills/rigor-project-init/SKILL.md +16 -0
  429. data/skills/rigor-protection-uplift/SKILL.md +15 -0
  430. data/skills/rigor-rbs-setup/SKILL.md +15 -0
  431. data/skills/rigor-upgrade/SKILL.md +16 -0
  432. metadata +8 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 97d286eb15708fc212260925d407b48926a331798f0d2f210321b1d3c66cda55
4
- data.tar.gz: 9fadaf2c31394c142dd15b335dbf740774ceabff8ce85b8a79fcd7faa26ea4fa
3
+ metadata.gz: d38773edf003d6de8ea6ec3de2f19aac4c2d74e404c56f39d1ed9595f388a648
4
+ data.tar.gz: beb2b7aea30bf74f4cc8d22e66d441d63ecb906689d6a22c6ca647a7a087a719
5
5
  SHA512:
6
- metadata.gz: 1b800eb64d10fdef9039ed429f5740f672cdf0811de86c1cac1434b6bb5a0c820df68d8d8b01aa03ed0cab518d18a6e51af12d608cf6ad50556e4b653a4d3ed6
7
- data.tar.gz: 5bee1c072412f7eac14d0c96314f8efe2473cd46efe9763e238bd3c345605deb29055db3601c52909c430d419d8821fc7befb51fe0f5211b8fbb94e379a5d635
6
+ metadata.gz: 1c33b18e310c956f9cc9bbd4806228a095bcf95a137efba50affd49ffca17decac9107006e4a7fd350a9783f289e7d9e336ebc99755e7d69ce321d60b21c731b
7
+ data.tar.gz: c9f7286bc00f3419083ced2e35678062e8508dfe03b6064fe6c9f4cf4ad752479ca8801cce059cb3fc6bc0ee0eaaa216824a9d43cc93332feb7e267ab988253c
data/README.md CHANGED
@@ -231,9 +231,10 @@ rigor docs --list # list every bundled page
231
231
 
232
232
  ## Status
233
233
 
234
- Current release: **`v0.2.0`** (2026-06-17) — the first
235
- publicly-announced (general / evaluation) release. It publishes an
236
- enumerated [compatibility surface](docs/compatibility.md) as a
234
+ Current release: **`v0.2.8`** (2026-07-06) — the latest cut on the
235
+ `0.2.x` evaluation line opened by `v0.2.0`, the first
236
+ publicly-announced (general / evaluation) release. The line publishes
237
+ an enumerated [compatibility surface](docs/compatibility.md) as a
237
238
  minor-non-break trial, rehearsing the contract that hard-freezes at
238
239
  `v1.0.0`. Rigor analyses real Ruby today: it has been hardened against
239
240
  Mastodon, Redmine, and GitLab FOSS, and the deliberately conservative
@@ -452,13 +452,14 @@ The positional slot is a skill *name*; alternative outputs are flags,
452
452
  so a skill can never be shadowed by a verb.
453
453
 
454
454
  ```sh
455
- rigor skill [<name>] [--path <name>] [--list] [--describe]
455
+ rigor skill [<name>] [--full <name>] [--path <name>] [--list] [--describe]
456
456
  ```
457
457
 
458
458
  | Form | Purpose |
459
459
  | --- | --- |
460
460
  | (none) / `--list` | Table of every bundled skill (name + absolute path). |
461
461
  | `<name>` | Print the `SKILL.md` body to stdout, with a header pointing at the skill's `references/` directory. |
462
+ | `--full <name>` | Print the `SKILL.md` body **followed by every `references/*.md` inline** — the complete, version-current procedure in one call. This is what a skill's "First: load the version-current copy" directive points at, so a copy vendored into a project (e.g. via `npx skills add`) re-fetches its current steps from the installed gem instead of following a frozen copy. |
462
463
  | `--path <name>` | Print the single-line absolute `SKILL.md` path, suitable as input to a file-reading tool. |
463
464
  | `--describe` | Probe the project's state (config / baseline / `sig/` / CI — presence only, never runs `rigor check`) and recommend the next skill to run. Also spelled `describe`; surfaced top-level as [`rigor describe`](#rigor-describe) below. |
464
465
 
@@ -20,16 +20,19 @@ Every rule has a two-segment `family.rule` identifier:
20
20
  | `dump` | `dump_type` notices. |
21
21
 
22
22
  `rigor explain <rule>` prints the full catalogue entry for any
23
- ID; `rigor explain` with no argument lists them all.
23
+ built-in rule ID; `rigor explain` with no argument lists them all.
24
24
 
25
25
  ### Catalogue
26
26
 
27
- Each rule has a stable per-rule anchor on this page
27
+ Each built-in rule has a stable per-rule anchor on this page
28
28
  (`#rule-<family>-<name>`, dots written as dashes) — the
29
29
  `documentation_url` field in `--format json` and `rigor explain`'s
30
30
  `Documentation:` line both point here. The `Evidence` column is
31
31
  Rigor's confidence that a firing is a true positive (see
32
- [Evidence tier](#evidence-tier) below).
32
+ [Evidence tier](#evidence-tier) below). The one exception is
33
+ `rbs_extended.unsatisfied-conformance`, an `rbs_extended`-family rule
34
+ rather than a built-in: `rigor explain` does not resolve it and it
35
+ carries no `documentation_url`.
33
36
 
34
37
  | Rule | Fires when | Evidence |
35
38
  | --- | --- | --- |
@@ -86,7 +89,7 @@ re-stamps it for the run. Three profiles, set with the
86
89
  | --- | --- |
87
90
  | `lenient` | Only proven diagnostics are errors; uncertain ones drop to `warning` / `info`. For incremental adoption on legacy code. |
88
91
  | `balanced` *(default)* | Most rules `error`; `dump.type` `info`; uncertain rules `warning`. |
89
- | `strict` | Every rule is an `error`. CI-friendly. |
92
+ | `strict` | Nearly every rule is an `error` — the exceptions are `call.self-undefined-method` (stays `off`, opt-in only) and `flow.unreachable-clause` (`warning`, pending its false-positive gate). CI-friendly. |
90
93
 
91
94
  For finer control, `severity_overrides:` maps a rule ID or a
92
95
  family to one of `error`, `warning`, `info`, or `off`:
@@ -112,5 +112,8 @@ baseline regenerate` to shrink the file. The
112
112
  [`rigor-baseline-reduce` skill](08-skills.md) walks this loop
113
113
  interactively.
114
114
 
115
- To make CI fail when the baseline *grows*, add
116
- `--baseline-strict` to `rigor check`.
115
+ To make CI fail on *any* baseline drift, add `--baseline-strict`
116
+ to `rigor check`. It fails not only when a bucket grows past its
117
+ recorded count (excess drift, which the surfaced diagnostics already
118
+ fail on) but also when the code has *shrunk* below it (deficit drift:
119
+ the baseline is now looser than the code and should be regenerated).
@@ -108,6 +108,7 @@ source checkout. The `rigor skill` command surfaces them:
108
108
  rigor skill describe # probe the project + recommend the next skill (alias: rigor describe)
109
109
  rigor skill --list # name + absolute path for each bundled skill
110
110
  rigor skill <name> # print the SKILL.md body (with a references/ header)
111
+ rigor skill --full <name> # the body + every references/*.md inline (complete procedure)
111
112
  rigor skill --path <name> # one-line absolute SKILL.md path, for a file-reading tool
112
113
  ```
113
114
 
@@ -133,6 +134,26 @@ npx skills add rigortype/rigor
133
134
  (The contributor-only skills under `.claude/skills/` are marked internal
134
135
  and are not installed by a bulk `npx skills add`.)
135
136
 
137
+ ### Keeping installed skills current
138
+
139
+ A skill copied into your project this way is **frozen at install time**,
140
+ while Rigor keeps evolving — flags, config keys, and rule ids move release
141
+ to release. To keep that from going stale (陳腐化), every skill opens with
142
+ a **"First: load the version-current copy"** directive: before following
143
+ its steps, an agent re-fetches the authoritative body from the *installed*
144
+ Rigor with
145
+
146
+ ```sh
147
+ rigor skill --full <name> # the current body + all its references/, in one call
148
+ ```
149
+
150
+ Because the vendored copy is fixed at install time but `rigor skill` always
151
+ reads the gem, the two diverge as you upgrade, and the directive makes the
152
+ agent prefer the gem's current version. You therefore do **not** need to
153
+ re-install the skills after upgrading Rigor — only the thin entry point
154
+ (`rigor-next-steps`) needs to work before Rigor exists; everything
155
+ version-specific is served live by the installed binary.
156
+
136
157
  ## Running a skill
137
158
 
138
159
  In an agent that supports Agent Skills, invoke the skill by name (in
@@ -181,8 +181,8 @@ entry carries two fields:
181
181
 
182
182
  - **`dynamic_origin`** — the cause the value became dynamic:
183
183
  `external_gem_without_rbs`, `framework_dsl_boundary`,
184
- `analyzer_budget_cutoff`, `explicit_untyped`, or
185
- `unsupported_syntax`.
184
+ `analyzer_budget_cutoff`, `explicit_untyped`,
185
+ `inferred_return_untyped`, or `unsupported_syntax`.
186
186
  - **`tractability`** — the action axis derived from that cause:
187
187
  - **`add_rbs`** — you can close it with a type: install RBS
188
188
  (`rbs collection install`), enable `dependencies.source_inference:`,
@@ -190,8 +190,10 @@ entry carries two fields:
190
190
  - **`enable_plugin`** — a framework / DSL boundary; reach for a
191
191
  plugin or [`pre_eval:`](03-configuration.md), not a hand-written
192
192
  type.
193
- - **`engine_gap`** — not closable by a user type (a budget cutoff
194
- or a construct Rigor does not yet model); report it.
193
+ - **`engine_gap`** — not closable by a user type: a budget cutoff,
194
+ an inference gap (an untyped parameter or an unbound instance
195
+ variable — `inferred_return_untyped`), or a construct Rigor does
196
+ not yet model; report it.
195
197
 
196
198
  The text report prints a one-line `by tractability:` breakdown under
197
199
  the "Add a type here" header, and the JSON carries the same totals as
@@ -6,9 +6,8 @@ module Rigor
6
6
  module Analysis
7
7
  # ADR-22 Slice 1 — PHPStan-shaped per-project baseline.
8
8
  #
9
- # Loads `.rigor-baseline.yml`, filters a current run's
10
- # diagnostic stream against the recorded buckets, and emits
11
- # an `(surfaced, silenced_count)` pair for the CLI to render.
9
+ # Loads `.rigor-baseline.yml`, filters a current run's diagnostic stream against the recorded buckets, and
10
+ # emits an `(surfaced, silenced_count)` pair for the CLI to render.
12
11
  #
13
12
  # Two row shapes are accepted (WD1):
14
13
  #
@@ -27,17 +26,13 @@ module Rigor
27
26
  # ## Semantics per (file, rule [, message]) bucket (WD4)
28
27
  #
29
28
  # actual <= count → ALL diagnostics in the bucket are silenced.
30
- # actual > count → ALL diagnostics in the bucket surface
31
- # (not just the excess delta the bucket
32
- # has crossed its threshold; the team's
33
- # review focus shifts from "which N is new"
34
- # to "what's going on with this rule in
35
- # this file as a whole").
29
+ # actual > count → ALL diagnostics in the bucket surface (not just the excess delta — the bucket has
30
+ # crossed its threshold; the team's review focus shifts from "which N is new" to
31
+ # "what's going on with this rule in this file as a whole").
36
32
  #
37
33
  # ## Filter pipeline position (WD6)
38
34
  #
39
- # The baseline filter runs LAST among the diagnostic-suppression
40
- # layers:
35
+ # The baseline filter runs LAST among the diagnostic-suppression layers:
41
36
  #
42
37
  # emit → `# rigor:disable` (per-line)
43
38
  # → `# rigor:disable-file`
@@ -47,41 +42,32 @@ module Rigor
47
42
  #
48
43
  # ## Loading (WD2 (b))
49
44
  #
50
- # `Baseline.load` is called by the CLI when it has resolved
51
- # an explicit baseline path (from `--baseline=PATH` on the
52
- # CLI or `baseline: <path>` in `.rigor.yml`). The presence
53
- # of `.rigor-baseline.yml` on disk alone never triggers a
54
- # load — that's the CLI / Configuration's job to enforce.
45
+ # `Baseline.load` is called by the CLI when it has resolved an explicit baseline path (from `--baseline=PATH`
46
+ # on the CLI or `baseline: <path>` in `.rigor.yml`). The presence of `.rigor-baseline.yml` on disk alone
47
+ # never triggers a load — that's the CLI / Configuration's job to enforce.
55
48
  #
56
49
  # ## Path handling
57
50
  #
58
- # Baselines store file paths **relative to the project root**
59
- # (the working directory when `rigor` is run). This makes the
60
- # generated `.rigor-baseline.yml` portable across machines and
61
- # checkout locations. When filtering a live diagnostic stream,
62
- # the instance normalises each diagnostic's absolute path to a
63
- # relative one before the bucket lookup.
51
+ # Baselines store file paths **relative to the project root** (the working directory when `rigor` is run).
52
+ # This makes the generated `.rigor-baseline.yml` portable across machines and checkout locations. When
53
+ # filtering a live diagnostic stream, the instance normalises each diagnostic's absolute path to a relative
54
+ # one before the bucket lookup.
64
55
  class Baseline
65
- # The bucket key is intentionally tuple-shaped so rule-ID
66
- # rows and message-pattern rows can coexist in a single
67
- # multimap. `message` is `nil` for rule-ID rows; a Regexp
68
- # for message-pattern rows.
69
- # `count` shadows Struct#count; intentional — `count` is the
70
- # PHPStan-compatible field name and we don't use the
71
- # Enumerable-style `Struct#count` on Bucket instances.
56
+ # The bucket key is intentionally tuple-shaped so rule-ID rows and message-pattern rows can coexist in a
57
+ # single multimap. `message` is `nil` for rule-ID rows; a Regexp for message-pattern rows.
58
+ # `count` shadows Struct#count; intentional — `count` is the PHPStan-compatible field name and we don't
59
+ # use the Enumerable-style `Struct#count` on Bucket instances.
72
60
  Bucket = Struct.new(:file, :rule, :message_regex, :count, keyword_init: true) # rubocop:disable Lint/StructNewOverride
73
61
 
74
62
  CURRENT_VERSION = 1
75
63
 
76
64
  class << self
77
- # Load a baseline file from disk. Returns `nil` when the
78
- # path is nil (the caller's "no baseline configured"
79
- # state). Raises {LoadError} on malformed content;
80
- # callers translate to a user-facing diagnostic.
65
+ # Load a baseline file from disk. Returns `nil` when the path is nil (the caller's "no baseline
66
+ # configured" state). Raises {LoadError} on malformed content; callers translate to a user-facing
67
+ # diagnostic.
81
68
  #
82
- # `project_root:` is the working directory against which
83
- # stored relative paths are resolved during filtering.
84
- # Defaults to `Dir.pwd`.
69
+ # `project_root:` is the working directory against which stored relative paths are resolved during
70
+ # filtering. Defaults to `Dir.pwd`.
85
71
  def load(path, project_root: Dir.pwd)
86
72
  return nil if path.nil?
87
73
  return new([], project_root: project_root) unless File.exist?(path)
@@ -90,15 +76,12 @@ module Rigor
90
76
  parse_loaded(raw, path: path, project_root: project_root)
91
77
  end
92
78
 
93
- # Build a baseline from a current run's diagnostic stream.
94
- # `match_mode:` is `:rule` (default) or `:message`. The
95
- # message-mode generator passes literal messages through
96
- # `Regexp.escape` so generated rows never accidentally
97
- # over-match on punctuation.
79
+ # Build a baseline from a current run's diagnostic stream. `match_mode:` is `:rule` (default) or
80
+ # `:message`. The message-mode generator passes literal messages through `Regexp.escape` so generated
81
+ # rows never accidentally over-match on punctuation.
98
82
  #
99
- # `project_root:` is used to convert absolute diagnostic
100
- # paths to relative paths in the generated YAML. Defaults
101
- # to `Dir.pwd`.
83
+ # `project_root:` is used to convert absolute diagnostic paths to relative paths in the generated YAML.
84
+ # Defaults to `Dir.pwd`.
102
85
  def from_diagnostics(diagnostics, match_mode: :rule, project_root: Dir.pwd)
103
86
  raise ArgumentError, "match_mode must be :rule or :message" unless %i[rule message].include?(match_mode)
104
87
 
@@ -177,11 +160,9 @@ module Rigor
177
160
  end
178
161
  end
179
162
 
180
- # Generates a Regexp source string for the baseline row.
181
- # The string is `Regexp.escape`d so the YAML round-trip
182
- # produces a regex that matches the literal message.
183
- # Users hand-editing the row can replace the escaped
184
- # form with a pattern.
163
+ # Generates a Regexp source string for the baseline row. The string is `Regexp.escape`d so the YAML
164
+ # round-trip produces a regex that matches the literal message. Users hand-editing the row can replace
165
+ # the escaped form with a pattern.
185
166
  def message_pattern_for(message)
186
167
  Regexp.new(Regexp.escape(message.to_s))
187
168
  end
@@ -203,10 +184,8 @@ module Rigor
203
184
  # For each (file, qualified_rule) pair, two arrays:
204
185
  # - rule-ID rows (message_regex == nil)
205
186
  # - message-pattern rows (message_regex != nil)
206
- # The matcher walks message-pattern rows first (tighter
207
- # match takes precedence); diagnostics that don't match
208
- # any message row fall through to the rule-ID row if
209
- # one exists.
187
+ # The matcher walks message-pattern rows first (tighter match takes precedence); diagnostics that
188
+ # don't match any message row fall through to the rule-ID row if one exists.
210
189
  @by_pair = buckets.group_by { |b| [b.file, b.rule] }.freeze
211
190
  freeze
212
191
  end
@@ -214,10 +193,8 @@ module Rigor
214
193
  # Apply the baseline filter to a diagnostic stream.
215
194
  #
216
195
  # Returns a 2-tuple:
217
- # - `surfaced` — the diagnostics that survived the filter
218
- # (new findings + entire over-threshold buckets).
219
- # - `silenced_count` — how many diagnostics the baseline
220
- # suppressed (for the WD7 stderr summary line).
196
+ # - `surfaced` — the diagnostics that survived the filter (new findings + entire over-threshold buckets).
197
+ # - `silenced_count` — how many diagnostics the baseline suppressed (for the WD7 stderr summary line).
221
198
  def filter(diagnostics)
222
199
  return [diagnostics, 0] if buckets.empty?
223
200
 
@@ -228,10 +205,8 @@ module Rigor
228
205
  grouped.each_value do |entries|
229
206
  bucket = entries[:bucket]
230
207
  diags = entries[:diagnostics]
231
- # No matching bucket → all surface as new findings.
232
- # `actual <= count` → all silenced (within threshold,
233
- # WD4). `actual > count` → all surface (over
234
- # threshold, WD4).
208
+ # No matching bucket → all surface as new findings. `actual <= count` → all silenced (within
209
+ # threshold, WD4). `actual > count` → all surface (over threshold, WD4).
235
210
  if bucket && diags.size <= bucket.count
236
211
  silenced_count += diags.size
237
212
  else
@@ -239,39 +214,31 @@ module Rigor
239
214
  end
240
215
  end
241
216
 
242
- # Diagnostics that lacked a rule or a path bypass the
243
- # baseline entirely (the baseline can't address them).
217
+ # Diagnostics that lacked a rule or a path bypass the baseline entirely (the baseline can't address
218
+ # them).
244
219
  unkeyable = diagnostics.reject { |d| d.qualified_rule && d.path }
245
220
  [surfaced + unkeyable, silenced_count]
246
221
  end
247
222
 
248
- # A single bucket's drift state for slice 2 inspection.
249
- # `status` is one of:
223
+ # A single bucket's drift state for slice 2 inspection. `status` is one of:
250
224
  #
251
225
  # - `:within` — `actual <= count` (silenced by the filter).
252
- # - `:over` — `actual > count` (over threshold; surfaced
253
- # in the regular `rigor check` output).
226
+ # - `:over` — `actual > count` (over threshold; surfaced in the regular `rigor check` output).
254
227
  # - `:cleared` — `actual == 0` (the bucket can be pruned).
255
- # - `:reducible` — `0 < actual < count` (the bucket's count
256
- # can be tightened; future `regenerate`
257
- # slice 5 handles this).
228
+ # - `:reducible` — `0 < actual < count` (the bucket's count can be tightened; future `regenerate` slice 5
229
+ # handles this).
258
230
  DriftRow = Struct.new(:bucket, :actual_count, :status, keyword_init: true) do
259
231
  def delta
260
232
  actual_count - bucket.count
261
233
  end
262
234
  end
263
235
 
264
- # Walk the current diagnostic stream and report
265
- # bucket-level drift. Each baseline bucket becomes one
266
- # DriftRow regardless of whether the current run still
267
- # matches it.
236
+ # Walk the current diagnostic stream and report bucket-level drift. Each baseline bucket becomes one
237
+ # DriftRow regardless of whether the current run still matches it.
268
238
  #
269
- # @param diagnostics [Array<Diagnostic>] current run's
270
- # diagnostic stream (PRE-filter pass the raw
271
- # `result.diagnostics` from `Runner#run`, not the
272
- # post-baseline surface).
273
- # @return [Array<DriftRow>] one entry per baseline bucket,
274
- # in baseline-file order.
239
+ # @param diagnostics [Array<Diagnostic>] current run's diagnostic stream (PRE-filter — pass the raw
240
+ # `result.diagnostics` from `Runner#run`, not the post-baseline surface).
241
+ # @return [Array<DriftRow>] one entry per baseline bucket, in baseline-file order.
275
242
  def audit(diagnostics)
276
243
  counts = Hash.new(0)
277
244
  diagnostics.each do |diag|
@@ -287,18 +254,15 @@ module Rigor
287
254
  end
288
255
  end
289
256
 
290
- # Returns a new Baseline with the given buckets dropped.
291
- # Used by `rigor baseline prune` (slice 2) to remove
292
- # cleared buckets (`actual == 0`) from the on-disk file.
257
+ # Returns a new Baseline with the given buckets dropped. Used by `rigor baseline prune` (slice 2) to
258
+ # remove cleared buckets (`actual == 0`) from the on-disk file.
293
259
  def without(buckets_to_drop)
294
260
  dropset = buckets_to_drop.to_set
295
261
  self.class.new(buckets.reject { |b| dropset.include?(b) }, project_root: @project_root)
296
262
  end
297
263
 
298
- # Serialise to a YAML string. The generator path writes
299
- # this through `File.write`; the dump format is stable
300
- # across versions of this class as long as the bucket
301
- # shape is unchanged.
264
+ # Serialise to a YAML string. The generator path writes this through `File.write`; the dump format is
265
+ # stable across versions of this class as long as the bucket shape is unchanged.
302
266
  def to_yaml
303
267
  rows = buckets.map do |bucket|
304
268
  row = { "file" => bucket.file, "rule" => bucket.rule }
@@ -311,8 +275,7 @@ module Rigor
311
275
  YAML.dump(document)
312
276
  end
313
277
 
314
- # The number of buckets recorded. Useful for the CLI
315
- # summary on `generate`.
278
+ # The number of buckets recorded. Useful for the CLI summary on `generate`.
316
279
  def size
317
280
  buckets.size
318
281
  end
@@ -344,11 +307,9 @@ module Rigor
344
307
  end
345
308
 
346
309
  def group_diagnostics_for_filtering(diagnostics)
347
- # First pass: bin each diagnostic into the bucket that
348
- # claims it. Message-pattern rows take precedence over
349
- # rule-ID rows because they're more specific. A
350
- # diagnostic that matches no row goes into a synthetic
351
- # "no-bucket" bin keyed by (file, rule).
310
+ # First pass: bin each diagnostic into the bucket that claims it. Message-pattern rows take precedence
311
+ # over rule-ID rows because they're more specific. A diagnostic that matches no row goes into a
312
+ # synthetic "no-bucket" bin keyed by (file, rule).
352
313
  bins = {}
353
314
  diagnostics.each do |diag|
354
315
  next if diag.qualified_rule.nil? || diag.path.nil?
@@ -370,8 +331,7 @@ module Rigor
370
331
  candidates = @by_pair[[normalize_path(diagnostic.path), diagnostic.qualified_rule]]
371
332
  return nil if candidates.nil? || candidates.empty?
372
333
 
373
- # Tighter (message-pattern) buckets first, then the
374
- # rule-ID bucket as fallback.
334
+ # Tighter (message-pattern) buckets first, then the rule-ID bucket as fallback.
375
335
  message_buckets, rule_buckets = candidates.partition(&:message_regex)
376
336
  message_buckets.each do |b|
377
337
  return b if b.message_regex.match?(diagnostic.message.to_s)
@@ -2,32 +2,25 @@
2
2
 
3
3
  module Rigor
4
4
  module Analysis
5
- # Binds one logical project path (the path the user is editing,
6
- # e.g. `lib/foo.rb`) to a physical file containing the in-flight
7
- # buffer bytes (e.g. `/tmp/9539itfeh2.rb`). When the runner /
8
- # workers / pre-passes need to read source for the logical path,
9
- # they read from the physical path instead; when they emit a
10
- # `Diagnostic`, the path is the logical one so editors highlight
11
- # the buffer the user is actually looking at.
5
+ # Binds one logical project path (the path the user is editing, e.g. `lib/foo.rb`) to a physical file
6
+ # containing the in-flight buffer bytes (e.g. `/tmp/9539itfeh2.rb`). When the runner / workers / pre-passes
7
+ # need to read source for the logical path, they read from the physical path instead; when they emit a
8
+ # `Diagnostic`, the path is the logical one so editors highlight the buffer the user is actually looking at.
12
9
  #
13
- # See `docs/design/20260516-editor-mode.md` for the design.
14
- # The CLI surfaces this through paired `--tmp-file` /
15
- # `--instead-of` flags on `rigor check` and `rigor type-of`;
16
- # programmatic callers pass a `BufferBinding` to `Runner.new`.
10
+ # See `docs/design/20260516-editor-mode.md` for the design. The CLI surfaces this through paired
11
+ # `--tmp-file` / `--instead-of` flags on `rigor check` and `rigor type-of`; programmatic callers pass a
12
+ # `BufferBinding` to `Runner.new`.
17
13
  BufferBinding = Data.define(:logical_path, :physical_path) do
18
- # Returns the physical path to read bytes from when the caller
19
- # is about to parse `path`. For non-logical paths returns the
20
- # input unchanged. Cheap to call on every path; the binding is
21
- # singular today (one buffer per run).
14
+ # Returns the physical path to read bytes from when the caller is about to parse `path`. For non-logical
15
+ # paths returns the input unchanged. Cheap to call on every path; the binding is singular today (one
16
+ # buffer per run).
22
17
  def resolve(path)
23
18
  path == logical_path ? physical_path : path
24
19
  end
25
20
 
26
- # Returns the path the caller should report in user-facing
27
- # output (diagnostics, run stats) when it currently holds the
28
- # physical path. The inverse of `#resolve`. Non-physical paths
29
- # pass through unchanged, so it is safe to stamp every
30
- # outgoing path through this helper.
21
+ # Returns the path the caller should report in user-facing output (diagnostics, run stats) when it
22
+ # currently holds the physical path. The inverse of `#resolve`. Non-physical paths pass through
23
+ # unchanged, so it is safe to stamp every outgoing path through this helper.
31
24
  def display_path(path)
32
25
  path == physical_path ? logical_path : path
33
26
  end
@@ -5,44 +5,29 @@ require "prism"
5
5
  module Rigor
6
6
  module Analysis
7
7
  module CheckRules
8
- # Walks a parse tree and collects every `IfNode` / `UnlessNode`
9
- # whose predicate folds to a `Type::Constant` AND is NOT
10
- # disqualified by the rule's conservative envelope.
8
+ # Walks a parse tree and collects every `IfNode` / `UnlessNode` whose predicate folds to a
9
+ # `Type::Constant` AND is NOT disqualified by the rule's conservative envelope.
11
10
  #
12
- # The companion rule (`flow.always-truthy-condition`) is
13
- # the inferred-constant counterpart to
14
- # `flow.unreachable-branch` (which only fires on syntactic
15
- # literals). The literal-only rule was the v0.1.2 first-cut
16
- # because Rigor's incomplete loop / mutation / RBS-strictness
17
- # modelling produces inferred constants that look real but
18
- # are pragmatically false-positives. This collector adds two
19
- # surgical skips to bring the inferred-constant case in
20
- # without resurfacing those false positives:
11
+ # The companion rule (`flow.always-truthy-condition`) is the inferred-constant counterpart to
12
+ # `flow.unreachable-branch` (which only fires on syntactic literals). The literal-only rule was the
13
+ # v0.1.2 first-cut because Rigor's incomplete loop / mutation / RBS-strictness modelling produces
14
+ # inferred constants that look real but are pragmatically false-positives. This collector adds two
15
+ # surgical skips to bring the inferred-constant case in without resurfacing those false positives:
21
16
  #
22
- # - **Inside a loop / block**: predicates nested inside
23
- # `WhileNode` / `UntilNode` / `ForNode` / `BlockNode`
24
- # ancestors. Mutation tracking through loop bodies is
25
- # incomplete (`shift = 0; loop { shift += 7 }` keeps
26
- # `shift` at `Constant<7>` per the analyzer), so a
27
- # `Constant<bool>` predicate inside the loop body is
28
- # suspect.
29
- # - **Defensive predicate calls**: `.nil?` / `.empty?` /
30
- # `.zero?` / `.any?` / `.none?` / `.all?`. These read
31
- # like the user explicitly checking for a runtime
32
- # condition that the type system already proves can't
33
- # happen — Rigor's strict-on-returns RBS often disagrees
34
- # with the user's defensive check (e.g. `Module#name`
35
- # returns `String` per RBS but anonymous classes really
36
- # do return `nil`). Skipping these forms keeps the rule
37
- # useful for genuine logic errors without faulting
38
- # defensive code.
17
+ # - **Inside a loop / block**: predicates nested inside `WhileNode` / `UntilNode` / `ForNode` /
18
+ # `BlockNode` ancestors. Mutation tracking through loop bodies is incomplete (`shift = 0; loop {
19
+ # shift += 7 }` keeps `shift` at `Constant<7>` per the analyzer), so a `Constant<bool>` predicate
20
+ # inside the loop body is suspect.
21
+ # - **Defensive predicate calls**: `.nil?` / `.empty?` / `.zero?` / `.any?` / `.none?` / `.all?`. These
22
+ # read like the user explicitly checking for a runtime condition that the type system already proves
23
+ # can't happen — Rigor's strict-on-returns RBS often disagrees with the user's defensive check (e.g.
24
+ # `Module#name` returns `String` per RBS but anonymous classes really do return `nil`). Skipping
25
+ # these forms keeps the rule useful for genuine logic errors without faulting defensive code.
39
26
  #
40
- # Also skipped (so the literal-only `unreachable-branch`
41
- # rule doesn't double-fire alongside this one):
27
+ # Also skipped (so the literal-only `unreachable-branch` rule doesn't double-fire alongside this one):
42
28
  #
43
- # - Predicates whose direct AST shape is a literal
44
- # (`TrueNode` / `FalseNode` / `NilNode` / numeric / string
45
- # / symbol / regexp).
29
+ # - Predicates whose direct AST shape is a literal (`TrueNode` / `FalseNode` / `NilNode` / numeric /
30
+ # string / symbol / regexp).
46
31
  class AlwaysTruthyConditionCollector
47
32
  DEFENSIVE_PREDICATES = %i[nil? empty? zero? any? none? all? respond_to?].freeze
48
33
  LOOP_OR_BLOCK_NODE_CLASSES = [
@@ -56,34 +41,30 @@ module Rigor
56
41
 
57
42
  Result = Data.define(:node, :polarity)
58
43
 
59
- # ADR-53 Track B — the node classes the shared {RuleWalk}
60
- # dispatches to this collector, and the context gate under which
61
- # the walk suppresses it (loop / block bodies see the envelope
62
- # above). The legacy walk's `!in_loop_or_block` guard is now the
63
- # walk's `:loop_or_block` gate, applied before `#visit`.
44
+ # ADR-53 Track B — the node classes the shared {RuleWalk} dispatches to this collector, and the
45
+ # context gate under which the walk suppresses it (loop / block bodies — see the envelope above). The
46
+ # legacy walk's `!in_loop_or_block` guard is now the walk's `:loop_or_block` gate, applied before
47
+ # `#visit`.
64
48
  NODE_CLASSES = [Prism::IfNode, Prism::UnlessNode].freeze
65
49
  RULE_WALK_GATES = [:loop_or_block].freeze
66
50
 
67
- # @return [Array<Result>] one entry per qualifying
68
- # predicate. Empty when the tree carries no firing
51
+ # @return [Array<Result>] one entry per qualifying predicate. Empty when the tree carries no firing
69
52
  # predicates.
70
53
  def initialize(scope_index)
71
54
  @scope_index = scope_index
72
55
  @results = []
73
56
  end
74
57
 
75
- # Legacy single-collector walk — kept as the oracle the
76
- # ADR-53 Track B equivalence harness compares {RuleWalk}
77
- # against; deleted when Track B completes.
58
+ # Legacy single-collector walk — kept as the oracle the ADR-53 Track B equivalence harness compares
59
+ # {RuleWalk} against; deleted when Track B completes.
78
60
  def collect(root)
79
61
  walk(root, in_loop_or_block: false)
80
62
  @results.freeze
81
63
  end
82
64
 
83
- # {RuleWalk} entry point: the per-node logic of the legacy walk,
84
- # invoked under the same traversal contract. The `context` is
85
- # unused the loop / block suppression this collector relied on
86
- # is the walk's `:loop_or_block` gate now.
65
+ # {RuleWalk} entry point: the per-node logic of the legacy walk, invoked under the same traversal
66
+ # contract. The `context` is unused — the loop / block suppression this collector relied on is the
67
+ # walk's `:loop_or_block` gate now.
87
68
  def visit(node, _context = nil)
88
69
  collect_predicate(node)
89
70
  end