rigortype 0.3.7 → 0.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (450) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/data/capability_roles/capability_roles.rbs +36 -0
  4. data/data/gem_overlay/activesupport/core_ext.rbs +109 -13
  5. data/docs/handbook/02-everyday-types.md +17 -14
  6. data/docs/handbook/03-narrowing.md +37 -4
  7. data/docs/handbook/05-methods-and-blocks.md +1 -1
  8. data/docs/handbook/06-classes.md +2 -2
  9. data/docs/handbook/07-rbs-and-extended.md +14 -1
  10. data/docs/handbook/11-sig-gen.md +96 -15
  11. data/docs/handbook/README.md +1 -1
  12. data/docs/handbook/appendix-elixir.md +2 -2
  13. data/docs/handbook/appendix-go.md +2 -2
  14. data/docs/handbook/appendix-java-csharp.md +2 -2
  15. data/docs/handbook/appendix-mypy.md +2 -2
  16. data/docs/handbook/appendix-phpstan.md +1 -1
  17. data/docs/handbook/appendix-rust.md +1 -1
  18. data/docs/handbook/appendix-type-theory.md +4 -4
  19. data/docs/handbook/appendix-typescript.md +1 -1
  20. data/docs/llms.txt +2 -0
  21. data/docs/manual/02-cli-reference.md +39 -4
  22. data/docs/manual/03-configuration.md +11 -0
  23. data/docs/manual/04-diagnostics.md +12 -1
  24. data/docs/manual/07-plugins.md +15 -5
  25. data/docs/manual/08-skills.md +23 -2
  26. data/docs/manual/11-ci.md +9 -0
  27. data/docs/manual/15-type-protection-coverage.md +8 -0
  28. data/docs/manual/16-rbs-extended-annotations.md +6 -1
  29. data/docs/manual/18-removing-dead-code.md +10 -8
  30. data/docs/manual/plugins/rigor-actionpack.md +18 -0
  31. data/docs/manual/plugins/rigor-activerecord.md +28 -0
  32. data/docs/manual/plugins/rigor-activesupport-core-ext.md +32 -2
  33. data/docs/manual/plugins/rigor-rbs-inline.md +42 -1
  34. data/lib/rigor/analysis/baseline.rb +2 -2
  35. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +1 -1
  36. data/lib/rigor/analysis/check_rules/dead_version_guard_arms.rb +1 -5
  37. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +10 -2
  38. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +1 -1
  39. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +15 -6
  40. data/lib/rigor/analysis/check_rules/rule_ids.rb +1 -1
  41. data/lib/rigor/analysis/check_rules/rule_walk.rb +39 -4
  42. data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +1 -1
  43. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +1 -1
  44. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
  45. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +0 -1
  46. data/lib/rigor/analysis/check_rules.rb +200 -32
  47. data/lib/rigor/analysis/crash_signature.rb +69 -13
  48. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +1 -1
  49. data/lib/rigor/analysis/dependency_source_inference/builder.rb +0 -2
  50. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +0 -2
  51. data/lib/rigor/analysis/dependency_source_inference/index.rb +5 -5
  52. data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +1 -2
  53. data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
  54. data/lib/rigor/analysis/effects_cache_probe.rb +3 -4
  55. data/lib/rigor/analysis/erb_template_detector.rb +1 -2
  56. data/lib/rigor/analysis/fact_store.rb +6 -1
  57. data/lib/rigor/analysis/incremental.rb +16 -0
  58. data/lib/rigor/analysis/incremental_session.rb +100 -29
  59. data/lib/rigor/analysis/plugin_fact_fingerprint.rb +0 -1
  60. data/lib/rigor/analysis/reachability/graph.rb +3 -5
  61. data/lib/rigor/analysis/reachability/plugin_roots.rb +6 -5
  62. data/lib/rigor/analysis/reachability/project_files.rb +2 -2
  63. data/lib/rigor/analysis/reachability/scan.rb +5 -5
  64. data/lib/rigor/analysis/reachability/scan_cache.rb +2 -2
  65. data/lib/rigor/analysis/reachability/signature_scan.rb +2 -2
  66. data/lib/rigor/analysis/result.rb +1 -3
  67. data/lib/rigor/analysis/rule_catalog.rb +4 -1
  68. data/lib/rigor/analysis/run_cache_key.rb +1 -1
  69. data/lib/rigor/analysis/run_cache_probe.rb +3 -5
  70. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -8
  71. data/lib/rigor/analysis/runner/declaration_position.rb +1 -2
  72. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +316 -40
  73. data/lib/rigor/analysis/runner/effect_annotation_residual_pass.rb +2 -3
  74. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +9 -10
  75. data/lib/rigor/analysis/runner/pool_coordinator.rb +430 -77
  76. data/lib/rigor/analysis/runner/project_pre_passes.rb +12 -6
  77. data/lib/rigor/analysis/runner/run_snapshots.rb +13 -3
  78. data/lib/rigor/analysis/runner.rb +200 -43
  79. data/lib/rigor/analysis/worker_session.rb +36 -12
  80. data/lib/rigor/bleeding_edge.rb +12 -24
  81. data/lib/rigor/builtins/hkt_builtins.rb +2 -2
  82. data/lib/rigor/builtins/imported_refinements.rb +166 -30
  83. data/lib/rigor/builtins/predefined_constant_refinements.rb +5 -7
  84. data/lib/rigor/builtins/regex_refinement.rb +4 -4
  85. data/lib/rigor/builtins/static_return_refinements.rb +5 -6
  86. data/lib/rigor/cache/annotation_location.rb +70 -0
  87. data/lib/rigor/cache/descriptor.rb +58 -20
  88. data/lib/rigor/cache/engine_source.rb +4 -4
  89. data/lib/rigor/cache/file_digest.rb +9 -1
  90. data/lib/rigor/cache/incremental_snapshot.rb +40 -6
  91. data/lib/rigor/cache/rbs_class_ancestor_table.rb +0 -3
  92. data/lib/rigor/cache/rbs_class_type_param_names.rb +0 -3
  93. data/lib/rigor/cache/rbs_constant_table.rb +0 -3
  94. data/lib/rigor/cache/rbs_descriptor.rb +79 -11
  95. data/lib/rigor/cache/rbs_environment.rb +8 -4
  96. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +39 -0
  97. data/lib/rigor/cache/rbs_known_class_names.rb +0 -3
  98. data/lib/rigor/cache/store.rb +85 -33
  99. data/lib/rigor/cli/annotate_command.rb +5 -6
  100. data/lib/rigor/cli/check_command.rb +57 -10
  101. data/lib/rigor/cli/check_invocation.rb +6 -11
  102. data/lib/rigor/cli/check_runner_factory.rb +1 -5
  103. data/lib/rigor/cli/coverage_command.rb +1 -1
  104. data/lib/rigor/cli/coverage_mutation.rb +12 -3
  105. data/lib/rigor/cli/coverage_scan.rb +1 -5
  106. data/lib/rigor/cli/diff_command.rb +1 -1
  107. data/lib/rigor/cli/doc_links.rb +3 -3
  108. data/lib/rigor/cli/docs_command.rb +2 -2
  109. data/lib/rigor/cli/doctor_command.rb +85 -38
  110. data/lib/rigor/cli/effects_command.rb +2 -2
  111. data/lib/rigor/cli/effects_diff_renderer.rb +3 -3
  112. data/lib/rigor/cli/effects_snapshot_command.rb +1 -1
  113. data/lib/rigor/cli/explain_command.rb +34 -1
  114. data/lib/rigor/cli/fused_protection_report.rb +7 -1
  115. data/lib/rigor/cli/lsp_command.rb +1 -1
  116. data/lib/rigor/cli/mcp_command.rb +1 -1
  117. data/lib/rigor/cli/measurement_integrity_warning.rb +4 -5
  118. data/lib/rigor/cli/mutation_fork_scan.rb +6 -6
  119. data/lib/rigor/cli/mutation_protection_report.rb +7 -1
  120. data/lib/rigor/cli/plugin_command.rb +1 -1
  121. data/lib/rigor/cli/plugins_command.rb +2 -1
  122. data/lib/rigor/cli/prism_colorizer.rb +2 -2
  123. data/lib/rigor/cli/protection_fork_scan.rb +6 -6
  124. data/lib/rigor/cli/show_bleedingedge_command.rb +1 -1
  125. data/lib/rigor/cli/sig_gen_command.rb +31 -2
  126. data/lib/rigor/cli/skill_command.rb +1 -1
  127. data/lib/rigor/cli/skill_deep_probe.rb +4 -4
  128. data/lib/rigor/cli/skill_describe.rb +36 -35
  129. data/lib/rigor/cli/trace_command.rb +4 -4
  130. data/lib/rigor/cli/trace_renderer.rb +5 -6
  131. data/lib/rigor/cli/triage_command.rb +1 -1
  132. data/lib/rigor/cli/type_of_command.rb +10 -6
  133. data/lib/rigor/cli/type_scan_command.rb +4 -4
  134. data/lib/rigor/cli/unused_command.rb +11 -3
  135. data/lib/rigor/cli/upgrade_command.rb +1 -1
  136. data/lib/rigor/cli.rb +68 -6
  137. data/lib/rigor/config_audit.rb +30 -5
  138. data/lib/rigor/configuration/severity_profile.rb +6 -6
  139. data/lib/rigor/configuration.rb +43 -4
  140. data/lib/rigor/effects/attribution.rb +1 -3
  141. data/lib/rigor/effects/config_envelopes.rb +6 -8
  142. data/lib/rigor/effects/definition_lines.rb +25 -6
  143. data/lib/rigor/effects/effect_table.rb +0 -1
  144. data/lib/rigor/effects/entry_points.rb +0 -2
  145. data/lib/rigor/effects/envelope_check.rb +8 -8
  146. data/lib/rigor/effects/envelope_index.rb +5 -8
  147. data/lib/rigor/effects/framework_units.rb +4 -6
  148. data/lib/rigor/effects/identity.rb +4 -6
  149. data/lib/rigor/effects/inline_anchor.rb +7 -7
  150. data/lib/rigor/effects/label_intent.rb +3 -4
  151. data/lib/rigor/effects/liskov_check.rb +8 -8
  152. data/lib/rigor/effects/method_key.rb +1 -1
  153. data/lib/rigor/effects/plugin_facts.rb +4 -6
  154. data/lib/rigor/effects/propagator.rb +2 -3
  155. data/lib/rigor/effects/scanner.rb +1 -1
  156. data/lib/rigor/effects/signature_sources.rb +3 -5
  157. data/lib/rigor/effects/snapshot.rb +10 -10
  158. data/lib/rigor/effects/snapshot_diff.rb +1 -1
  159. data/lib/rigor/effects/unit_scan.rb +10 -10
  160. data/lib/rigor/effects/unknown_label_check.rb +3 -8
  161. data/lib/rigor/effects/unknown_label_report.rb +3 -4
  162. data/lib/rigor/environment/bundle_sig_discovery.rb +6 -6
  163. data/lib/rigor/environment/class_registry.rb +3 -1
  164. data/lib/rigor/environment/failure_slot.rb +28 -0
  165. data/lib/rigor/environment/installed_gem_set.rb +85 -0
  166. data/lib/rigor/environment/lockfile_resolver.rb +51 -4
  167. data/lib/rigor/environment/missing_gem_constant_index.rb +4 -4
  168. data/lib/rigor/environment/rbs_collection_discovery.rb +5 -5
  169. data/lib/rigor/environment/rbs_coverage_report.rb +5 -5
  170. data/lib/rigor/environment/rbs_hierarchy.rb +3 -1
  171. data/lib/rigor/environment/rbs_loader.rb +613 -113
  172. data/lib/rigor/environment.rb +189 -27
  173. data/lib/rigor/flow_contribution/merger.rb +0 -2
  174. data/lib/rigor/flow_contribution.rb +11 -13
  175. data/lib/rigor/inference/acceptance.rb +153 -8
  176. data/lib/rigor/inference/block_parameter_binder.rb +2 -3
  177. data/lib/rigor/inference/body_fixpoint.rb +5 -5
  178. data/lib/rigor/inference/budget_trace.rb +1 -5
  179. data/lib/rigor/inference/captured_locals.rb +2 -3
  180. data/lib/rigor/inference/closure_escape_analyzer.rb +2 -4
  181. data/lib/rigor/inference/coverage_scanner.rb +4 -6
  182. data/lib/rigor/inference/dynamic_origin.rb +1 -1
  183. data/lib/rigor/inference/element_read_widening.rb +168 -0
  184. data/lib/rigor/inference/expression_typer.rb +763 -143
  185. data/lib/rigor/inference/fork_map.rb +5 -7
  186. data/lib/rigor/inference/hkt_reducer.rb +2 -3
  187. data/lib/rigor/inference/hkt_registry.rb +19 -10
  188. data/lib/rigor/inference/hkt_sugar_translator.rb +15 -21
  189. data/lib/rigor/inference/index_write_widening.rb +1 -5
  190. data/lib/rigor/inference/macro_block_self_type.rb +1 -4
  191. data/lib/rigor/inference/method_dispatcher/block_folding.rb +2 -9
  192. data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +1 -1
  193. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +305 -19
  194. data/lib/rigor/inference/method_dispatcher/data_folding.rb +1 -1
  195. data/lib/rigor/inference/method_dispatcher/file_folding.rb +1 -1
  196. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +2 -2
  197. data/lib/rigor/inference/method_dispatcher/json_folding.rb +1 -1
  198. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -1
  199. data/lib/rigor/inference/method_dispatcher/math_folding.rb +41 -3
  200. data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -5
  201. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +61 -47
  202. data/lib/rigor/inference/method_dispatcher/random_folding.rb +82 -0
  203. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +165 -31
  204. data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +25 -4
  205. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +2 -8
  206. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +1 -1
  207. data/lib/rigor/inference/method_dispatcher/set_folding.rb +1 -1
  208. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +1 -1
  209. data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +1 -1
  210. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +1 -2
  211. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +4 -5
  212. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +85 -4
  213. data/lib/rigor/inference/method_dispatcher/time_folding.rb +1 -1
  214. data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +1 -2
  215. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  216. data/lib/rigor/inference/method_dispatcher.rb +12 -13
  217. data/lib/rigor/inference/method_parameter_binder.rb +4 -6
  218. data/lib/rigor/inference/multi_target_binder.rb +1 -3
  219. data/lib/rigor/inference/mutation_rejoin.rb +168 -0
  220. data/lib/rigor/inference/mutation_widening.rb +100 -51
  221. data/lib/rigor/inference/narrowing.rb +299 -46
  222. data/lib/rigor/inference/optimistic_origin.rb +1 -7
  223. data/lib/rigor/inference/origin_lookup.rb +2 -3
  224. data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
  225. data/lib/rigor/inference/pre_eval_constants.rb +6 -5
  226. data/lib/rigor/inference/precision_scanner.rb +3 -4
  227. data/lib/rigor/inference/project_patched_methods.rb +2 -2
  228. data/lib/rigor/inference/project_patched_scanner.rb +3 -3
  229. data/lib/rigor/inference/protection_scanner.rb +1 -2
  230. data/lib/rigor/inference/range_constant.rb +57 -0
  231. data/lib/rigor/inference/rbs_type_translator.rb +49 -11
  232. data/lib/rigor/inference/receiver_alias.rb +3 -3
  233. data/lib/rigor/inference/refinement_mutation.rb +72 -0
  234. data/lib/rigor/inference/scope_indexer.rb +688 -151
  235. data/lib/rigor/inference/statement_evaluator.rb +221 -50
  236. data/lib/rigor/inference/string_mutation.rb +60 -0
  237. data/lib/rigor/inference/struct_fold_safety.rb +6 -7
  238. data/lib/rigor/inference/synthetic_method_index.rb +1 -2
  239. data/lib/rigor/inference/synthetic_method_scanner.rb +4 -6
  240. data/lib/rigor/inference/version_guard.rb +28 -21
  241. data/lib/rigor/inference/void_origin.rb +3 -3
  242. data/lib/rigor/inference/void_tail_summary.rb +2 -4
  243. data/lib/rigor/language_server/buffer_table.rb +4 -4
  244. data/lib/rigor/language_server/completion_provider.rb +1 -1
  245. data/lib/rigor/language_server/debouncer.rb +1 -1
  246. data/lib/rigor/language_server/diagnostic_publisher.rb +4 -4
  247. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  248. data/lib/rigor/language_server/folding_range_provider.rb +1 -1
  249. data/lib/rigor/language_server/hover_provider.rb +1 -1
  250. data/lib/rigor/language_server/hover_renderer.rb +2 -2
  251. data/lib/rigor/language_server/incremental_sync.rb +6 -6
  252. data/lib/rigor/language_server/project_context.rb +2 -5
  253. data/lib/rigor/language_server/publish_batcher.rb +2 -2
  254. data/lib/rigor/language_server/selection_range_provider.rb +2 -2
  255. data/lib/rigor/language_server/server.rb +9 -9
  256. data/lib/rigor/language_server/signature_help_provider.rb +1 -1
  257. data/lib/rigor/language_server/uri.rb +1 -1
  258. data/lib/rigor/plugin/base.rb +90 -21
  259. data/lib/rigor/plugin/bundled_catalog.rb +167 -0
  260. data/lib/rigor/plugin/effect_attribution.rb +7 -7
  261. data/lib/rigor/plugin/effect_entry_points.rb +3 -3
  262. data/lib/rigor/plugin/fact_store.rb +6 -6
  263. data/lib/rigor/plugin/io_boundary.rb +93 -8
  264. data/lib/rigor/plugin/isolation.rb +29 -6
  265. data/lib/rigor/plugin/loader.rb +31 -12
  266. data/lib/rigor/plugin/macro/heredoc_template.rb +1 -1
  267. data/lib/rigor/plugin/macro/trait_registry.rb +1 -1
  268. data/lib/rigor/plugin/manifest.rb +23 -1
  269. data/lib/rigor/plugin/registry.rb +67 -14
  270. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +12 -1
  271. data/lib/rigor/plugin/trust_policy.rb +2 -4
  272. data/lib/rigor/plugin/type_node_resolver.rb +3 -3
  273. data/lib/rigor/plugin_gap_advisory.rb +96 -0
  274. data/lib/rigor/project_environment.rb +138 -0
  275. data/lib/rigor/protection/analysis_guard.rb +93 -14
  276. data/lib/rigor/protection/closure_kill_oracle.rb +67 -23
  277. data/lib/rigor/protection/dependency_closure.rb +5 -8
  278. data/lib/rigor/protection/diagnostic_oracle.rb +2 -2
  279. data/lib/rigor/protection/discovery_seed.rb +11 -11
  280. data/lib/rigor/protection/kill_signature.rb +2 -4
  281. data/lib/rigor/protection/measurement_integrity.rb +1 -3
  282. data/lib/rigor/protection/mutation_cache.rb +6 -9
  283. data/lib/rigor/protection/mutation_scanner.rb +12 -15
  284. data/lib/rigor/protection/mutator.rb +2 -1
  285. data/lib/rigor/protection/test_suite_oracle.rb +5 -5
  286. data/lib/rigor/rbs_extended/conformance_checker.rb +4 -3
  287. data/lib/rigor/rbs_extended/envelope_scanner.rb +12 -13
  288. data/lib/rigor/rbs_extended/hkt_directives.rb +16 -1
  289. data/lib/rigor/rbs_extended/reporter.rb +122 -16
  290. data/lib/rigor/rbs_extended.rb +67 -22
  291. data/lib/rigor/reflection/constant_path.rb +126 -0
  292. data/lib/rigor/reflection.rb +26 -10
  293. data/lib/rigor/runtime/jit.rb +6 -8
  294. data/lib/rigor/scope/discovery_index.rb +30 -0
  295. data/lib/rigor/scope.rb +203 -4
  296. data/lib/rigor/sig_gen/generator.rb +181 -102
  297. data/lib/rigor/sig_gen/layout_index.rb +3 -4
  298. data/lib/rigor/sig_gen/meta_class_shape.rb +3 -5
  299. data/lib/rigor/sig_gen/observation_collector.rb +17 -15
  300. data/lib/rigor/sig_gen/path_mapper.rb +4 -6
  301. data/lib/rigor/sig_gen/rbs_validity.rb +4 -4
  302. data/lib/rigor/sig_gen/renderer.rb +17 -11
  303. data/lib/rigor/sig_gen/skip_reason_catalog.rb +111 -0
  304. data/lib/rigor/sig_gen/superclass_spelling.rb +27 -0
  305. data/lib/rigor/sig_gen/type_elaborator.rb +1 -3
  306. data/lib/rigor/sig_gen/writer.rb +23 -6
  307. data/lib/rigor/signature_path_audit.rb +153 -6
  308. data/lib/rigor/source/literals.rb +0 -23
  309. data/lib/rigor/source/node_children.rb +0 -2
  310. data/lib/rigor/source/node_locator.rb +5 -11
  311. data/lib/rigor/source/node_walker.rb +2 -5
  312. data/lib/rigor/triage/catalogue.rb +1 -3
  313. data/lib/rigor/triage.rb +3 -5
  314. data/lib/rigor/type/accepts_result.rb +24 -6
  315. data/lib/rigor/type/combinator.rb +68 -5
  316. data/lib/rigor/type/data_class.rb +2 -2
  317. data/lib/rigor/type/data_instance.rb +4 -4
  318. data/lib/rigor/type/float_range.rb +128 -0
  319. data/lib/rigor/type/hash_shape.rb +5 -5
  320. data/lib/rigor/type/integer_range.rb +13 -8
  321. data/lib/rigor/type/nominal.rb +8 -2
  322. data/lib/rigor/type/refined.rb +3 -3
  323. data/lib/rigor/type/struct_class.rb +3 -3
  324. data/lib/rigor/type/struct_instance.rb +4 -4
  325. data/lib/rigor/type.rb +1 -0
  326. data/lib/rigor/type_node/generic.rb +1 -1
  327. data/lib/rigor/type_node/range_literal.rb +25 -0
  328. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  329. data/lib/rigor/type_node.rb +1 -0
  330. data/lib/rigor/version.rb +1 -1
  331. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +0 -4
  332. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +0 -1
  333. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +0 -2
  334. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -0
  335. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +0 -4
  336. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +3 -5
  337. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +1 -4
  338. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +1 -0
  339. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +33 -30
  340. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +0 -1
  341. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -3
  342. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +28 -2
  343. data/plugins/rigor-actionpack/sig/action_controller.rbs +71 -0
  344. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +0 -4
  345. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
  346. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +0 -1
  347. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +0 -2
  348. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +1 -1
  349. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +1 -0
  350. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +3 -4
  351. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +1 -2
  352. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +1 -2
  353. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +42 -7
  354. data/plugins/rigor-activerecord/sig/active_record/framework.rbs +123 -0
  355. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +1 -0
  356. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +9 -2
  357. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +119 -14
  358. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +1 -0
  359. data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +1 -0
  360. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +9 -7
  361. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +7 -3
  362. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +1 -0
  363. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +8 -6
  364. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +11 -7
  365. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -11
  366. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +9 -4
  367. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +1 -0
  368. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +0 -5
  369. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +0 -1
  370. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +0 -1
  371. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +8 -1
  372. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +36 -4
  373. data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +1 -0
  374. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +8 -6
  375. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +6 -3
  376. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +1 -0
  377. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -1
  378. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +0 -3
  379. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +1 -0
  380. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +0 -5
  381. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -2
  382. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +0 -1
  383. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +0 -1
  384. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +1 -0
  385. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +0 -12
  386. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +1 -3
  387. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +0 -1
  388. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +1 -0
  389. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/acronyms.rb +5 -5
  390. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -10
  391. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +3 -3
  392. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +2 -2
  393. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +2 -2
  394. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +2 -2
  395. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +9 -10
  396. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +2 -3
  397. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -0
  398. data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +1 -0
  399. data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +13 -1
  400. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +128 -3
  401. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +0 -3
  402. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +0 -3
  403. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -5
  404. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +2 -3
  405. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +1 -0
  406. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +0 -3
  407. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +1 -0
  408. data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +1 -0
  409. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +1 -9
  410. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +1 -0
  411. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +0 -4
  412. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/effects.rb +1 -1
  413. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +1 -1
  414. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +0 -1
  415. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +0 -2
  416. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +1 -0
  417. data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -0
  418. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +2 -5
  419. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +1 -4
  420. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +3 -9
  421. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +4 -4
  422. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +1 -2
  423. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +4 -5
  424. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +7 -5
  425. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +5 -5
  426. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +1 -0
  427. data/sig/rigor/analysis/reachability/scan_cache.rbs +12 -0
  428. data/sig/rigor/cache.rbs +2 -3
  429. data/sig/rigor/environment.rbs +11 -0
  430. data/sig/rigor/inference.rbs +3 -10
  431. data/sig/rigor/plugin/io_boundary.rbs +4 -0
  432. data/sig/rigor/plugin/manifest.rbs +2 -1
  433. data/sig/rigor/reflection.rbs +3 -1
  434. data/sig/rigor/scope.rbs +24 -3
  435. data/sig/rigor/sig_gen/skip_reason_catalog.rbs +14 -0
  436. data/sig/rigor/type.rbs +27 -1
  437. data/sig/rigor.rbs +1 -0
  438. data/skills/rigor-ask/SKILL.md +8 -6
  439. data/skills/rigor-plugin-author/references/02-walker-and-types.md +13 -2
  440. data/skills/rigor-project-init/SKILL.md +6 -3
  441. data/skills/rigor-project-init/references/02-configure.md +10 -6
  442. data/skills/rigor-project-init/references/06-agent-contract.md +67 -0
  443. data/skills/rigor-type-oracle/SKILL.md +228 -0
  444. data/skills/rigor-type-oracle/references/01-oracle-commands.md +261 -0
  445. data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +52 -0
  446. data/skills/rigor-type-oracle/references/03-gap-protocol.md +128 -0
  447. data/skills/rigor-unused-adjudicate/SKILL.md +8 -5
  448. metadata +28 -3
  449. data/lib/rigor/cli/probe_environment.rb +0 -85
  450. data/sig/rigor/inference/builtins/numeric_catalog.rbs +0 -3
@@ -7,9 +7,11 @@ require_relative "../configuration"
7
7
  require_relative "../config_audit"
8
8
  require_relative "../analysis/baseline"
9
9
  require_relative "../analysis/result"
10
+ require_relative "../environment/bundle_sig_discovery"
10
11
  require_relative "../plugin"
11
12
  require_relative "../plugin/loader"
12
13
  require_relative "../plugin/services"
14
+ require_relative "../plugin_gap_advisory"
13
15
  require_relative "../reflection"
14
16
  require_relative "../type/combinator"
15
17
  require_relative "check_invocation"
@@ -33,17 +35,12 @@ module Rigor
33
35
  CHECK_RBS = "rbs_environment"
34
36
  CHECK_PLUGINS = "plugins"
35
37
  CHECK_BASELINE = "baseline"
36
- CHECK_RAILS = "rails_plugins"
38
+ CHECK_PLUGIN_GAP = "plugin_gap"
37
39
  CHECK_GEMFILE = "gemfile_install"
38
40
  CHECK_PLUGIN_SKEW = "plugin_skew"
41
+ CHECK_BUNDLE_LAYOUT = "bundle_layout"
39
42
 
40
- RAILS_LOCK_MARKERS = %w[railties actionpack activerecord actioncable].freeze
41
- RAILS_PLUGIN_MARKERS = %w[
42
- rigor-activerecord rigor-actionpack rigor-actionmailer rigor-activejob
43
- rigor-rails-routes rigor-rails-i18n rigor-actioncable rigor-activestorage rigor-rails
44
- ].freeze
45
-
46
- # @return [Integer] CLI exit status.
43
+ # @return CLI exit status.
47
44
  def run
48
45
  options = parse_options
49
46
  configuration = Configuration.load(options.fetch(:config))
@@ -70,8 +67,8 @@ module Rigor
70
67
  # 5. Baseline drift check.
71
68
  findings.concat(check_baseline(configuration, result))
72
69
 
73
- # 6. Rails locked but no Rails plugins.
74
- findings.concat(check_rails_plugins(configuration))
70
+ # 6. Gems locked that a bundled plugin models, with that plugin not enabled (ADR-96 WD2).
71
+ findings.concat(check_plugin_gap(configuration))
75
72
 
76
73
  # 7. Rigor itself resolved as a project dependency.
77
74
  findings.concat(check_gemfile_install)
@@ -79,6 +76,9 @@ module Rigor
79
76
  # 8. A bundled plugin loaded from a different rigortype installation than the engine (#194 slice 3).
80
77
  findings.concat(check_plugin_skew(registry))
81
78
 
79
+ # 9. The project's gems are installed somewhere Rigor cannot see (ADR-25 carry-over).
80
+ findings.concat(check_bundle_layout(configuration))
81
+
82
82
  report(findings, options.fetch(:format))
83
83
  findings.any? { |f| f[:status] == :fail } ? 1 : 0
84
84
  rescue OptionParser::InvalidArgument => e
@@ -249,6 +249,46 @@ module Rigor
249
249
  File.expand_path(path)
250
250
  end
251
251
 
252
+ # ADR-25 carry-over — the plain `bundle install` layout. {Environment::BundleSigDiscovery} detects a
253
+ # bundle root only from paths the PROJECT records: `.bundle/config`'s `BUNDLE_PATH`, an in-tree
254
+ # `vendor/bundle`, or the user-global Bundler config. The default layout records none of those — the
255
+ # gems live in the active Ruby's `GEM_HOME`, which is a property of the project's toolchain, and
256
+ # ADR-27 keeps Rigor from running that toolchain to ask. So detection is not a bounded probe Rigor can
257
+ # add; what it CAN do is stop the state being silent. Without this the user sees gem-shipped `sig/`
258
+ # simply not taking effect, with nothing anywhere naming the reason.
259
+ #
260
+ # Warn, never fail: the layout is the Ruby default and is perfectly correct — only ~3% of gems ship
261
+ # `sig/` at all, so most projects lose nothing by it.
262
+ def check_bundle_layout(configuration)
263
+ return [] unless File.file?(File.join(Dir.pwd, "Gemfile.lock"))
264
+ return [] if resolved_bundle_root(configuration)
265
+
266
+ [
267
+ {
268
+ check: CHECK_BUNDLE_LAYOUT,
269
+ status: :warn,
270
+ message: "Gems are installed in the active Ruby's default gem home, which Rigor cannot locate — " \
271
+ "no gem-shipped `sig/` is being discovered",
272
+ hint: "Rigor reads your project as data and never runs its toolchain, so it can only find a " \
273
+ "bundle whose location the project records (`.bundle/config`'s `BUNDLE_PATH`, an in-tree " \
274
+ "`vendor/bundle`, or your global Bundler config). Point it at the install root with " \
275
+ "`bundler.bundle_path:` in `.rigor.yml`, run `bundle config set --local path vendor/bundle` " \
276
+ "and reinstall, or supply signatures with `rbs collection install` instead."
277
+ }
278
+ ]
279
+ end
280
+
281
+ def resolved_bundle_root(configuration)
282
+ Environment::BundleSigDiscovery.resolve_bundle_path(
283
+ bundle_path: configuration.bundler_bundle_path,
284
+ project_root: Dir.pwd,
285
+ auto_detect: configuration.bundler_auto_detect
286
+ )
287
+ rescue StandardError
288
+ # A doctor check must never be the thing that breaks the command it is diagnosing.
289
+ nil
290
+ end
291
+
252
292
  def check_baseline(configuration, result)
253
293
  path = configuration.baseline_path
254
294
  return [] if path.nil? || !File.exist?(path)
@@ -287,38 +327,45 @@ module Rigor
287
327
  parts.join(", ")
288
328
  end
289
329
 
290
- def check_rails_plugins(configuration)
291
- return [] unless rails_unconfigured?(configuration)
292
-
293
- [
294
- {
295
- check: CHECK_RAILS,
296
- status: :fail,
297
- message: "Rails detected but no Rails plugins enabled",
298
- hint: "Add Rails plugins (e.g. rigor-activerecord, rigor-actionpack) to " \
299
- "`.rigor.yml` `plugins:` so framework calls resolve."
300
- }
301
- ]
330
+ # ADR-96 WD2 — the plugin-gap advisory, read off each bundled plugin's `target_gems:` rather than the
331
+ # Rails-only tables it replaces. Per plugin, so a Rails app that enabled only `rigor-activerecord` now
332
+ # hears about the other seven; `:warn`, because declining a plugin is a legitimate choice and must not
333
+ # fail the command forever. The one `:fail` is today's behaviour generalised: the project depends on
334
+ # gems Rigor models and enables none of the plugins that model them, which is genuinely unconfigured.
335
+ # Both branches read the lockfile's `DEPENDENCIES` section, never its resolved graph: a transitive
336
+ # `minitest` or `i18n` rides along in nearly every Rails lock, and failing `doctor` on one would fail a
337
+ # correctly configured project.
338
+ def check_plugin_gap(configuration)
339
+ return [] if Configuration.discover.nil?
340
+
341
+ gaps = PluginGapAdvisory.gaps(project_root: Dir.pwd, plugins: configuration.plugins)
342
+ return [] if gaps.empty?
343
+
344
+ if PluginGapAdvisory.unconfigured?(project_root: Dir.pwd, plugins: configuration.plugins)
345
+ [unconfigured_plugin_finding(gaps)]
346
+ else
347
+ gaps.map { |gap| plugin_gap_finding(gap) }
348
+ end
302
349
  end
303
350
 
304
- # True when Rails is in Gemfile.lock but the config enables no Rails plugin — the same probe {ProjectStateProbe}
305
- # uses.
306
- def rails_unconfigured?(_configuration)
307
- config_path = Configuration.discover
308
- return false if config_path.nil?
309
- return false unless File.file?(config_path)
310
-
311
- lock = File.join(Dir.pwd, "Gemfile.lock")
312
- return false unless File.file?(lock) && file_mentions_any?(lock, RAILS_LOCK_MARKERS)
313
-
314
- !file_mentions_any?(config_path, RAILS_PLUGIN_MARKERS)
351
+ def unconfigured_plugin_finding(gaps)
352
+ {
353
+ check: CHECK_PLUGIN_GAP,
354
+ status: :fail,
355
+ message: "#{gaps.size} bundled plugin(s) model gems this project depends on and none is enabled " \
356
+ "(#{gaps.map(&:plugin_gem).join(', ')})",
357
+ hint: "Add the plugins for your stack to `.rigor.yml` `plugins:` so framework calls resolve."
358
+ }
315
359
  end
316
360
 
317
- def file_mentions_any?(path, markers)
318
- content = File.read(path)
319
- markers.any? { |marker| content.include?(marker) }
320
- rescue StandardError
321
- false
361
+ def plugin_gap_finding(gap)
362
+ {
363
+ check: CHECK_PLUGIN_GAP,
364
+ status: :warn,
365
+ message: "#{gap.locked_gems.join(', ')} #{gap.locked_gems.one? ? 'is a dependency' : 'are dependencies'} " \
366
+ "of this project and #{gap.plugin_gem} models it, but it is not enabled",
367
+ hint: "Add `#{gap.plugin_gem}` to `.rigor.yml` `plugins:`, or leave it out deliberately."
368
+ }
322
369
  end
323
370
 
324
371
  # Rigor is a tool, not a library (ADR-27): resolving it as one of the project's own dependencies pins the
@@ -41,7 +41,7 @@ module Rigor
41
41
  class EffectsCommand < Command
42
42
  USAGE = "Usage: rigor effects [options] [paths]"
43
43
 
44
- # @return [Integer] CLI exit status: 0 on success, 1 on `check` drift, 64 on a usage error.
44
+ # @return CLI exit status: 0 on success, 1 on `check` drift, 64 on a usage error.
45
45
  def run
46
46
  verb = @argv.first
47
47
  return run_verb(verb) if EffectsSnapshotCommand::VERBS.include?(verb)
@@ -231,7 +231,7 @@ module Rigor
231
231
  # project does — still analyses it. Inside a project the argument is already under `paths:` and the
232
232
  # union is the configured set unchanged.
233
233
  #
234
- # @return [Array(Rigor::Effects::EffectTable, Hash{String=>Array<String>})] the table, and which
234
+ # @return the table, and which
235
235
  # file each unit was defined in — the map {EffectsReport} needs to answer a path argument.
236
236
  def analyze(configuration, scope)
237
237
  require_relative "../analysis/runner"
@@ -50,12 +50,12 @@ module Rigor
50
50
  REGENERATION_CLOSING_LINE = "Run `rigor effects update` to regenerate the record under the current " \
51
51
  "rules."
52
52
 
53
- # @param sources [Hash{String=>Array<String>}] `Runner#effect_sources` — where each unit is defined.
53
+ # @param sources — `Runner#effect_sources` — where each unit is defined.
54
54
  # A drift row names `file:line` so a reviewer does not have to search for the method (#435). The
55
55
  # file rides the cached summary entry and is free; the line is resolved by parsing the row's own
56
56
  # file ({Effects::DefinitionLines}), which is why a fresh report — no rows — still parses nothing
57
57
  # and the whole-project parse ADR-104 removed from this command stays removed.
58
- # @param lines [Effects::DefinitionLines] seam for the specs; the default parses on demand.
58
+ # @param lines — seam for the specs; the default parses on demand.
59
59
  def initialize(out:, path:, sources: nil, lines: Effects::DefinitionLines.new)
60
60
  @out = out
61
61
  @path = path
@@ -121,7 +121,7 @@ module Rigor
121
121
  # method that no longer exists was not defined by it. Such a row renders exactly as it did before
122
122
  # this suffix existed.
123
123
  #
124
- # @return [Array<Array(String, Integer)>] `[relative path, line]` per file, the line nil when that
124
+ # @return `[relative path, line]` per file, the line nil when that
125
125
  # file spells the key with no `def` — an accessor, or a definition the nesting cannot name.
126
126
  def sources_of(symbol)
127
127
  Array(@sources[symbol]).map do |path|
@@ -51,7 +51,7 @@ module Rigor
51
51
  @verb = verb
52
52
  end
53
53
 
54
- # @return [Integer] 0 fresh / written, 1 on drift under `check`, 64 on a usage error.
54
+ # @return 0 fresh / written, 1 on drift under `check`, 64 on a usage error.
55
55
  def run
56
56
  options = parse_options
57
57
  return usage_error("unsupported format: #{options.fetch(:format)}") unless
@@ -4,6 +4,7 @@ require "json"
4
4
  require "optionparser"
5
5
 
6
6
  require_relative "../analysis/rule_catalog"
7
+ require_relative "../sig_gen/skip_reason_catalog"
7
8
  require_relative "command"
8
9
 
9
10
  module Rigor
@@ -18,7 +19,7 @@ module Rigor
18
19
  class ExplainCommand < Command
19
20
  USAGE = "Usage: rigor explain [options] [<rule>]"
20
21
 
21
- # @return [Integer] CLI exit status.
22
+ # @return CLI exit status.
22
23
  def run
23
24
  options = parse_options
24
25
 
@@ -28,6 +29,12 @@ module Rigor
28
29
  end
29
30
 
30
31
  token = @argv.shift
32
+ skip_reason = SigGen::SkipReasonCatalog.resolve(token)
33
+ if skip_reason
34
+ render_skip_reason(skip_reason, options.fetch(:format))
35
+ return 0
36
+ end
37
+
31
38
  entries = Analysis::RuleCatalog.resolve(token)
32
39
  if entries.empty?
33
40
  @err.puts("Unknown rule: #{token}")
@@ -68,6 +75,32 @@ module Rigor
68
75
  @out.puts("")
69
76
  @out.puts("Run `rigor explain <rule>` for the full description.")
70
77
  @out.puts("Family wildcards (`call`, `flow`, `assert`, `dump`, `def`) print every rule under that prefix.")
78
+ @out.puts("")
79
+ @out.puts("`rigor sig-gen` skip reasons:")
80
+ @out.puts("")
81
+ SigGen::SkipReasonCatalog.all.each do |entry|
82
+ @out.puts(" #{entry.id.ljust(33)} #{entry.summary}")
83
+ end
84
+ end
85
+
86
+ def render_skip_reason(entry, format)
87
+ if format == "json"
88
+ @out.puts(JSON.pretty_generate(entry.to_h))
89
+ return
90
+ end
91
+
92
+ @out.puts(entry.id)
93
+ @out.puts("=" * entry.id.length)
94
+ @out.puts("")
95
+ @out.puts(entry.summary)
96
+ @out.puts("")
97
+ @out.puts("A `rigor sig-gen` skip reason, not a diagnostic rule: it reports a signature that was NOT")
98
+ @out.puts("written, so it has no severity and nothing to suppress.")
99
+ @out.puts("")
100
+ @out.puts(entry.explanation)
101
+ @out.puts("")
102
+ @out.puts("What to do:")
103
+ @out.puts(" #{entry.next_step}")
71
104
  end
72
105
 
73
106
  def render_entries(entries, format)
@@ -12,7 +12,13 @@ module Rigor
12
12
  # Framing (ADR-63 / ADR-62 Criterion A, extended): the payload is the **attribution** — which protection axis is
13
13
  # missing — never raw survival. An unprotected site is "add protection here", never "your code is broken".
14
14
  # `harness_errors` (#264) — see {Rigor::CLI::FileEffectiveness}; defaults to 0 for the same reason.
15
- FusedFileProtection = Data.define(:path, :type_killed, :test_killed, :unprotected, :ratio, :harness_errors) do
15
+ # A `class ... < Data.define(...)` body, not the `Data.define(...) do ... end` shorthand: Steep
16
+ # resolves a `def` inside the shorthand block against the *lexically enclosing* class (here,
17
+ # `Rigor::CLI`, which declares its own differently-shaped `initialize`) rather than the anonymous
18
+ # Data subclass being defined — a `sig/` declaration for `FusedFileProtection` cannot fix this,
19
+ # since the misattribution happens before any signature is consulted. The explicit `class` keyword
20
+ # is a real namespace boundary Steep's block walker respects; behaviour is identical either way.
21
+ class FusedFileProtection < Data.define(:path, :type_killed, :test_killed, :unprotected, :ratio, :harness_errors)
16
22
  def initialize(path:, type_killed:, test_killed:, unprotected:, ratio:, harness_errors: 0)
17
23
  super
18
24
  end
@@ -15,7 +15,7 @@ module Rigor
15
15
  class LspCommand < Command
16
16
  USAGE = "Usage: rigor lsp [options]"
17
17
 
18
- # @return [Integer] CLI exit status.
18
+ # @return CLI exit status.
19
19
  def run
20
20
  options = parse_options
21
21
  return CLI::EXIT_USAGE if options == :usage_error
@@ -17,7 +17,7 @@ module Rigor
17
17
  class McpCommand < Command
18
18
  USAGE = "Usage: rigor mcp [options]"
19
19
 
20
- # @return [Integer] CLI exit status.
20
+ # @return CLI exit status.
21
21
  def run
22
22
  options = parse_options
23
23
  return CLI::EXIT_USAGE if options == :usage_error
@@ -20,15 +20,14 @@ module Rigor
20
20
  module MeasurementIntegrityWarning
21
21
  module_function
22
22
 
23
- # @param report [MutationProtectionReport, FusedProtectionReport] both expose the two counts.
24
- # @param err [IO] the stream to warn on.
25
- # @param floor [Integer] {CoverageMutation::HARNESS_ERROR_WARN_FLOOR}.
26
- # @return [void]
23
+ # @param report — both expose the two counts.
24
+ # @param err — the stream to warn on.
25
+ # @param floor — {CoverageMutation::HARNESS_ERROR_WARN_FLOOR}.
27
26
  def emit(report, err:, floor:)
28
27
  lines_for(report, floor: floor).each { |line| err.puts(line) }
29
28
  end
30
29
 
31
- # @return [Array<String>] the warnings this report earns, widest consequence first.
30
+ # @return the warnings this report earns, widest consequence first.
32
31
  def lines_for(report, floor:)
33
32
  lines = []
34
33
  unmeasured = report.respond_to?(:unmeasured_files) ? report.unmeasured_files : 0 # see CoverageCommand
@@ -32,12 +32,12 @@ module Rigor
32
32
  # reliably marshalable, and the accumulator only needs the count).
33
33
  ParseError = Data.define(:count)
34
34
 
35
- # @param paths [Array<String>] the files to measure, in caller order.
36
- # @param scanner [Protection::MutationScanner] built on the parent; COW-inherited by workers.
37
- # @param environment [Rigor::Environment] the scanner's environment, prewarmed here before forking.
38
- # @param configuration [Rigor::Configuration] for the Prism `target_ruby` version.
39
- # @param workers [Integer] resolved worker count (≤1 → sequential).
40
- # @return [Hash{String => Protection::MutationScanner::FileResult, ParseError}] one entry per path.
35
+ # @param paths — the files to measure, in caller order.
36
+ # @param scanner — built on the parent; COW-inherited by workers.
37
+ # @param environment — the scanner's environment, prewarmed here before forking.
38
+ # @param configuration — for the Prism `target_ruby` version.
39
+ # @param workers — resolved worker count (≤1 → sequential).
40
+ # @return one entry per path.
41
41
  def run(paths:, scanner:, environment:, configuration:, workers:)
42
42
  # Force the full RBS load on the parent so children copy-on-write inherit a warm environment rather
43
43
  # than each rebuilding it after the fork. A no-op on the sequential path but cheap.
@@ -14,7 +14,13 @@ module Rigor
14
14
  # `harness_errors` (#264) — mutants where the harness itself raised (a rescued failure), distinguished from
15
15
  # a parse-invalid mutant. Defaults to 0 so existing call sites keep constructing a `FileEffectiveness`
16
16
  # without the new keyword.
17
- FileEffectiveness = Data.define(:path, :killed, :survived, :ratio, :harness_errors) do
17
+ # A `class ... < Data.define(...)` body, not the `Data.define(...) do ... end` shorthand: Steep
18
+ # resolves a `def` inside the shorthand block against the *lexically enclosing* class (here,
19
+ # `Rigor::CLI`, which declares its own differently-shaped `initialize`) rather than the anonymous
20
+ # Data subclass being defined — a `sig/` declaration for `FileEffectiveness` cannot fix this,
21
+ # since the misattribution happens before any signature is consulted. The explicit `class` keyword
22
+ # is a real namespace boundary Steep's block walker respects; behaviour is identical either way.
23
+ class FileEffectiveness < Data.define(:path, :killed, :survived, :ratio, :harness_errors)
18
24
  def initialize(path:, killed:, survived:, ratio:, harness_errors: 0)
19
25
  super
20
26
  end
@@ -64,7 +64,7 @@ module Rigor
64
64
  PLUGINS_ROOT = File.join(GEM_ROOT, "plugins")
65
65
  EXAMPLES_ROOT = File.join(GEM_ROOT, "examples")
66
66
 
67
- # @return [Integer] CLI exit status.
67
+ # @return CLI exit status.
68
68
  def run
69
69
  subcommand = @argv.shift || "list"
70
70
 
@@ -62,7 +62,7 @@ module Rigor
62
62
  class PluginsCommand < Command # rubocop:disable Metrics/ClassLength
63
63
  USAGE = "Usage: rigor plugins [options]"
64
64
 
65
- # @return [Integer] CLI exit status.
65
+ # @return CLI exit status.
66
66
  def run
67
67
  options = parse_options
68
68
  config_path = options.fetch(:config) || Configuration.discover
@@ -143,6 +143,7 @@ module Rigor
143
143
  required_by = loaded_ids & Plugin::Inflector::CONSUMER_PLUGIN_IDS
144
144
  return nil if required_by.empty?
145
145
 
146
+ Plugin::Isolation.configured_strategy = configuration.plugins_isolation
146
147
  Plugin::Isolation.target_bundle_root ||= Environment::BundleSigDiscovery.resolve_bundle_path(
147
148
  bundle_path: configuration.bundler_bundle_path,
148
149
  project_root: Dir.pwd,
@@ -35,8 +35,8 @@ module Rigor
35
35
 
36
36
  VARIABLE_TOKENS = %i[INSTANCE_VARIABLE CLASS_VARIABLE GLOBAL_VARIABLE].freeze
37
37
 
38
- # @param source [String] Ruby source.
39
- # @return [String] the source with ANSI colour escapes, or
38
+ # @param source — Ruby source.
39
+ # @return the source with ANSI colour escapes, or
40
40
  # the input unchanged when lexing surfaces an error.
41
41
  def colorize(source)
42
42
  # Sources read under a POSIX locale arrive tagged US-ASCII even when they carry UTF-8 bytes; retag so the token
@@ -23,12 +23,12 @@ module Rigor
23
23
  # reliably marshalable, and the accumulator only needs the count).
24
24
  ParseError = Data.define(:count)
25
25
 
26
- # @param paths [Array<String>] the files to scan, in caller order.
27
- # @param scanner [Inference::ProtectionScanner] built on the parent; COW-inherited by workers.
28
- # @param environment [Rigor::Environment] the scanner's environment, prewarmed here before forking.
29
- # @param configuration [Rigor::Configuration] for the Prism `target_ruby` version.
30
- # @param workers [Integer] resolved worker count (≤1 → sequential).
31
- # @return [Hash{String => Inference::ProtectionScanner::FileResult, ParseError}] one entry per path.
26
+ # @param paths — the files to scan, in caller order.
27
+ # @param scanner — built on the parent; COW-inherited by workers.
28
+ # @param environment — the scanner's environment, prewarmed here before forking.
29
+ # @param configuration — for the Prism `target_ruby` version.
30
+ # @param workers — resolved worker count (≤1 → sequential).
31
+ # @return one entry per path.
32
32
  def run(paths:, scanner:, environment:, configuration:, workers:)
33
33
  # Force the full RBS load on the parent so children copy-on-write inherit a warm environment rather
34
34
  # than each rebuilding it after the fork (mirrors the check fork pool's parent-side prewarm). A
@@ -21,7 +21,7 @@ module Rigor
21
21
  class ShowBleedingedgeCommand < Command
22
22
  USAGE = "Usage: rigor show-bleedingedge [options]"
23
23
 
24
- # @return [Integer] CLI exit status.
24
+ # @return CLI exit status.
25
25
  def run
26
26
  options = parse_options
27
27
  configuration = load_configuration(options)
@@ -33,7 +33,13 @@ module Rigor
33
33
  VALID_PARAM_POLICIES = %w[untyped observed observed-strict].freeze
34
34
  VALID_FORMATS = %w[text json].freeze
35
35
 
36
- # @return [Integer] CLI exit status.
36
+ # The skip reasons {#report_skipped} counts. The two left out each have a detailed report of their own
37
+ # ({#report_unrenderable}, {#report_unresolvable_superclasses}), so a method never shows up in two tallies.
38
+ SUMMARISED_SKIP_REASONS = (SigGen::Classification::SKIP_DIAGNOSTIC_IDS.keys -
39
+ %i[unrenderable_rbs unresolvable_superclass]).freeze
40
+ private_constant :SUMMARISED_SKIP_REASONS
41
+
42
+ # @return CLI exit status.
37
43
  def run
38
44
  options = parse_options
39
45
  return CLI::EXIT_USAGE if options.nil?
@@ -54,6 +60,7 @@ module Rigor
54
60
  dispatch_print_or_diff(candidates, mode, options)
55
61
  0
56
62
  end
63
+ report_skipped(candidates, options)
57
64
  report_unrenderable(generator.unrenderable)
58
65
  report_unresolvable_superclasses(generator.unresolvable_superclasses)
59
66
  status
@@ -61,6 +68,28 @@ module Rigor
61
68
 
62
69
  private
63
70
 
71
+ # Issue #778 — one stderr line per run saying how many methods the generator declined and why, so a
72
+ # method missing from the output is never a silent absence. Text mode only: under `--format=json` every
73
+ # skipped row is already in the payload with its `skip_reason`, and stderr stays clean for the consumer.
74
+ # Per-method lines would be noise at project scale; the JSON payload is where each one is named.
75
+ def report_skipped(candidates, options)
76
+ return unless options.fetch(:format) == "text"
77
+
78
+ counts = candidates.each_with_object(Hash.new(0)) do |candidate, acc|
79
+ next unless candidate.classification == SigGen::Classification::SKIPPED
80
+ next unless SUMMARISED_SKIP_REASONS.include?(candidate.skip_reason)
81
+
82
+ acc[candidate.skip_reason] += 1
83
+ end
84
+ return if counts.empty?
85
+
86
+ breakdown = counts.map { |reason, n| "#{SigGen::Classification::SKIP_DIAGNOSTIC_IDS.fetch(reason)}: #{n}" }
87
+ @err.puts(
88
+ "rigor sig-gen: skipped #{counts.values.sum} method(s) it could not type or would not overwrite " \
89
+ "(#{breakdown.join(', ')}). Run with --format=json to see each one with its skip_reason."
90
+ )
91
+ end
92
+
64
93
  # A method whose rendered RBS does not parse is a Rigor rendering defect, not a fact about the user's
65
94
  # code — the generator skipped it (so the rest of the signatures are still usable and still valid), but
66
95
  # staying silent would leave the user with a quietly incomplete `sig/` and us with an unreported bug.
@@ -106,7 +135,7 @@ module Rigor
106
135
  )
107
136
  end
108
137
 
109
- # @return [Integer] exit status — non-zero when a file the user asked to write could not be written.
138
+ # @return exit status — non-zero when a file the user asked to write could not be written.
110
139
  def dispatch_write(candidates, configuration, options)
111
140
  layout_index = SigGen::LayoutIndex.new(signature_paths: configuration.signature_paths)
112
141
  path_mapper = SigGen::PathMapper.new(configuration: configuration, layout_index: layout_index)
@@ -76,7 +76,7 @@ module Rigor
76
76
  # up.
77
77
  SKILLS_ROOT = File.expand_path("../../../skills", __dir__)
78
78
 
79
- # @return [Integer] CLI exit status.
79
+ # @return CLI exit status.
80
80
  def run
81
81
  case @argv.first
82
82
  when nil
@@ -37,22 +37,22 @@ module Rigor
37
37
  # that could not run is NOT a clean check and the report must never let it read as one.
38
38
  # - `route` / `reason` — the headline override, or nil to leave the presence-only recommendation standing.
39
39
  Report = Data.define(:status, :detail, :route, :reason) do
40
- # @return [Boolean] true when the check did not complete — the caller must then say so rather than let the
40
+ # @return true when the check did not complete — the caller must then say so rather than let the
41
41
  # fallback recommendation read as an all-clear.
42
42
  def failed?
43
43
  status == :error
44
44
  end
45
45
  end
46
46
 
47
- # @param config [String, nil] the config filename the presence probe found, relative to `root`.
48
- # @param root [String] project root (the analysis itself resolves paths against the process cwd, as
47
+ # @param config — the config filename the presence probe found, relative to `root`.
48
+ # @param root — project root (the analysis itself resolves paths against the process cwd, as
49
49
  # `rigor check` does).
50
50
  def initialize(config:, root: Dir.pwd)
51
51
  @config = config
52
52
  @root = root
53
53
  end
54
54
 
55
- # @return [Report] never raises — every failure degrades to a `:error`/`:skipped` report whose `route` is nil,
55
+ # @return never raises — every failure degrades to a `:error`/`:skipped` report whose `route` is nil,
56
56
  # leaving the presence-only headline in charge.
57
57
  def run
58
58
  if @config.nil?