rigortype 0.4.5 → 0.4.7

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 (452) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/data/gem_overlay/activesupport/core_ext.rbs +16 -13
  4. data/data/vendored_gem_sigs/bundler/bundler.rbs +1 -1
  5. data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +4 -3
  6. data/docs/handbook/01-getting-started.md +5 -2
  7. data/docs/handbook/11-sig-gen.md +61 -5
  8. data/docs/handbook/appendix-phpstan.md +4 -4
  9. data/docs/manual/02-cli-reference.md +192 -27
  10. data/docs/manual/03-configuration.md +38 -4
  11. data/docs/manual/04-diagnostics.md +1 -1
  12. data/docs/manual/06-baseline.md +6 -4
  13. data/docs/manual/09-editor-integration.md +13 -2
  14. data/docs/manual/10-mcp-server.md +47 -3
  15. data/docs/manual/12-caching.md +9 -1
  16. data/docs/manual/plugins/rigor-actionmailer.md +30 -0
  17. data/docs/manual/plugins/rigor-actionpack.md +49 -11
  18. data/docs/manual/plugins/rigor-activesupport-core-ext.md +20 -1
  19. data/docs/manual/plugins/rigor-devise.md +15 -0
  20. data/docs/manual/plugins/rigor-rspec.md +2 -1
  21. data/lib/rigor/analysis/baseline.rb +24 -27
  22. data/lib/rigor/analysis/buffer_binding.rb +14 -0
  23. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +5 -6
  24. data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +2 -2
  25. data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +4 -4
  26. data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +2 -2
  27. data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +105 -12
  28. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +143 -15
  29. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +4 -1
  30. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -8
  31. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +1 -1
  32. data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +66 -3
  33. data/lib/rigor/analysis/check_rules/rebinding_block_callees/dispatch.rb +112 -0
  34. data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +394 -0
  35. data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
  36. data/lib/rigor/analysis/check_rules/ruby_deprecations.rb +7 -0
  37. data/lib/rigor/analysis/check_rules/rule_ids.rb +1 -1
  38. data/lib/rigor/analysis/check_rules/rule_walk.rb +9 -7
  39. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +3 -4
  40. data/lib/rigor/analysis/check_rules/special_global_setters.rb +3 -3
  41. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +53 -2
  42. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +1 -1
  43. data/lib/rigor/analysis/check_rules.rb +217 -52
  44. data/lib/rigor/analysis/crash_signature.rb +2 -2
  45. data/lib/rigor/analysis/dependency_recorder.rb +15 -4
  46. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +14 -3
  47. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +1 -1
  48. data/lib/rigor/analysis/dependency_source_inference/index.rb +6 -8
  49. data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
  50. data/lib/rigor/analysis/edit_replay.rb +154 -0
  51. data/lib/rigor/analysis/fact_store.rb +3 -3
  52. data/lib/rigor/analysis/incremental.rb +37 -0
  53. data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +2 -2
  54. data/lib/rigor/analysis/incremental_run_slot.rb +2 -2
  55. data/lib/rigor/analysis/incremental_session.rb +202 -43
  56. data/lib/rigor/analysis/path_expansion.rb +41 -0
  57. data/lib/rigor/analysis/require_relative_closure.rb +100 -0
  58. data/lib/rigor/analysis/rule_catalog.rb +7 -3
  59. data/lib/rigor/analysis/run_disclosure_credit.rb +80 -0
  60. data/lib/rigor/analysis/run_disclosure_ledger.rb +155 -0
  61. data/lib/rigor/analysis/run_stats.rb +2 -2
  62. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +8 -4
  63. data/lib/rigor/analysis/runner/declaration_position.rb +1 -1
  64. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +204 -96
  65. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +1 -1
  66. data/lib/rigor/analysis/runner/pool_coordinator.rb +157 -320
  67. data/lib/rigor/analysis/runner/project_pre_passes.rb +48 -6
  68. data/lib/rigor/analysis/runner/run_level_facts.rb +304 -0
  69. data/lib/rigor/analysis/runner/run_snapshots.rb +44 -54
  70. data/lib/rigor/analysis/runner/scan_environment.rb +51 -0
  71. data/lib/rigor/analysis/runner/worker_payload.rb +36 -0
  72. data/lib/rigor/analysis/runner.rb +233 -120
  73. data/lib/rigor/analysis/self_call_resolution_recorder.rb +1 -1
  74. data/lib/rigor/analysis/template_unit_positions.rb +2 -3
  75. data/lib/rigor/analysis/template_units.rb +9 -1
  76. data/lib/rigor/analysis/worker_session.rb +38 -33
  77. data/lib/rigor/bleeding_edge.rb +3 -6
  78. data/lib/rigor/builtins/imported_refinements.rb +6 -8
  79. data/lib/rigor/builtins/predefined_constant_refinements.rb +1 -1
  80. data/lib/rigor/builtins/regex_refinement.rb +4 -6
  81. data/lib/rigor/cache/descriptor.rb +6 -5
  82. data/lib/rigor/cache/incremental_snapshot.rb +60 -18
  83. data/lib/rigor/cache/rbs_cache_producer.rb +2 -2
  84. data/lib/rigor/cache/rbs_class_ancestor_table.rb +1 -1
  85. data/lib/rigor/cache/rbs_class_type_param_names.rb +2 -2
  86. data/lib/rigor/cache/rbs_constant_table.rb +1 -1
  87. data/lib/rigor/cache/rbs_descriptor.rb +54 -3
  88. data/lib/rigor/cache/rbs_environment.rb +4 -1
  89. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +2 -2
  90. data/lib/rigor/cache/rbs_known_class_names.rb +1 -1
  91. data/lib/rigor/cache/store.rb +11 -10
  92. data/lib/rigor/ci_detector.rb +1 -1
  93. data/lib/rigor/cli/annotate_command.rb +24 -19
  94. data/lib/rigor/cli/baseline_command.rb +145 -55
  95. data/lib/rigor/cli/catalogue.rb +256 -0
  96. data/lib/rigor/cli/check_command.rb +188 -93
  97. data/lib/rigor/cli/check_runner_factory.rb +2 -2
  98. data/lib/rigor/cli/command.rb +15 -1
  99. data/lib/rigor/cli/coverage_command.rb +21 -8
  100. data/lib/rigor/cli/coverage_mutation.rb +5 -7
  101. data/lib/rigor/cli/coverage_renderer.rb +2 -1
  102. data/lib/rigor/cli/coverage_scan.rb +3 -5
  103. data/lib/rigor/cli/diff_command.rb +27 -21
  104. data/lib/rigor/cli/docs_command.rb +1 -2
  105. data/lib/rigor/cli/doctor_command.rb +12 -12
  106. data/lib/rigor/cli/effects_command.rb +50 -40
  107. data/lib/rigor/cli/effects_diff_renderer.rb +18 -17
  108. data/lib/rigor/cli/effects_explain_renderer.rb +13 -12
  109. data/lib/rigor/cli/effects_renderer.rb +2 -1
  110. data/lib/rigor/cli/effects_snapshot_command.rb +18 -17
  111. data/lib/rigor/cli/explain_command.rb +18 -8
  112. data/lib/rigor/cli/fused_protection_renderer.rb +2 -1
  113. data/lib/rigor/cli/json_document.rb +41 -0
  114. data/lib/rigor/cli/lens_command.rb +17 -13
  115. data/lib/rigor/cli/lsp_command.rb +250 -23
  116. data/lib/rigor/cli/mcp_command.rb +14 -8
  117. data/lib/rigor/cli/mutation_fork_scan.rb +1 -1
  118. data/lib/rigor/cli/mutation_protection_renderer.rb +3 -2
  119. data/lib/rigor/cli/options.rb +60 -4
  120. data/lib/rigor/cli/plugin_command.rb +93 -46
  121. data/lib/rigor/cli/plugins_command.rb +22 -18
  122. data/lib/rigor/cli/plugins_renderer.rb +34 -8
  123. data/lib/rigor/cli/protection_fork_scan.rb +2 -2
  124. data/lib/rigor/cli/protection_renderer.rb +2 -1
  125. data/lib/rigor/cli/show_bleedingedge_command.rb +23 -16
  126. data/lib/rigor/cli/sig_gen_command.rb +130 -34
  127. data/lib/rigor/cli/skill_command.rb +32 -3
  128. data/lib/rigor/cli/skill_deep_probe.rb +1 -1
  129. data/lib/rigor/cli/skill_describe.rb +1 -1
  130. data/lib/rigor/cli/trace_command.rb +14 -9
  131. data/lib/rigor/cli/triage_command.rb +10 -11
  132. data/lib/rigor/cli/triage_renderer.rb +2 -1
  133. data/lib/rigor/cli/type_of_command.rb +18 -12
  134. data/lib/rigor/cli/type_of_project_scope.rb +24 -0
  135. data/lib/rigor/cli/type_of_renderer.rb +4 -1
  136. data/lib/rigor/cli/type_scan_command.rb +14 -9
  137. data/lib/rigor/cli/type_scan_renderer.rb +2 -1
  138. data/lib/rigor/cli/unused_command.rb +27 -17
  139. data/lib/rigor/cli/upgrade_command.rb +5 -0
  140. data/lib/rigor/cli.rb +209 -54
  141. data/lib/rigor/config_audit.rb +17 -6
  142. data/lib/rigor/configuration/severity_profile.rb +5 -5
  143. data/lib/rigor/configuration.rb +86 -34
  144. data/lib/rigor/effects/definition_context.rb +0 -2
  145. data/lib/rigor/effects/envelope.rb +1 -1
  146. data/lib/rigor/effects/framework_units.rb +1 -1
  147. data/lib/rigor/effects/mutation_classifier.rb +4 -7
  148. data/lib/rigor/effects/propagator.rb +1 -1
  149. data/lib/rigor/effects/registry.rb +2 -2
  150. data/lib/rigor/effects/snapshot.rb +1 -1
  151. data/lib/rigor/effects/taint_cause.rb +0 -2
  152. data/lib/rigor/effects/unit_scan.rb +9 -13
  153. data/lib/rigor/environment/bundle_sig_discovery.rb +5 -9
  154. data/lib/rigor/environment/class_registry.rb +3 -3
  155. data/lib/rigor/environment/core_root.rb +77 -0
  156. data/lib/rigor/environment/default_libraries.rb +1 -1
  157. data/lib/rigor/environment/deferred_signature_plan.rb +283 -0
  158. data/lib/rigor/environment/gated_signature_guard.rb +1 -1
  159. data/lib/rigor/environment/gated_signature_plan.rb +5 -2
  160. data/lib/rigor/environment/lockfile_resolver.rb +28 -12
  161. data/lib/rigor/environment/member_consistency/comparator.rb +18 -1
  162. data/lib/rigor/environment/member_consistency.rb +56 -7
  163. data/lib/rigor/environment/missing_gem_constant_index.rb +3 -2
  164. data/lib/rigor/environment/project_path_gems.rb +124 -0
  165. data/lib/rigor/environment/rbs_hierarchy.rb +6 -15
  166. data/lib/rigor/environment/rbs_loader.rb +592 -70
  167. data/lib/rigor/environment/reflection.rb +7 -7
  168. data/lib/rigor/environment/required_features.rb +136 -32
  169. data/lib/rigor/environment/ruby_library_roots.rb +122 -0
  170. data/lib/rigor/environment/vendored_signature_plan.rb +153 -0
  171. data/lib/rigor/environment.rb +44 -11
  172. data/lib/rigor/flow_contribution/merger.rb +4 -4
  173. data/lib/rigor/inference/acceptance.rb +93 -32
  174. data/lib/rigor/inference/block_parameter_binder.rb +2 -3
  175. data/lib/rigor/inference/block_run_count.rb +178 -0
  176. data/lib/rigor/inference/budget_trace.rb +8 -2
  177. data/lib/rigor/inference/builtins/exception_catalog.rb +2 -2
  178. data/lib/rigor/inference/builtins/numeric_catalog.rb +2 -5
  179. data/lib/rigor/inference/builtins/random_catalog.rb +2 -2
  180. data/lib/rigor/inference/callee_mutation_chain.rb +126 -0
  181. data/lib/rigor/inference/callee_write_census.rb +815 -0
  182. data/lib/rigor/inference/construction_escape.rb +217 -0
  183. data/lib/rigor/inference/declaration_walk/traversal.rb +1 -1
  184. data/lib/rigor/inference/deep_erasure.rb +170 -0
  185. data/lib/rigor/inference/def_handle.rb +1 -1
  186. data/lib/rigor/inference/def_node_resolver.rb +1 -1
  187. data/lib/rigor/inference/def_return_typer.rb +204 -12
  188. data/lib/rigor/inference/definer_resolution.rb +21 -5
  189. data/lib/rigor/inference/definers_index.rb +106 -0
  190. data/lib/rigor/inference/direct_ivar_writes.rb +300 -0
  191. data/lib/rigor/inference/element_read_widening.rb +49 -3
  192. data/lib/rigor/inference/error_info.rb +17 -3
  193. data/lib/rigor/inference/expression_typer.rb +173 -57
  194. data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
  195. data/lib/rigor/inference/global_write_census.rb +110 -1
  196. data/lib/rigor/inference/global_writer_reach.rb +260 -0
  197. data/lib/rigor/inference/hkt_body.rb +1 -2
  198. data/lib/rigor/inference/hkt_body_parser.rb +3 -5
  199. data/lib/rigor/inference/hkt_reducer.rb +2 -2
  200. data/lib/rigor/inference/hkt_registry.rb +3 -5
  201. data/lib/rigor/inference/indexed_narrowing.rb +4 -7
  202. data/lib/rigor/inference/intervening_ivar_reset.rb +26 -4
  203. data/lib/rigor/inference/ivar_writer_census.rb +246 -0
  204. data/lib/rigor/inference/key_presence_guard.rb +2 -1
  205. data/lib/rigor/inference/loop_value_record.rb +57 -0
  206. data/lib/rigor/inference/match_rebinding/frame.rb +65 -0
  207. data/lib/rigor/inference/match_rebinding/yielding_match.rb +45 -0
  208. data/lib/rigor/inference/match_rebinding.rb +11 -1
  209. data/lib/rigor/inference/memo_audit.rb +55 -0
  210. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +1 -1
  211. data/lib/rigor/inference/method_dispatcher/argument_distribution.rb +40 -0
  212. data/lib/rigor/inference/method_dispatcher/block_folding.rb +1 -1
  213. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +113 -12
  214. data/lib/rigor/inference/method_dispatcher/contested_overloads.rb +89 -0
  215. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -1
  216. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -3
  217. data/lib/rigor/inference/method_dispatcher/no_match_fallback.rb +54 -0
  218. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +39 -27
  219. data/lib/rigor/inference/method_dispatcher/random_folding.rb +1 -1
  220. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +203 -17
  221. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +1 -1
  222. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +2 -2
  223. data/lib/rigor/inference/method_dispatcher/self_substitute.rb +2 -2
  224. data/lib/rigor/inference/method_dispatcher/set_folding.rb +59 -24
  225. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +83 -20
  226. data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +0 -1
  227. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +50 -17
  228. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -5
  229. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +4 -6
  230. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  231. data/lib/rigor/inference/method_dispatcher.rb +109 -29
  232. data/lib/rigor/inference/module_function_state/spellings.rb +64 -0
  233. data/lib/rigor/inference/module_function_state.rb +23 -1
  234. data/lib/rigor/inference/multi_target_binder.rb +1 -1
  235. data/lib/rigor/inference/mutation_rejoin.rb +240 -4
  236. data/lib/rigor/inference/mutation_widening.rb +162 -29
  237. data/lib/rigor/inference/narrowing.rb +113 -38
  238. data/lib/rigor/inference/object_mixins.rb +13 -1
  239. data/lib/rigor/inference/optimistic_origin.rb +1 -1
  240. data/lib/rigor/inference/out_buffer_mutation.rb +72 -0
  241. data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
  242. data/lib/rigor/inference/pre_eval_constants.rb +1 -1
  243. data/lib/rigor/inference/project_method_ownership.rb +6 -3
  244. data/lib/rigor/inference/project_seed_tables.rb +2 -1
  245. data/lib/rigor/inference/project_yielder.rb +122 -0
  246. data/lib/rigor/inference/receiver_alias.rb +20 -0
  247. data/lib/rigor/inference/refined_dispatch/constructed_class.rb +3 -1
  248. data/lib/rigor/inference/refined_dispatch/own_method.rb +2 -1
  249. data/lib/rigor/inference/remover_holder_scan.rb +233 -0
  250. data/lib/rigor/inference/remover_narrowing.rb +224 -0
  251. data/lib/rigor/inference/rewrite_mutation.rb +53 -2
  252. data/lib/rigor/inference/scope_indexer/certainty.rb +9 -1
  253. data/lib/rigor/inference/scope_indexer/constant_ivar_value.rb +93 -0
  254. data/lib/rigor/inference/scope_indexer/declared_ivar_types.rb +64 -0
  255. data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +205 -0
  256. data/lib/rigor/inference/scope_indexer/derived_param_value.rb +112 -0
  257. data/lib/rigor/inference/scope_indexer/empty_seed.rb +99 -0
  258. data/lib/rigor/inference/scope_indexer/ivar_contributions.rb +135 -0
  259. data/lib/rigor/inference/scope_indexer/unseen_ivar_writers.rb +133 -0
  260. data/lib/rigor/inference/scope_indexer.rb +614 -146
  261. data/lib/rigor/inference/self_call_ivar_writes.rb +101 -17
  262. data/lib/rigor/inference/set_mutation.rb +189 -0
  263. data/lib/rigor/inference/singleton_hook_decline.rb +1 -2
  264. data/lib/rigor/inference/statement_evaluator.rb +3036 -278
  265. data/lib/rigor/inference/struct_fold_safety.rb +4 -6
  266. data/lib/rigor/inference/synthetic_method_index.rb +23 -0
  267. data/lib/rigor/inference/synthetic_method_scanner/project_members.rb +130 -0
  268. data/lib/rigor/inference/synthetic_method_scanner.rb +73 -14
  269. data/lib/rigor/inference/toplevel_def_census.rb +59 -0
  270. data/lib/rigor/inference/unknown_store_widening.rb +103 -4
  271. data/lib/rigor/inference/unthreaded_rebinds.rb +2 -3
  272. data/lib/rigor/inference/void_tail_summary.rb +2 -1
  273. data/lib/rigor/language_server/buffer_table.rb +9 -3
  274. data/lib/rigor/language_server/completion_provider.rb +26 -20
  275. data/lib/rigor/language_server/config_warning_announcer.rb +28 -0
  276. data/lib/rigor/language_server/diagnostic_publisher.rb +28 -9
  277. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  278. data/lib/rigor/language_server/folding_range_provider.rb +1 -2
  279. data/lib/rigor/language_server/hover_provider.rb +1 -7
  280. data/lib/rigor/language_server/hover_renderer.rb +6 -8
  281. data/lib/rigor/language_server/project_context.rb +98 -4
  282. data/lib/rigor/language_server/publish_batcher.rb +3 -4
  283. data/lib/rigor/language_server/selection_range_provider.rb +2 -4
  284. data/lib/rigor/language_server/server.rb +12 -6
  285. data/lib/rigor/language_server/signature_help_provider.rb +4 -4
  286. data/lib/rigor/lens/plugins.rb +1 -1
  287. data/lib/rigor/mcp/server.rb +82 -3
  288. data/lib/rigor/mcp.rb +1 -1
  289. data/lib/rigor/plugin/access_denied_error.rb +1 -2
  290. data/lib/rigor/plugin/base.rb +26 -11
  291. data/lib/rigor/plugin/bundled_catalog.rb +1 -12
  292. data/lib/rigor/plugin/io_boundary.rb +1 -1
  293. data/lib/rigor/plugin/loader.rb +25 -8
  294. data/lib/rigor/plugin/macro/heredoc_template.rb +7 -8
  295. data/lib/rigor/plugin/macro/trait_registry.rb +6 -9
  296. data/lib/rigor/plugin/manifest.rb +38 -16
  297. data/lib/rigor/plugin/registry.rb +41 -3
  298. data/lib/rigor/plugin/services.rb +2 -3
  299. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -0
  300. data/lib/rigor/plugin/trust_policy.rb +2 -2
  301. data/lib/rigor/plugin/type_node_resolver.rb +2 -4
  302. data/lib/rigor/plugin.rb +6 -8
  303. data/lib/rigor/plugin_gap_advisory.rb +5 -1
  304. data/lib/rigor/project_environment.rb +16 -2
  305. data/lib/rigor/protection/analysis_guard.rb +1 -1
  306. data/lib/rigor/protection/discovery_seed.rb +96 -2
  307. data/lib/rigor/protection/mutator.rb +1 -1
  308. data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
  309. data/lib/rigor/rbs_extended/hkt_directives.rb +6 -10
  310. data/lib/rigor/rbs_extended/reporter.rb +27 -3
  311. data/lib/rigor/rbs_extended.rb +2 -3
  312. data/lib/rigor/reflection/constant_ancestors.rb +5 -3
  313. data/lib/rigor/rooted_glob.rb +25 -2
  314. data/lib/rigor/run_scoped_slots.rb +38 -0
  315. data/lib/rigor/runtime/jit.rb +1 -1
  316. data/lib/rigor/scope/discovery_index.rb +19 -6
  317. data/lib/rigor/scope/resolution_chain.rb +3 -1
  318. data/lib/rigor/scope.rb +379 -36
  319. data/lib/rigor/sig_gen/classification.rb +4 -0
  320. data/lib/rigor/sig_gen/effect_annotation.rb +3 -3
  321. data/lib/rigor/sig_gen/generator.rb +668 -84
  322. data/lib/rigor/sig_gen/generic_header_scan.rb +189 -0
  323. data/lib/rigor/sig_gen/inline_declarations.rb +2 -2
  324. data/lib/rigor/sig_gen/layout_index.rb +3 -3
  325. data/lib/rigor/sig_gen/method_candidate.rb +23 -4
  326. data/lib/rigor/sig_gen/observation_collector.rb +7 -6
  327. data/lib/rigor/sig_gen/observed_call.rb +1 -1
  328. data/lib/rigor/sig_gen/path_mapper.rb +6 -0
  329. data/lib/rigor/sig_gen/rbs_validity.rb +1 -1
  330. data/lib/rigor/sig_gen/renderer.rb +21 -12
  331. data/lib/rigor/sig_gen/skip_reason_catalog.rb +15 -2
  332. data/lib/rigor/sig_gen/type_elaborator.rb +21 -5
  333. data/lib/rigor/sig_gen/type_name_qualification.rb +69 -0
  334. data/lib/rigor/sig_gen/write_reporter.rb +63 -14
  335. data/lib/rigor/sig_gen/write_result.rb +23 -12
  336. data/lib/rigor/sig_gen/writer.rb +217 -37
  337. data/lib/rigor/type/acceptance_router.rb +2 -2
  338. data/lib/rigor/type/accepts_result.rb +1 -1
  339. data/lib/rigor/type/app.rb +4 -6
  340. data/lib/rigor/type/combinator.rb +13 -16
  341. data/lib/rigor/type/constant.rb +48 -7
  342. data/lib/rigor/type/hash_shape.rb +26 -40
  343. data/lib/rigor/type/struct_instance.rb +3 -2
  344. data/lib/rigor/type/union.rb +1 -1
  345. data/lib/rigor/type_node/identifier.rb +1 -2
  346. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  347. data/lib/rigor/type_node/string_literal.rb +1 -2
  348. data/lib/rigor/type_node/symbol_literal.rb +1 -2
  349. data/lib/rigor/type_node/union.rb +3 -6
  350. data/lib/rigor/value_semantics.rb +1 -10
  351. data/lib/rigor/version.rb +1 -1
  352. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +2 -2
  353. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +2 -3
  354. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -1
  355. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +89 -70
  356. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +4 -2
  357. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +25 -4
  358. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +1 -1
  359. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -5
  360. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +0 -1
  361. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +2 -1
  362. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +1 -1
  363. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +79 -19
  364. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
  365. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +6 -3
  366. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +3 -2
  367. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +2 -2
  368. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +21 -31
  369. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +10 -16
  370. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +2 -3
  371. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +0 -1
  372. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +5 -4
  373. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +3 -4
  374. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +1 -1
  375. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +7 -12
  376. data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +1 -1
  377. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +1 -1
  378. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/symbolize_keys.rb +102 -0
  379. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +19 -6
  380. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +26 -13
  381. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +3 -3
  382. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +5 -3
  383. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +1 -3
  384. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +6 -6
  385. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +5 -6
  386. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +7 -6
  387. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/params_shape.rb +2 -3
  388. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -8
  389. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +0 -1
  390. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
  391. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +10 -18
  392. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +0 -1
  393. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +0 -2
  394. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +4 -5
  395. data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +0 -2
  396. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +6 -7
  397. data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +2 -4
  398. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +2 -5
  399. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -2
  400. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +6 -6
  401. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
  402. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +3 -8
  403. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +2 -3
  404. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -8
  405. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -3
  406. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -3
  407. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +1 -1
  408. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +4 -11
  409. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -1
  410. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +2 -1
  411. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +102 -11
  412. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +2 -2
  413. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +3 -4
  414. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +6 -8
  415. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +5 -2
  416. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +4 -5
  417. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +6 -7
  418. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +1 -1
  419. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +2 -2
  420. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +17 -21
  421. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +2 -3
  422. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +3 -2
  423. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +4 -4
  424. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer.rb +1 -1
  425. data/sig/rigor/analysis/baseline.rbs +0 -2
  426. data/sig/rigor/analysis/fact_store.rbs +1 -0
  427. data/sig/rigor/analysis/reachability/scan_cache.rbs +1 -1
  428. data/sig/rigor/cli/diff_command.rbs +1 -1
  429. data/sig/rigor/cli/explain_command.rbs +1 -1
  430. data/sig/rigor/effects/file_collection.rbs +5 -6
  431. data/sig/rigor/effects/registry.rbs +8 -6
  432. data/sig/rigor/effects/summary.rbs +6 -5
  433. data/sig/rigor/environment.rbs +5 -3
  434. data/sig/rigor/inference/optimistic_origin.rbs +11 -0
  435. data/sig/rigor/inference.rbs +10 -1
  436. data/sig/rigor/plugin/additional_initializer.rbs +0 -5
  437. data/sig/rigor/plugin/base.rbs +1 -0
  438. data/sig/rigor/plugin/effect_ancestry.rbs +6 -4
  439. data/sig/rigor/plugin/effect_attribution.rbs +25 -10
  440. data/sig/rigor/plugin/effect_edge.rbs +12 -5
  441. data/sig/rigor/plugin/effect_entry_points.rbs +8 -3
  442. data/sig/rigor/plugin/io_boundary.rbs +6 -5
  443. data/sig/rigor/plugin/manifest.rbs +16 -18
  444. data/sig/rigor/plugin/protocol_contract.rbs +6 -9
  445. data/sig/rigor/plugin/registry.rbs +1 -1
  446. data/sig/rigor/scope.rbs +51 -50
  447. data/sig/rigor/type.rbs +0 -6
  448. data/skills/rigor-plugin-author/SKILL.md +4 -4
  449. data/skills/rigor-plugin-author/references/02-walker-and-types.md +5 -5
  450. data/skills/rigor-plugin-review/SKILL.md +3 -3
  451. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +12 -1
  452. metadata +55 -3
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "../analysis/buffer_binding"
4
+ require_relative "../configuration_error"
4
5
 
5
6
  module Rigor
6
7
  class CLI
@@ -10,37 +11,92 @@ module Rigor
10
11
  # `--instead-of` flag pair and the buffer-binding resolution that validates it. That resolution used to be copied
11
12
  # verbatim into both `Rigor::CLI` and `TypeOfCommand`, so a fix to one (the paired-flag check, the readability
12
13
  # check, the error wording) could silently miss the other. Centralising it keeps the two editor-mode entry points in
13
- # lockstep.
14
+ # lockstep. It also owns the `--config` check (#1992), so every command and the MCP tools that pass `--config`
15
+ # read the same rule.
14
16
  module Options
15
17
  module_function
16
18
 
19
+ # The formats `parser` accepts for `--format`, or nil when {.add_format} did not define the switch. A
20
+ # block-validated switch carries no list of its own, so {.add_format} leaves it on the parser for the CLI
21
+ # catalogue (ADR-126 WD3) to read.
22
+ def accepted_formats(parser)
23
+ parser.instance_variable_get(:@rigor_accepted_formats)
24
+ end
25
+
17
26
  # Defines the standard `--config=PATH` flag on `parser`, writing the path into `options[:config]`. Used by every
18
27
  # subcommand that loads a `.rigor.yml`; the few whose `--config` help text is intentionally bespoke (`diff`,
19
- # `mcp`, `show-bleedingedge`) keep their own `parser.on` rather than this shared wording.
28
+ # `mcp`, `show-bleedingedge`) keep their own `parser.on` and call {.config_path} for the same check.
20
29
  def add_config(parser, options)
21
- parser.on("--config=PATH", "Path to the Rigor configuration file") { |value| options[:config] = value }
30
+ parser.on("--config=PATH", "Path to the Rigor configuration file") do |value|
31
+ options[:config] = config_path(value)
32
+ end
33
+ end
34
+
35
+ # Defines `--format=FORMAT` restricted to `formats`, so an unsupported value is an `OptionParser::InvalidArgument`
36
+ # raised while the options are parsed (#2502). A check left to the renderer ran the whole analysis first, and a
37
+ # renderer that read only `format == "json"` printed text for any other value. The match is exact: an
38
+ # OptionParser enum would also complete a prefix (`--format=j`), which then turns ambiguous the day a second
39
+ # format shares it. The renderers' own "unsupported format" branches remain as a guard against internal drift.
40
+ def add_format(parser, options, description, formats: %w[text json])
41
+ parser.instance_variable_set(:@rigor_accepted_formats, formats.dup.freeze)
42
+ parser.on("--format=FORMAT", description) do |value|
43
+ raise OptionParser::InvalidArgument, value unless formats.include?(value)
44
+
45
+ options[:format] = value
46
+ end
47
+ end
48
+
49
+ # The `--config` value, checked where the flag is read (#1992). A named file that is not there is an error, not
50
+ # a request for the defaults: the run would otherwise analyse under a configuration the caller did not ask for,
51
+ # and an MCP caller would get a result for it with no sign anything was missing. Auto-discovery, with no
52
+ # `--config` given, is not affected.
53
+ def config_path(value)
54
+ raise ConfigurationError, "empty config path" if value.strip.empty?
55
+ raise ConfigurationError, "config path is a directory: #{value}" if File.directory?(value)
56
+ raise ConfigurationError, "config file not found: #{value}" unless File.exist?(value)
57
+ raise ConfigurationError, "config file not readable: #{value}" unless File.readable?(value)
58
+
59
+ value
22
60
  end
23
61
 
24
62
  # Defines the `--tmp-file` / `--instead-of` editor-mode flag pair on `parser`, writing into `options`.
25
63
  def add_editor_mode(parser, options)
26
64
  parser.on("--tmp-file=PATH",
27
65
  "Editor mode: read source bytes from PATH instead of --instead-of (paired)") do |value|
66
+ note_repeated_editor_flag(options, :tmp_file, "--tmp-file")
28
67
  options[:tmp_file] = value
29
68
  end
30
69
  parser.on("--instead-of=PATH",
31
70
  "Editor mode: the logical project path the buffer represents (paired with --tmp-file)") do |value|
71
+ note_repeated_editor_flag(options, :instead_of, "--instead-of")
32
72
  options[:instead_of] = value
33
73
  end
34
74
  end
35
75
 
76
+ # Issue #2233 — editor mode binds one buffer per run, so a second `--tmp-file` or `--instead-of` cannot be
77
+ # honoured beside the first. Overwriting it dropped the first pair with no message; the repeat is recorded here
78
+ # and rejected by {resolve_buffer_binding} as a usage error instead.
79
+ def note_repeated_editor_flag(options, key, flag)
80
+ return if options[key].nil?
81
+
82
+ (options[:repeated_editor_flags] ||= []) << flag
83
+ end
84
+
36
85
  # Resolves the editor-mode buffer binding from parsed `options`: returns nil when neither editor-mode flag is set,
37
86
  # a `Analysis::BufferBinding` when the pair is valid, or `:usage_error` (after writing the reason to `err`) when
38
- # the flags are unpaired or the temp file is unreadable.
87
+ # a flag is repeated, the flags are unpaired, or the temp file is unreadable.
39
88
  def resolve_buffer_binding(options, err:)
40
89
  tmp = options[:tmp_file]
41
90
  instead = options[:instead_of]
42
91
  return nil if tmp.nil? && instead.nil?
43
92
 
93
+ repeated = Array(options[:repeated_editor_flags])
94
+ unless repeated.empty?
95
+ err.puts("#{repeated.join(' and ')} given more than once; editor mode takes one buffer per run " \
96
+ "(a single --tmp-file / --instead-of pair)")
97
+ return :usage_error
98
+ end
99
+
44
100
  if tmp.nil? || instead.nil?
45
101
  err.puts("--tmp-file and --instead-of must appear together")
46
102
  return :usage_error
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "optparse"
4
+
3
5
  require_relative "command"
4
6
  require_relative "../rooted_glob"
5
7
 
@@ -7,7 +9,7 @@ module Rigor
7
9
  class CLI
8
10
  # `rigor plugin` (singular) — discover and read the plugin source bundled with the `rigortype` gem.
9
11
  #
10
- # Rigor ships ~30 production plugins under `plugins/` and a set of tutorial plugins under `examples/`. When Rigor is
12
+ # Rigor ships production plugins under `plugins/` and tutorial plugins under `examples/`. When Rigor is
11
13
  # installed via `mise` / `gem install` the gem checkout is on disk, so a plugin author (or an AI coding agent
12
14
  # following the `rigor-plugin-author` skill) can read a real, working plugin as a worked example — instead of
13
15
  # guessing the `Rigor::Plugin::Base` surface from prose. This command outputs the absolute paths so they can be
@@ -17,75 +19,122 @@ module Rigor
17
19
  # configured in *your* `.rigor.yml`. This command (singular) browses the plugins bundled in the *toolchain*.
18
20
  # Mnemonic: "plugins" = my config; "plugin" = the catalogue I can learn from.
19
21
  #
20
- # Subcommands:
22
+ # Grammar (mirrors `rigor docs` and `rigor skill`, ADR-126 WD4): the positional slot is always a plugin *name*;
23
+ # alternative outputs are flags, so a plugin can never be shadowed by a verb.
21
24
  #
22
- # - `rigor plugin list` — every bundled plugin + example,
23
- # name + absolute directory path.
24
- # - `rigor plugin path <name>` — one-line absolute path to the
25
- # plugin's directory (Read-tool input).
26
- # - `rigor plugin print <name>` — a header (dir / lib / sig / README
27
- # paths) followed by the plugin's main
25
+ # - `rigor plugin` — every bundled plugin + example, name + absolute directory path (the default).
26
+ # - `rigor plugin <name>` — a header (dir / lib / sig / README paths) followed by the plugin's main
28
27
  # `lib/<name>.rb` source body.
29
- # - `rigor plugin root` — the rigortype gem root and its key
30
- # subdirectories (lib/, plugins/,
31
- # examples/, skills/, sig/), so an
32
- # author can read the public plugin
33
- # API (`lib/rigor/plugin.rb`) directly.
28
+ # - `rigor plugin --path <name>` — one-line absolute path to the plugin's directory (Read-tool input).
29
+ # - `rigor plugin --list` — the same listing as no argument.
30
+ # - `rigor plugin --root` — the rigortype gem root and its key subdirectories (lib/, plugins/, examples/,
31
+ # skills/, sig/), so an author can read the public plugin API
32
+ # (`lib/rigor/plugin.rb`) directly.
34
33
  #
35
- # `rigor plugin` with no subcommand is an alias for `list`.
34
+ # The verbs `list`, `path <name>`, `print <name>` and `root` are the pre-ADR-126 spelling. They still work for one
35
+ # minor, print the same stdout and return the same status, and add one deprecation notice on stderr naming the
36
+ # replacement.
36
37
  #
37
38
  # **Docker / cross-filesystem note.** Every path printed is resolved at runtime from this file's location, so it is
38
39
  # correct *on the filesystem where `rigor` runs*. If you run `rigor` inside a container but read files from the host
39
40
  # (or vice versa), the paths will not resolve — read them from the same environment that ran the command (`rigor
40
- # plugin print` inlines the body for exactly this case: it works with no file-reading tool at all).
41
+ # plugin <name>` inlines the body for exactly this case: it works with no file-reading tool at all).
41
42
  class PluginCommand < Command
42
43
  USAGE = <<~USAGE
43
- Usage: rigor plugin <subcommand> [args]
44
+ Usage: rigor plugin [<name>] [--path <name>] [--list] [--root]
44
45
 
45
46
  Browse the plugins bundled in the rigortype toolchain (worked
46
47
  examples for authoring your own). For the activation status of
47
48
  the plugins in your .rigor.yml, use `rigor plugins` (plural).
48
49
 
49
- Subcommands:
50
- list List bundled + example plugins (default)
51
- path <name> Print the absolute directory path of <name>
52
- print <name> Print <name>'s main lib source, with a header
53
- root Print the gem root + key subdirectories
50
+ rigor plugin List bundled + example plugins
51
+ rigor plugin <name> Print <name>'s main lib source, with a header
52
+ rigor plugin --path <name> Print the absolute directory path of <name>
53
+ rigor plugin --list List bundled + example plugins
54
+ rigor plugin --root Print the gem root + key subdirectories
54
55
 
55
56
  Examples:
56
- rigor plugin list
57
- rigor plugin path rigor-activerecord
58
- rigor plugin print rigor-activesupport-core-ext
59
- rigor plugin root
57
+ rigor plugin
58
+ rigor plugin rigor-activesupport-core-ext
59
+ rigor plugin --path rigor-activerecord
60
+ rigor plugin --root
60
61
  USAGE
61
62
 
63
+ # The pre-ADR-126 verbs, each with the spelling that replaces it.
64
+ DEPRECATED_VERBS = {
65
+ "list" => "rigor plugin --list",
66
+ "path" => "rigor plugin --path <name>",
67
+ "print" => "rigor plugin <name>",
68
+ "root" => "rigor plugin --root"
69
+ }.freeze
70
+
62
71
  # The bundled plugins/examples/source live at `<gem_root>/...`. From `lib/rigor/cli/plugin_command.rb` the gem
63
72
  # root is three directories up (matching SkillCommand::SKILLS_ROOT).
64
73
  GEM_ROOT = File.expand_path("../../..", __dir__).freeze
65
74
  PLUGINS_ROOT = File.join(GEM_ROOT, "plugins").freeze
66
75
  EXAMPLES_ROOT = File.join(GEM_ROOT, "examples").freeze
67
76
 
77
+ # The parser this command parses with, built without parsing anything: the catalogue reads its flags from it
78
+ # (ADR-126 WD3). `options` is only written by the switches' blocks.
79
+ def option_parser(options = {})
80
+ OptionParser.new do |opts|
81
+ opts.banner = USAGE.lines.first.chomp
82
+ opts.on("--path NAME", "Print the absolute directory path of a plugin") { |value| options[:path] = value }
83
+ opts.on("--list", "List bundled + example plugins") { options[:list] = true }
84
+ opts.on("--root", "Print the gem root + key subdirectories") { options[:root] = true }
85
+ end
86
+ end
87
+
68
88
  # @return CLI exit status.
69
89
  def run
70
- subcommand = @argv.shift || "list"
71
-
72
- case subcommand
73
- when "list" then run_list
74
- when "path" then run_path
75
- when "print" then run_print
76
- when "root" then run_root
77
- when "-h", "--help", "help"
90
+ if @argv.intersect?(%w[-h --help])
78
91
  @out.puts(USAGE)
79
- 0
80
- else
81
- @err.puts("Unknown subcommand: #{subcommand}")
82
- @err.puts(USAGE)
83
- Rigor::CLI::EXIT_USAGE
92
+ return 0
84
93
  end
94
+
95
+ verb = @argv.first
96
+ return run_deprecated_verb(@argv.shift) if DEPRECATED_VERBS.key?(verb)
97
+ return run_help if verb == "help"
98
+
99
+ run_grammar
85
100
  end
86
101
 
87
102
  private
88
103
 
104
+ def run_help
105
+ @out.puts(USAGE)
106
+ 0
107
+ end
108
+
109
+ def run_grammar
110
+ options = {}
111
+ begin
112
+ option_parser(options).parse!(@argv)
113
+ rescue OptionParser::ParseError => e
114
+ return usage_error(e.message)
115
+ end
116
+ return run_path(options[:path]) if options.key?(:path)
117
+ return run_root if options[:root]
118
+ return run_list if options[:list]
119
+
120
+ name = @argv.shift
121
+ name.nil? ? run_list : run_print(name)
122
+ end
123
+
124
+ def run_deprecated_verb(verb)
125
+ @err.puts("rigor: `rigor plugin #{verb}` is deprecated and will be removed in the next minor release; " \
126
+ "use `#{DEPRECATED_VERBS.fetch(verb)}`.")
127
+ # The verbs' stdout stays byte-identical to what they printed before the grammar changed, so its prose still
128
+ # names the verb spelling.
129
+ @legacy = true
130
+ case verb
131
+ when "list" then run_list
132
+ when "path" then run_path(@argv.shift)
133
+ when "print" then run_print(@argv.shift)
134
+ else run_root
135
+ end
136
+ end
137
+
89
138
  def run_list
90
139
  plugins = discover(PLUGINS_ROOT)
91
140
  examples = discover(EXAMPLES_ROOT)
@@ -113,8 +162,7 @@ module Rigor
113
162
  end
114
163
  end
115
164
 
116
- def run_path
117
- name = @argv.shift
165
+ def run_path(name)
118
166
  return usage_error("`path` requires a plugin name") if name.nil?
119
167
 
120
168
  plugin = find(name)
@@ -124,8 +172,7 @@ module Rigor
124
172
  0
125
173
  end
126
174
 
127
- def run_print
128
- name = @argv.shift
175
+ def run_print(name)
129
176
  return usage_error("`print` requires a plugin name") if name.nil?
130
177
 
131
178
  plugin = find(name)
@@ -159,7 +206,7 @@ module Rigor
159
206
  0
160
207
  end
161
208
 
162
- # The header that precedes the plugin body when an author runs `rigor plugin print <name>`. `# `-prefixed so the
209
+ # The header that precedes the plugin body when an author runs `rigor plugin <name>`. `# `-prefixed so the
163
210
  # combined output stays readable; the Ruby body below it is unchanged.
164
211
  def render_print_header(plugin)
165
212
  dir = plugin.fetch(:path)
@@ -176,7 +223,7 @@ module Rigor
176
223
  # The main source body is below; read the other files from the
177
224
  # paths above. To suppress `call.undefined-method` for methods a
178
225
  # DSL generates, study how an RBS-bundle plugin ships `sig/` (see
179
- # `rigor plugin print rigor-activesupport-core-ext`).
226
+ # `#{@legacy ? 'rigor plugin print' : 'rigor plugin'} rigor-activesupport-core-ext`).
180
227
  HEADER
181
228
  end
182
229
 
@@ -211,12 +258,12 @@ module Rigor
211
258
  def docker_note
212
259
  "\nNote: paths are local to where `rigor` runs. If you run rigor in a " \
213
260
  "container,\nread these files from the same filesystem (use " \
214
- "`rigor plugin print` to inline a body)."
261
+ "`#{@legacy ? 'rigor plugin print' : 'rigor plugin <name>'}` to inline a body)."
215
262
  end
216
263
 
217
264
  def name_error(name)
218
265
  @err.puts("Unknown plugin: #{name}")
219
- @err.puts("Run `rigor plugin list` to see the bundled plugins.")
266
+ @err.puts("Run `rigor plugin --list` to see the bundled plugins.")
220
267
  1
221
268
  end
222
269
 
@@ -30,7 +30,7 @@ module Rigor
30
30
  # - every manifest-declared extension surface
31
31
  # (`open_receivers:` / `owns_receivers:` / `produces:` /
32
32
  # `consumes:` / `block_as_methods:` / `heredoc_templates:` /
33
- # `trait_registries:` /
33
+ # `trait_registries:` / `per_invocation_classes:` /
34
34
  # `type_node_resolvers:` / `hkt_registrations:` /
35
35
  # `hkt_definitions:` / `protocol_contracts:` /
36
36
  # `source_rbs_synthesizer:`);
@@ -80,6 +80,20 @@ module Rigor
80
80
  0
81
81
  end
82
82
 
83
+ # The parser this command parses with, built without parsing anything: the catalogue reads the
84
+ # flags and formats from it (ADR-126 WD3). `options` is only written by the switches' blocks.
85
+ def option_parser(options = {})
86
+ OptionParser.new do |opts|
87
+ opts.banner = USAGE
88
+ Options.add_config(opts, options)
89
+ Options.add_format(opts, options, "Output format: text (default) or json")
90
+ opts.on("--strict", "Exit 1 if any plugin failed to load (CI gate)") { options[:strict] = true }
91
+ opts.on("--capabilities", "Emit the per-plugin extension-protocol catalogue (ADR-37)") do
92
+ options[:capabilities] = true
93
+ end
94
+ end
95
+ end
96
+
83
97
  private
84
98
 
85
99
  # Picks the renderer view. `--capabilities` switches to the focused extension-protocol catalogue (ADR-37 §
@@ -97,25 +111,10 @@ module Rigor
97
111
 
98
112
  def parse_options
99
113
  options = { config: nil, format: "text", strict: false, capabilities: false }
100
- OptionParser.new do |opts|
101
- opts.banner = USAGE
102
- Options.add_config(opts, options)
103
- opts.on("--format=FORMAT", "Output format: text (default) or json") { |v| options[:format] = v }
104
- opts.on("--strict", "Exit 1 if any plugin failed to load (CI gate)") { options[:strict] = true }
105
- opts.on("--capabilities", "Emit the per-plugin extension-protocol catalogue (ADR-37)") do
106
- options[:capabilities] = true
107
- end
108
- end.parse!(@argv)
109
- validate!(options)
114
+ option_parser(options).parse!(@argv)
110
115
  options
111
116
  end
112
117
 
113
- def validate!(options)
114
- return if %w[text json].include?(options.fetch(:format))
115
-
116
- raise OptionParser::InvalidArgument, "unsupported format: #{options.fetch(:format)}"
117
- end
118
-
119
118
  # Runs the plugin loader against the project configuration and returns a row per declared plugin entry. Each row
120
119
  # is a plain Hash with the fields enumerated in the class docstring so the renderer (text + JSON) reads from a
121
120
  # single shape.
@@ -204,7 +203,7 @@ module Rigor
204
203
  .merge(load_error: nil)
205
204
  end
206
205
 
207
- # ADR-37 narrow extension protocols. Unlike the 10 declarative manifest fields, these are class-level DSLs
206
+ # ADR-37 narrow extension protocols. Unlike the declarative manifest fields, these are class-level DSLs
208
207
  # (`node_rule` / `dynamic_return` / `narrowing_facts`), so they are read off the plugin class rather than the
209
208
  # manifest. The gate values — node types, receiver class names, specified method names — are the greppable,
210
209
  # enumerable surface the capability catalogue exposes.
@@ -233,6 +232,8 @@ module Rigor
233
232
  signature_paths: signature_path_rows(plugin),
234
233
  open_receivers: manifest.open_receivers.dup,
235
234
  owns_receivers: manifest.owns_receivers.dup,
235
+ per_invocation_classes: manifest.per_invocation_classes.dup,
236
+ per_invocation_non_action_macros: manifest.per_invocation_non_action_macros.map(&:to_s),
236
237
  produces: manifest.produces.map(&:to_s),
237
238
  consumes: manifest.consumes.map { |c| "#{c.plugin_id}/#{c.name}#{'?' if c.optional}" },
238
239
  source_rbs_synthesizer: !manifest.source_rbs_synthesizer.nil?
@@ -265,6 +266,8 @@ module Rigor
265
266
  signature_paths: [],
266
267
  open_receivers: [],
267
268
  owns_receivers: [],
269
+ per_invocation_classes: [],
270
+ per_invocation_non_action_macros: [],
268
271
  produces: [],
269
272
  consumes: [],
270
273
  block_as_methods: 0,
@@ -345,6 +348,7 @@ module Rigor
345
348
  config: {},
346
349
  signature_paths: [],
347
350
  open_receivers: [], owns_receivers: [], produces: [], consumes: [],
351
+ per_invocation_classes: [], per_invocation_non_action_macros: [],
348
352
  block_as_methods: 0, heredoc_templates: 0, trait_registries: 0,
349
353
  type_node_resolvers: 0,
350
354
  hkt_registrations: 0, hkt_definitions: 0,
@@ -1,11 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "json"
4
+ require_relative "json_document"
4
5
 
5
6
  module Rigor
6
7
  class CLI
7
8
  # Renderer for `rigor plugins`. Produces a human-readable text table and a JSON representation from the same row
8
- # shape (the Hash documented on {Rigor::CLI::PluginsCommand#loaded_row}).
9
+ # shape (the Hash {Rigor::CLI::PluginsCommand#build_rows} returns).
9
10
  #
10
11
  # The two formats carry the same content; JSON is meant for tooling (SKILLs, CI, editor integrations) while text is
11
12
  # for interactive inspection. Rows are printed in the order the loader resolved them.
@@ -44,7 +45,7 @@ module Rigor
44
45
  "available" => @inflection.fetch(:available)
45
46
  }
46
47
  end
47
- JSON.pretty_generate(payload)
48
+ JSON.pretty_generate(JsonDocument.stamp(payload, mode: "status"))
48
49
  end
49
50
 
50
51
  # ADR-37 § "Machine-readable capability catalogue" — the focused per-plugin extension-protocol dump. Only loaded
@@ -53,10 +54,13 @@ module Rigor
53
54
  # type-specifier methods, and produced / consumed facts.
54
55
  def capabilities_json
55
56
  JSON.pretty_generate(
56
- {
57
- "configuration" => @configuration_path,
58
- "capabilities" => loaded_rows.map { |row| capabilities_json_for(row) }
59
- }
57
+ JsonDocument.stamp(
58
+ {
59
+ "configuration" => @configuration_path,
60
+ "capabilities" => loaded_rows.map { |row| capabilities_json_for(row) }
61
+ },
62
+ mode: "capabilities"
63
+ )
60
64
  )
61
65
  end
62
66
 
@@ -185,12 +189,26 @@ module Rigor
185
189
  lines = []
186
190
  lines << " open_receivers: #{row[:open_receivers].join(', ')}" if row[:open_receivers].any?
187
191
  lines << " owns_receivers: #{row[:owns_receivers].join(', ')}" if row[:owns_receivers].any?
192
+ lines.concat(per_invocation_lines(row))
188
193
  lines << " produces: #{row[:produces].join(', ')}" if row[:produces].any?
189
194
  lines << " consumes: #{row[:consumes].join(', ')}" if row[:consumes].any?
190
195
  lines.concat(narrow_protocol_lines(row))
191
196
  lines
192
197
  end
193
198
 
199
+ # ADR-125 — the per-invocation classes a plugin vouches for, and the macros whose arguments name a method that is
200
+ # not one of their actions.
201
+ def per_invocation_lines(row)
202
+ lines = []
203
+ if row[:per_invocation_classes].any?
204
+ lines << " per_invocation_classes: #{row[:per_invocation_classes].join(', ')}"
205
+ end
206
+ if row[:per_invocation_non_action_macros].any?
207
+ lines << " per_invocation_non_action_macros: #{row[:per_invocation_non_action_macros].join(', ')}"
208
+ end
209
+ lines
210
+ end
211
+
194
212
  # ADR-37 narrow extension protocols (node_rule / dynamic_return / narrowing_facts). Surfaced in the full report
195
213
  # alongside the declarative surfaces; `--capabilities` is the focused view.
196
214
  def narrow_protocol_lines(row)
@@ -249,8 +267,6 @@ module Rigor
249
267
  "signature_paths" => row[:signature_paths].map do |sp|
250
268
  { "path" => sp[:path], "exists" => sp[:exists], "rbs_files" => sp[:rbs_files] }
251
269
  end,
252
- "open_receivers" => row[:open_receivers],
253
- "owns_receivers" => row[:owns_receivers],
254
270
  "produces" => row[:produces],
255
271
  "consumes" => row[:consumes],
256
272
  "block_as_methods" => row[:block_as_methods],
@@ -265,6 +281,16 @@ module Rigor
265
281
  "dynamic_return_receivers" => row[:dynamic_return_receivers],
266
282
  "narrowing_facts_methods" => row[:narrowing_facts_methods],
267
283
  "load_error" => row[:load_error]
284
+ }.merge(receiver_json(row))
285
+ end
286
+
287
+ # ADR-125 — the per-invocation keys are additive; an empty array when the plugin declares none.
288
+ def receiver_json(row)
289
+ {
290
+ "open_receivers" => row[:open_receivers],
291
+ "owns_receivers" => row[:owns_receivers],
292
+ "per_invocation_classes" => row[:per_invocation_classes],
293
+ "per_invocation_non_action_macros" => row[:per_invocation_non_action_macros]
268
294
  }
269
295
  end
270
296
 
@@ -31,8 +31,8 @@ module Rigor
31
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
- # than each rebuilding it after the fork (mirrors the check fork pool's parent-side prewarm). A
35
- # no-op on the sequential path but cheap.
34
+ # than each rebuilding it after the fork (mirrors the check fork pool's parent-side prewarm).
35
+ # Skipped when the run is sequential.
36
36
  environment.rbs_loader&.prewarm if Inference::ForkMap.parallel?([workers, paths.size].min)
37
37
 
38
38
  payloads = Inference::ForkMap.call(items: paths, workers: workers) do |slice|
@@ -3,6 +3,7 @@
3
3
  require "json"
4
4
 
5
5
  require_relative "../inference/dynamic_origin"
6
+ require_relative "json_document"
6
7
 
7
8
  module Rigor
8
9
  class CLI
@@ -32,7 +33,7 @@ module Rigor
32
33
  private
33
34
 
34
35
  def render_json(report)
35
- @out.puts(JSON.pretty_generate(report.to_h))
36
+ @out.puts(JSON.pretty_generate(JsonDocument.stamp(report.to_h, mode: "protection")))
36
37
  end
37
38
 
38
39
  def render_text(report)
@@ -6,6 +6,8 @@ require "optionparser"
6
6
  require_relative "../bleeding_edge"
7
7
  require_relative "../configuration"
8
8
  require_relative "command"
9
+ require_relative "options"
10
+ require_relative "json_document"
9
11
 
10
12
  module Rigor
11
13
  class CLI
@@ -34,19 +36,23 @@ module Rigor
34
36
  0
35
37
  end
36
38
 
37
- private
38
-
39
- def parse_options
40
- options = { format: "text", config: nil }
39
+ # The parser this command parses with, built without parsing anything: the catalogue reads the
40
+ # flags and formats from it (ADR-126 WD3). `options` is only written by the switches' blocks.
41
+ def option_parser(options = {})
41
42
  OptionParser.new do |opt|
42
43
  opt.banner = USAGE
43
- opt.on("--format=FORMAT", %w[text json], "Output format (text | json). Default: text.") do |fmt|
44
- options[:format] = fmt
45
- end
44
+ Options.add_format(opt, options, "Output format (text | json). Default: text.")
46
45
  opt.on("--config=PATH", "Path to a .rigor.yml (default: auto-discovery).") do |path|
47
- options[:config] = path
46
+ options[:config] = Options.config_path(path)
48
47
  end
49
- end.parse!(@argv)
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ def parse_options
54
+ options = { format: "text", config: nil }
55
+ option_parser(options).parse!(@argv)
50
56
  options
51
57
  end
52
58
 
@@ -59,13 +65,14 @@ module Rigor
59
65
 
60
66
  def render_json(configuration)
61
67
  selector = configuration.bleeding_edge
62
- @out.puts(JSON.pretty_generate(
63
- "overlay" => BleedingEdge.features.map(&:to_h),
64
- "selector" => configuration.to_h.fetch("bleeding_edge"),
65
- "active" => BleedingEdge.active_features(selector).map(&:id),
66
- "unknown_selected" => BleedingEdge.unknown_selected_ids(selector),
67
- "graduated" => BleedingEdge::GRADUATED
68
- ))
68
+ payload = {
69
+ "overlay" => BleedingEdge.features.map(&:to_h),
70
+ "selector" => configuration.to_h.fetch("bleeding_edge"),
71
+ "active" => BleedingEdge.active_features(selector).map(&:id),
72
+ "unknown_selected" => BleedingEdge.unknown_selected_ids(selector),
73
+ "graduated" => BleedingEdge::GRADUATED
74
+ }
75
+ @out.puts(JSON.pretty_generate(JsonDocument.stamp(payload)))
69
76
  end
70
77
 
71
78
  def render_text(configuration)