rigortype 0.4.6 → 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.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/data/gem_overlay/activesupport/core_ext.rbs +16 -13
- data/data/vendored_gem_sigs/bundler/bundler.rbs +1 -1
- data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +4 -3
- data/docs/handbook/01-getting-started.md +5 -2
- data/docs/handbook/11-sig-gen.md +50 -3
- data/docs/handbook/appendix-phpstan.md +4 -4
- data/docs/manual/02-cli-reference.md +152 -26
- data/docs/manual/03-configuration.md +36 -3
- data/docs/manual/06-baseline.md +6 -4
- data/docs/manual/09-editor-integration.md +13 -2
- data/docs/manual/10-mcp-server.md +38 -2
- data/docs/manual/12-caching.md +4 -3
- data/docs/manual/plugins/rigor-actionpack.md +19 -11
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +20 -1
- data/lib/rigor/analysis/baseline.rb +24 -27
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +5 -6
- data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +4 -4
- data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +85 -13
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +3 -2
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +0 -1
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -8
- data/lib/rigor/analysis/check_rules/published_constant_guard.rb +1 -1
- data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +3 -3
- data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
- data/lib/rigor/analysis/check_rules/rule_ids.rb +1 -1
- data/lib/rigor/analysis/check_rules/rule_walk.rb +9 -7
- data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +3 -4
- data/lib/rigor/analysis/check_rules/special_global_setters.rb +3 -3
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules.rb +124 -44
- data/lib/rigor/analysis/crash_signature.rb +2 -2
- data/lib/rigor/analysis/dependency_recorder.rb +4 -4
- data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +14 -3
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +1 -1
- data/lib/rigor/analysis/dependency_source_inference/index.rb +6 -8
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
- data/lib/rigor/analysis/edit_replay.rb +93 -6
- data/lib/rigor/analysis/fact_store.rb +3 -3
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +2 -2
- data/lib/rigor/analysis/incremental_run_slot.rb +2 -2
- data/lib/rigor/analysis/incremental_session.rb +27 -16
- data/lib/rigor/analysis/rule_catalog.rb +2 -2
- data/lib/rigor/analysis/run_stats.rb +2 -2
- data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +2 -2
- data/lib/rigor/analysis/runner/declaration_position.rb +1 -1
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +193 -93
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +1 -1
- data/lib/rigor/analysis/runner/pool_coordinator.rb +147 -323
- data/lib/rigor/analysis/runner/project_pre_passes.rb +3 -3
- data/lib/rigor/analysis/runner/run_level_facts.rb +304 -0
- data/lib/rigor/analysis/runner/run_snapshots.rb +44 -54
- data/lib/rigor/analysis/runner/worker_payload.rb +36 -0
- data/lib/rigor/analysis/runner.rb +77 -71
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +1 -1
- data/lib/rigor/analysis/template_unit_positions.rb +2 -3
- data/lib/rigor/analysis/template_units.rb +9 -1
- data/lib/rigor/analysis/worker_session.rb +24 -31
- data/lib/rigor/bleeding_edge.rb +3 -6
- data/lib/rigor/builtins/imported_refinements.rb +6 -8
- data/lib/rigor/builtins/predefined_constant_refinements.rb +1 -1
- data/lib/rigor/builtins/regex_refinement.rb +4 -6
- data/lib/rigor/cache/descriptor.rb +6 -5
- data/lib/rigor/cache/incremental_snapshot.rb +31 -12
- data/lib/rigor/cache/rbs_cache_producer.rb +2 -2
- data/lib/rigor/cache/rbs_class_ancestor_table.rb +1 -1
- data/lib/rigor/cache/rbs_class_type_param_names.rb +2 -2
- data/lib/rigor/cache/rbs_constant_table.rb +1 -1
- data/lib/rigor/cache/rbs_descriptor.rb +54 -3
- data/lib/rigor/cache/rbs_environment.rb +4 -1
- data/lib/rigor/cache/rbs_environment_marshal_patch.rb +2 -2
- data/lib/rigor/cache/rbs_known_class_names.rb +1 -1
- data/lib/rigor/cache/store.rb +11 -10
- data/lib/rigor/ci_detector.rb +1 -1
- data/lib/rigor/cli/annotate_command.rb +24 -19
- data/lib/rigor/cli/baseline_command.rb +145 -55
- data/lib/rigor/cli/catalogue.rb +256 -0
- data/lib/rigor/cli/check_command.rb +108 -102
- data/lib/rigor/cli/check_runner_factory.rb +2 -2
- data/lib/rigor/cli/command.rb +15 -1
- data/lib/rigor/cli/coverage_command.rb +21 -8
- data/lib/rigor/cli/coverage_mutation.rb +5 -7
- data/lib/rigor/cli/coverage_renderer.rb +2 -1
- data/lib/rigor/cli/coverage_scan.rb +3 -5
- data/lib/rigor/cli/diff_command.rb +25 -20
- data/lib/rigor/cli/docs_command.rb +1 -2
- data/lib/rigor/cli/doctor_command.rb +12 -12
- data/lib/rigor/cli/effects_command.rb +50 -40
- data/lib/rigor/cli/effects_diff_renderer.rb +18 -17
- data/lib/rigor/cli/effects_explain_renderer.rb +13 -12
- data/lib/rigor/cli/effects_renderer.rb +2 -1
- data/lib/rigor/cli/effects_snapshot_command.rb +18 -17
- data/lib/rigor/cli/explain_command.rb +18 -8
- data/lib/rigor/cli/fused_protection_renderer.rb +2 -1
- data/lib/rigor/cli/json_document.rb +41 -0
- data/lib/rigor/cli/lens_command.rb +12 -8
- data/lib/rigor/cli/lsp_command.rb +250 -23
- data/lib/rigor/cli/mcp_command.rb +12 -7
- data/lib/rigor/cli/mutation_fork_scan.rb +1 -1
- data/lib/rigor/cli/mutation_protection_renderer.rb +3 -2
- data/lib/rigor/cli/options.rb +40 -1
- data/lib/rigor/cli/plugin_command.rb +93 -46
- data/lib/rigor/cli/plugins_command.rb +17 -18
- data/lib/rigor/cli/plugins_renderer.rb +10 -6
- data/lib/rigor/cli/protection_fork_scan.rb +2 -2
- data/lib/rigor/cli/protection_renderer.rb +2 -1
- data/lib/rigor/cli/show_bleedingedge_command.rb +21 -15
- data/lib/rigor/cli/sig_gen_command.rb +84 -27
- data/lib/rigor/cli/skill_command.rb +32 -3
- data/lib/rigor/cli/skill_deep_probe.rb +1 -1
- data/lib/rigor/cli/skill_describe.rb +1 -1
- data/lib/rigor/cli/trace_command.rb +14 -9
- data/lib/rigor/cli/triage_command.rb +10 -11
- data/lib/rigor/cli/triage_renderer.rb +2 -1
- data/lib/rigor/cli/type_of_command.rb +16 -11
- data/lib/rigor/cli/type_of_project_scope.rb +1 -4
- data/lib/rigor/cli/type_of_renderer.rb +4 -1
- data/lib/rigor/cli/type_scan_command.rb +14 -9
- data/lib/rigor/cli/type_scan_renderer.rb +2 -1
- data/lib/rigor/cli/unused_command.rb +27 -17
- data/lib/rigor/cli/upgrade_command.rb +5 -0
- data/lib/rigor/cli.rb +209 -54
- data/lib/rigor/config_audit.rb +1 -1
- data/lib/rigor/configuration/severity_profile.rb +5 -5
- data/lib/rigor/configuration.rb +72 -30
- data/lib/rigor/effects/definition_context.rb +0 -2
- data/lib/rigor/effects/envelope.rb +1 -1
- data/lib/rigor/effects/framework_units.rb +1 -1
- data/lib/rigor/effects/mutation_classifier.rb +4 -7
- data/lib/rigor/effects/propagator.rb +1 -1
- data/lib/rigor/effects/registry.rb +2 -2
- data/lib/rigor/effects/snapshot.rb +1 -1
- data/lib/rigor/effects/taint_cause.rb +0 -2
- data/lib/rigor/effects/unit_scan.rb +9 -13
- data/lib/rigor/environment/bundle_sig_discovery.rb +5 -9
- data/lib/rigor/environment/class_registry.rb +3 -3
- data/lib/rigor/environment/core_root.rb +77 -0
- data/lib/rigor/environment/default_libraries.rb +1 -1
- data/lib/rigor/environment/deferred_signature_plan.rb +283 -0
- data/lib/rigor/environment/gated_signature_guard.rb +1 -1
- data/lib/rigor/environment/gated_signature_plan.rb +5 -2
- data/lib/rigor/environment/lockfile_resolver.rb +28 -12
- data/lib/rigor/environment/member_consistency/comparator.rb +18 -1
- data/lib/rigor/environment/member_consistency.rb +56 -7
- data/lib/rigor/environment/missing_gem_constant_index.rb +3 -2
- data/lib/rigor/environment/project_path_gems.rb +124 -0
- data/lib/rigor/environment/rbs_hierarchy.rb +6 -15
- data/lib/rigor/environment/rbs_loader.rb +480 -67
- data/lib/rigor/environment/reflection.rb +7 -7
- data/lib/rigor/environment/required_features.rb +0 -3
- data/lib/rigor/environment/ruby_library_roots.rb +1 -1
- data/lib/rigor/environment/vendored_signature_plan.rb +153 -0
- data/lib/rigor/environment.rb +41 -10
- data/lib/rigor/flow_contribution/merger.rb +4 -4
- data/lib/rigor/inference/acceptance.rb +93 -32
- data/lib/rigor/inference/block_parameter_binder.rb +2 -3
- data/lib/rigor/inference/block_run_count.rb +178 -0
- data/lib/rigor/inference/budget_trace.rb +8 -2
- data/lib/rigor/inference/builtins/exception_catalog.rb +2 -2
- data/lib/rigor/inference/builtins/numeric_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/random_catalog.rb +2 -2
- data/lib/rigor/inference/callee_mutation_chain.rb +88 -77
- data/lib/rigor/inference/callee_write_census.rb +815 -0
- data/lib/rigor/inference/construction_escape.rb +217 -0
- data/lib/rigor/inference/declaration_walk/traversal.rb +1 -1
- data/lib/rigor/inference/deep_erasure.rb +39 -7
- data/lib/rigor/inference/def_handle.rb +1 -1
- data/lib/rigor/inference/def_node_resolver.rb +1 -1
- data/lib/rigor/inference/def_return_typer.rb +46 -4
- data/lib/rigor/inference/definer_resolution.rb +4 -2
- data/lib/rigor/inference/definers_index.rb +106 -0
- data/lib/rigor/inference/direct_ivar_writes.rb +300 -0
- data/lib/rigor/inference/element_read_widening.rb +49 -3
- data/lib/rigor/inference/expression_typer.rb +116 -49
- data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
- data/lib/rigor/inference/global_write_census.rb +76 -2
- data/lib/rigor/inference/global_writer_reach.rb +260 -0
- data/lib/rigor/inference/hkt_body.rb +1 -2
- data/lib/rigor/inference/hkt_body_parser.rb +3 -5
- data/lib/rigor/inference/hkt_reducer.rb +2 -2
- data/lib/rigor/inference/hkt_registry.rb +3 -5
- data/lib/rigor/inference/indexed_narrowing.rb +4 -7
- data/lib/rigor/inference/intervening_ivar_reset.rb +26 -4
- data/lib/rigor/inference/ivar_writer_census.rb +51 -0
- data/lib/rigor/inference/key_presence_guard.rb +2 -1
- data/lib/rigor/inference/loop_value_record.rb +8 -4
- data/lib/rigor/inference/match_rebinding/frame.rb +46 -0
- data/lib/rigor/inference/match_rebinding/yielding_match.rb +45 -0
- data/lib/rigor/inference/match_rebinding.rb +11 -1
- data/lib/rigor/inference/memo_audit.rb +55 -0
- data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/argument_distribution.rb +40 -0
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +3 -3
- data/lib/rigor/inference/method_dispatcher/contested_overloads.rb +89 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/no_match_fallback.rb +54 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +39 -27
- data/lib/rigor/inference/method_dispatcher/random_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +203 -17
- data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/self_substitute.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +9 -7
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +83 -20
- data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +0 -1
- data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +50 -17
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -5
- data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +4 -6
- data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher.rb +64 -14
- data/lib/rigor/inference/module_function_state.rb +11 -1
- data/lib/rigor/inference/multi_target_binder.rb +1 -1
- data/lib/rigor/inference/mutation_rejoin.rb +240 -4
- data/lib/rigor/inference/mutation_widening.rb +157 -27
- data/lib/rigor/inference/narrowing.rb +47 -33
- data/lib/rigor/inference/optimistic_origin.rb +1 -1
- data/lib/rigor/inference/out_buffer_mutation.rb +72 -0
- data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
- data/lib/rigor/inference/pre_eval_constants.rb +1 -1
- data/lib/rigor/inference/project_method_ownership.rb +6 -3
- data/lib/rigor/inference/project_yielder.rb +11 -17
- data/lib/rigor/inference/receiver_alias.rb +20 -0
- data/lib/rigor/inference/refined_dispatch/constructed_class.rb +3 -1
- data/lib/rigor/inference/refined_dispatch/own_method.rb +2 -1
- data/lib/rigor/inference/remover_holder_scan.rb +233 -0
- data/lib/rigor/inference/remover_narrowing.rb +224 -0
- data/lib/rigor/inference/rewrite_mutation.rb +53 -2
- data/lib/rigor/inference/scope_indexer/certainty.rb +0 -1
- data/lib/rigor/inference/scope_indexer/constant_ivar_value.rb +93 -0
- data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +58 -15
- data/lib/rigor/inference/scope_indexer/derived_param_value.rb +112 -0
- data/lib/rigor/inference/scope_indexer/empty_seed.rb +99 -0
- data/lib/rigor/inference/scope_indexer/ivar_contributions.rb +135 -0
- data/lib/rigor/inference/scope_indexer.rb +137 -46
- data/lib/rigor/inference/self_call_ivar_writes.rb +84 -12
- data/lib/rigor/inference/set_mutation.rb +189 -0
- data/lib/rigor/inference/singleton_hook_decline.rb +1 -2
- data/lib/rigor/inference/statement_evaluator.rb +2328 -138
- data/lib/rigor/inference/struct_fold_safety.rb +4 -6
- data/lib/rigor/inference/synthetic_method_scanner.rb +5 -6
- data/lib/rigor/inference/toplevel_def_census.rb +59 -0
- data/lib/rigor/inference/unknown_store_widening.rb +103 -4
- data/lib/rigor/inference/unthreaded_rebinds.rb +2 -3
- data/lib/rigor/inference/void_tail_summary.rb +2 -1
- data/lib/rigor/language_server/buffer_table.rb +3 -3
- data/lib/rigor/language_server/completion_provider.rb +26 -20
- data/lib/rigor/language_server/config_warning_announcer.rb +28 -0
- data/lib/rigor/language_server/diagnostic_publisher.rb +8 -3
- data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
- data/lib/rigor/language_server/folding_range_provider.rb +1 -2
- data/lib/rigor/language_server/hover_provider.rb +1 -7
- data/lib/rigor/language_server/hover_renderer.rb +6 -8
- data/lib/rigor/language_server/project_context.rb +39 -1
- data/lib/rigor/language_server/publish_batcher.rb +3 -4
- data/lib/rigor/language_server/selection_range_provider.rb +2 -4
- data/lib/rigor/language_server/server.rb +7 -3
- data/lib/rigor/language_server/signature_help_provider.rb +4 -4
- data/lib/rigor/lens/plugins.rb +1 -1
- data/lib/rigor/mcp/server.rb +73 -2
- data/lib/rigor/mcp.rb +1 -1
- data/lib/rigor/plugin/access_denied_error.rb +1 -2
- data/lib/rigor/plugin/bundled_catalog.rb +1 -12
- data/lib/rigor/plugin/io_boundary.rb +1 -1
- data/lib/rigor/plugin/loader.rb +25 -8
- data/lib/rigor/plugin/macro/heredoc_template.rb +7 -8
- data/lib/rigor/plugin/macro/trait_registry.rb +6 -9
- data/lib/rigor/plugin/manifest.rb +9 -13
- data/lib/rigor/plugin/registry.rb +3 -3
- data/lib/rigor/plugin/services.rb +2 -3
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -0
- data/lib/rigor/plugin/trust_policy.rb +2 -2
- data/lib/rigor/plugin/type_node_resolver.rb +2 -4
- data/lib/rigor/plugin.rb +6 -8
- data/lib/rigor/plugin_gap_advisory.rb +5 -1
- data/lib/rigor/project_environment.rb +16 -2
- data/lib/rigor/protection/analysis_guard.rb +1 -1
- data/lib/rigor/protection/discovery_seed.rb +44 -5
- data/lib/rigor/protection/mutator.rb +1 -1
- data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
- data/lib/rigor/rbs_extended/hkt_directives.rb +6 -10
- data/lib/rigor/rbs_extended/reporter.rb +27 -3
- data/lib/rigor/rbs_extended.rb +2 -3
- data/lib/rigor/reflection/constant_ancestors.rb +5 -3
- data/lib/rigor/rooted_glob.rb +15 -1
- data/lib/rigor/run_scoped_slots.rb +38 -0
- data/lib/rigor/runtime/jit.rb +1 -1
- data/lib/rigor/scope/discovery_index.rb +19 -6
- data/lib/rigor/scope/resolution_chain.rb +3 -1
- data/lib/rigor/scope.rb +365 -31
- data/lib/rigor/sig_gen/classification.rb +4 -0
- data/lib/rigor/sig_gen/effect_annotation.rb +3 -3
- data/lib/rigor/sig_gen/generator.rb +371 -58
- data/lib/rigor/sig_gen/generic_header_scan.rb +189 -0
- data/lib/rigor/sig_gen/inline_declarations.rb +2 -2
- data/lib/rigor/sig_gen/layout_index.rb +3 -3
- data/lib/rigor/sig_gen/method_candidate.rb +23 -4
- data/lib/rigor/sig_gen/observation_collector.rb +3 -5
- data/lib/rigor/sig_gen/observed_call.rb +1 -1
- data/lib/rigor/sig_gen/path_mapper.rb +6 -0
- data/lib/rigor/sig_gen/renderer.rb +15 -9
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +15 -2
- data/lib/rigor/sig_gen/type_name_qualification.rb +69 -0
- data/lib/rigor/sig_gen/write_reporter.rb +63 -14
- data/lib/rigor/sig_gen/write_result.rb +23 -12
- data/lib/rigor/sig_gen/writer.rb +217 -37
- data/lib/rigor/type/acceptance_router.rb +2 -2
- data/lib/rigor/type/accepts_result.rb +1 -1
- data/lib/rigor/type/app.rb +4 -6
- data/lib/rigor/type/combinator.rb +9 -13
- data/lib/rigor/type/constant.rb +4 -5
- data/lib/rigor/type/hash_shape.rb +21 -40
- data/lib/rigor/type/union.rb +1 -1
- data/lib/rigor/type_node/identifier.rb +1 -2
- data/lib/rigor/type_node/resolver_chain.rb +1 -1
- data/lib/rigor/type_node/string_literal.rb +1 -2
- data/lib/rigor/type_node/symbol_literal.rb +1 -2
- data/lib/rigor/type_node/union.rb +3 -6
- data/lib/rigor/value_semantics.rb +1 -10
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +2 -2
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +2 -3
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -1
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +38 -36
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +4 -2
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +6 -4
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -5
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +0 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +2 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +66 -19
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +4 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +3 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +2 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +21 -31
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +10 -16
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +2 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +0 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +5 -4
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +3 -4
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +1 -1
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +7 -12
- data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +1 -1
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +1 -1
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/symbolize_keys.rb +102 -0
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +19 -6
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +26 -13
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +3 -3
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +1 -3
- data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +6 -6
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +5 -6
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +1 -1
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/params_shape.rb +2 -3
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -8
- data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +0 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +10 -18
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +0 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +0 -2
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +4 -5
- data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +0 -2
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +6 -7
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +2 -4
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +2 -5
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -2
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +6 -6
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +3 -8
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +2 -3
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -8
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +1 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +4 -11
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -1
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +2 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +1 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +2 -2
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +3 -4
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +6 -8
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +3 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +4 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +6 -7
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +1 -1
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +2 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +17 -21
- data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +2 -3
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +3 -2
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +4 -4
- data/sig/rigor/analysis/baseline.rbs +0 -3
- data/sig/rigor/analysis/fact_store.rbs +1 -0
- data/sig/rigor/analysis/reachability/scan_cache.rbs +1 -1
- data/sig/rigor/cli/diff_command.rbs +1 -1
- data/sig/rigor/cli/explain_command.rbs +1 -1
- data/sig/rigor/effects/registry.rbs +2 -2
- data/sig/rigor/inference.rbs +4 -1
- data/sig/rigor/plugin/additional_initializer.rbs +0 -5
- data/sig/rigor/plugin/base.rbs +1 -0
- data/sig/rigor/plugin/effect_attribution.rbs +3 -3
- data/sig/rigor/plugin/effect_edge.rbs +1 -1
- data/sig/rigor/plugin/effect_entry_points.rbs +3 -3
- data/sig/rigor/plugin/io_boundary.rbs +2 -1
- data/sig/rigor/plugin/manifest.rbs +9 -9
- data/sig/rigor/plugin/protocol_contract.rbs +3 -7
- data/sig/rigor/scope.rbs +23 -3
- data/skills/rigor-plugin-author/SKILL.md +4 -4
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +5 -5
- data/skills/rigor-plugin-review/SKILL.md +3 -3
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +12 -1
- metadata +36 -3
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
require "optparse"
|
|
4
4
|
|
|
5
|
+
require "did_you_mean"
|
|
6
|
+
|
|
5
7
|
require_relative "../analysis/baseline"
|
|
6
8
|
require_relative "options"
|
|
7
9
|
require_relative "../analysis/runner"
|
|
@@ -24,7 +26,9 @@ module Rigor
|
|
|
24
26
|
# rigor baseline prune
|
|
25
27
|
class BaselineCommand < Command # rubocop:disable Metrics/ClassLength
|
|
26
28
|
EXIT_USAGE = 64
|
|
29
|
+
USAGE = "Usage: rigor baseline <subcommand> [options]"
|
|
27
30
|
DEFAULT_BASELINE_PATH = ".rigor-baseline.yml"
|
|
31
|
+
MAX_UNBASELINEABLE_FILES = 10
|
|
28
32
|
|
|
29
33
|
SUBCOMMANDS = %w[generate regenerate dump drift prune].freeze
|
|
30
34
|
|
|
@@ -41,6 +45,8 @@ module Rigor
|
|
|
41
45
|
when "prune" then run_prune
|
|
42
46
|
else
|
|
43
47
|
@err.puts("Unknown baseline subcommand: #{subcommand.inspect}")
|
|
48
|
+
nearest = ::DidYouMean::SpellChecker.new(dictionary: SUBCOMMANDS).correct(subcommand.to_s).first
|
|
49
|
+
@err.puts("Did you mean `rigor baseline #{nearest}`?") if nearest
|
|
44
50
|
@err.puts(help)
|
|
45
51
|
EXIT_USAGE
|
|
46
52
|
end
|
|
@@ -49,10 +55,78 @@ module Rigor
|
|
|
49
55
|
EXIT_USAGE
|
|
50
56
|
end
|
|
51
57
|
|
|
58
|
+
# The parser of `subcommand`, built without parsing anything (the catalogue reads it, ADR-126 WD3).
|
|
59
|
+
def option_parser(subcommand, options = {})
|
|
60
|
+
case subcommand
|
|
61
|
+
when "generate", "regenerate" then generate_parser(subcommand, options)
|
|
62
|
+
when "dump" then dump_parser(options)
|
|
63
|
+
when "drift" then drift_parser(options)
|
|
64
|
+
when "prune" then prune_parser(options)
|
|
65
|
+
else raise ArgumentError, "unknown baseline subcommand: #{subcommand.inspect}"
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
52
69
|
private
|
|
53
70
|
|
|
71
|
+
def generate_parser(subcommand, options)
|
|
72
|
+
OptionParser.new do |opts|
|
|
73
|
+
opts.banner = "Usage: rigor baseline #{subcommand} [options]"
|
|
74
|
+
Options.add_config(opts, options)
|
|
75
|
+
opts.on("--output=PATH", "Write baseline to PATH (default: #{DEFAULT_BASELINE_PATH})") do |v|
|
|
76
|
+
options[:output] = v
|
|
77
|
+
end
|
|
78
|
+
opts.on("--match-mode=MODE", %i[rule message],
|
|
79
|
+
"Row form: rule (default) or message") do |v|
|
|
80
|
+
options[:match_mode] = v
|
|
81
|
+
end
|
|
82
|
+
# `regenerate` always overwrites — no `--force` to offer.
|
|
83
|
+
if subcommand == "generate"
|
|
84
|
+
opts.on("--force", "Overwrite an existing baseline file") { options[:force] = true }
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def dump_parser(options)
|
|
90
|
+
OptionParser.new do |opts|
|
|
91
|
+
opts.banner = "Usage: rigor baseline dump [options]"
|
|
92
|
+
opts.on("--baseline=PATH", "Path to the baseline file (default: #{DEFAULT_BASELINE_PATH})") do |v|
|
|
93
|
+
options[:baseline] = v
|
|
94
|
+
end
|
|
95
|
+
Options.add_format(opts, options, "Output format: text (default) or json")
|
|
96
|
+
opts.on("--rule=RULE", "Filter rows by exact rule id") { |v| options[:rule] = v }
|
|
97
|
+
opts.on("--file=GLOB", "Filter rows by File.fnmatch? glob") { |v| options[:file] = v }
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def drift_parser(options)
|
|
102
|
+
OptionParser.new do |opts|
|
|
103
|
+
opts.banner = "Usage: rigor baseline drift [options]"
|
|
104
|
+
Options.add_config(opts, options)
|
|
105
|
+
opts.on("--baseline=PATH", "Path to the baseline file (default: #{DEFAULT_BASELINE_PATH})") do |v|
|
|
106
|
+
options[:baseline] = v
|
|
107
|
+
end
|
|
108
|
+
opts.on("--only=STATUS", %i[within over cleared reducible],
|
|
109
|
+
"Show only buckets with the given status (within|over|cleared|reducible)") do |v|
|
|
110
|
+
options[:only] = v
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def prune_parser(options)
|
|
116
|
+
OptionParser.new do |opts|
|
|
117
|
+
opts.banner = "Usage: rigor baseline prune [options]"
|
|
118
|
+
Options.add_config(opts, options)
|
|
119
|
+
opts.on("--baseline=PATH", "Path to the baseline file (default: #{DEFAULT_BASELINE_PATH})") do |v|
|
|
120
|
+
options[:baseline] = v
|
|
121
|
+
end
|
|
122
|
+
opts.on("--dry-run", "Show what would be dropped without writing the file") { options[:dry_run] = true }
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
54
126
|
def run_generate
|
|
55
127
|
options = parse_generate_options
|
|
128
|
+
return positional_paths_error("generate") unless @argv.empty?
|
|
129
|
+
|
|
56
130
|
path = options.fetch(:output)
|
|
57
131
|
|
|
58
132
|
if File.exist?(path) && !options.fetch(:force)
|
|
@@ -69,9 +143,32 @@ module Rigor
|
|
|
69
143
|
# so `rigor baseline regenerate` reads cleanly as "make the baseline match reality again".
|
|
70
144
|
def run_regenerate
|
|
71
145
|
options = parse_generate_options(subcommand: "regenerate")
|
|
146
|
+
return positional_paths_error("regenerate") unless @argv.empty?
|
|
147
|
+
|
|
72
148
|
write_baseline(options, verb: "regenerated baseline")
|
|
73
149
|
end
|
|
74
150
|
|
|
151
|
+
# Issues #2466 and #2577 — the baseline covers the configured `paths:`, never the positional arguments: a
|
|
152
|
+
# baseline built over a subset would silently drop the entries of every other file. `drift` and `prune`
|
|
153
|
+
# analyse the configured `paths:` and `dump` prints the whole file, so none of the five reads a path argument.
|
|
154
|
+
# Reject them, as `rigor effects update` does (`effects_snapshot_command.rb:54`), rather than honour them.
|
|
155
|
+
def positional_paths_error(subcommand)
|
|
156
|
+
@err.puts("rigor baseline #{subcommand}: unexpected argument(s) #{@argv.map(&:inspect).join(' ')}; " \
|
|
157
|
+
"#{positional_paths_hint(subcommand)}")
|
|
158
|
+
EXIT_USAGE
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def positional_paths_hint(subcommand)
|
|
162
|
+
case subcommand
|
|
163
|
+
when "dump"
|
|
164
|
+
"dump reads the baseline file, so pass none: use --file=GLOB to filter rows or --baseline=PATH to pick a file"
|
|
165
|
+
when "drift", "prune"
|
|
166
|
+
"#{subcommand} audits the configured `paths:`, so pass none"
|
|
167
|
+
else
|
|
168
|
+
"the baseline is generated from the configured `paths:`, so pass none"
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
|
|
75
172
|
def write_baseline(options, verb:)
|
|
76
173
|
path = options.fetch(:output)
|
|
77
174
|
configuration = Configuration.load(options.fetch(:config))
|
|
@@ -81,11 +178,13 @@ module Rigor
|
|
|
81
178
|
project_root: Dir.pwd)
|
|
82
179
|
File.write(path, baseline.to_yaml)
|
|
83
180
|
|
|
181
|
+
covered = baseline.buckets.sum(&:count)
|
|
84
182
|
@err.puts(
|
|
85
183
|
"rigor: #{verb} #{path} " \
|
|
86
|
-
"(#{baseline.size} bucket(s) covering #{
|
|
184
|
+
"(#{baseline.size} bucket(s) covering #{covered} diagnostic(s); " \
|
|
87
185
|
"match-mode: #{options.fetch(:match_mode)})"
|
|
88
186
|
)
|
|
187
|
+
report_unbaselineable(Analysis::Baseline.unkeyable(diagnostics))
|
|
89
188
|
if configuration.baseline_path.nil?
|
|
90
189
|
# Name the config file that actually governs this project (`.rigor.dist.yml` when no local override
|
|
91
190
|
# exists), so the hint points at the file the user must edit.
|
|
@@ -98,6 +197,38 @@ module Rigor
|
|
|
98
197
|
0
|
|
99
198
|
end
|
|
100
199
|
|
|
200
|
+
# Diagnostics the baseline cannot key never enter the file, so the next `rigor check` surfaces them. Say so,
|
|
201
|
+
# with how many, why and where; the exit status stays 0 (the file was written as asked).
|
|
202
|
+
def report_unbaselineable(unkeyable)
|
|
203
|
+
return if unkeyable.empty?
|
|
204
|
+
|
|
205
|
+
no_rule, no_path = unkeyable.partition { |d| d.qualified_rule.nil? }
|
|
206
|
+
files = unbaselineable_files(no_rule)
|
|
207
|
+
parts = []
|
|
208
|
+
unless no_rule.empty?
|
|
209
|
+
parts << "#{no_rule.size} diagnostic(s) have no rule (parse, path or internal errors) " \
|
|
210
|
+
"in #{files.size} file(s)"
|
|
211
|
+
end
|
|
212
|
+
parts << "#{no_path.size} diagnostic(s) have no path" unless no_path.empty?
|
|
213
|
+
listing = files.empty? ? "" : ":"
|
|
214
|
+
@err.puts("rigor: #{parts.join('; ')} and cannot be baselined; " \
|
|
215
|
+
"fix them, or remove or exclude the files#{listing}")
|
|
216
|
+
files.first(MAX_UNBASELINEABLE_FILES).each { |f| @err.puts(" #{f}") }
|
|
217
|
+
extra = files.size - MAX_UNBASELINEABLE_FILES
|
|
218
|
+
@err.puts(" ... and #{extra} more file(s)") if extra.positive?
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def unbaselineable_files(diagnostics)
|
|
222
|
+
root = Pathname.new(Dir.pwd)
|
|
223
|
+
diagnostics.filter_map(&:path).map { |p| relative_to(root, p) }.uniq.sort
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def relative_to(root, path)
|
|
227
|
+
Pathname.new(path).relative_path_from(root).to_s
|
|
228
|
+
rescue ArgumentError
|
|
229
|
+
path
|
|
230
|
+
end
|
|
231
|
+
|
|
101
232
|
def parse_generate_options(subcommand: "generate")
|
|
102
233
|
options = {
|
|
103
234
|
config: nil,
|
|
@@ -105,22 +236,7 @@ module Rigor
|
|
|
105
236
|
match_mode: :rule,
|
|
106
237
|
force: false
|
|
107
238
|
}
|
|
108
|
-
|
|
109
|
-
opts.banner = "Usage: rigor baseline #{subcommand} [options]"
|
|
110
|
-
Options.add_config(opts, options)
|
|
111
|
-
opts.on("--output=PATH", "Write baseline to PATH (default: #{DEFAULT_BASELINE_PATH})") do |v|
|
|
112
|
-
options[:output] = v
|
|
113
|
-
end
|
|
114
|
-
opts.on("--match-mode=MODE", %i[rule message],
|
|
115
|
-
"Row form: rule (default) or message") do |v|
|
|
116
|
-
options[:match_mode] = v
|
|
117
|
-
end
|
|
118
|
-
# `regenerate` always overwrites — no `--force` to offer.
|
|
119
|
-
if subcommand == "generate"
|
|
120
|
-
opts.on("--force", "Overwrite an existing baseline file") { options[:force] = true }
|
|
121
|
-
end
|
|
122
|
-
end
|
|
123
|
-
parser.parse!(@argv)
|
|
239
|
+
generate_parser(subcommand, options).parse!(@argv)
|
|
124
240
|
options
|
|
125
241
|
end
|
|
126
242
|
|
|
@@ -139,8 +255,6 @@ module Rigor
|
|
|
139
255
|
runner.run(configuration_for_generation.paths).diagnostics
|
|
140
256
|
end
|
|
141
257
|
|
|
142
|
-
# The loaded configuration with only `baseline` disabled (#1777): any other key can change the
|
|
143
|
-
# diagnostic set, and the baseline must record the set `rigor check` will then report.
|
|
144
258
|
def override_configuration_baseline_off(configuration)
|
|
145
259
|
configuration.without_baseline
|
|
146
260
|
end
|
|
@@ -149,31 +263,22 @@ module Rigor
|
|
|
149
263
|
|
|
150
264
|
def run_dump
|
|
151
265
|
options = parse_dump_options
|
|
266
|
+
return positional_paths_error("dump") unless @argv.empty?
|
|
267
|
+
|
|
152
268
|
baseline = load_baseline_or_exit(options.fetch(:baseline))
|
|
153
269
|
return EXIT_USAGE if baseline == :error
|
|
154
270
|
|
|
155
271
|
rows = filter_dump_rows(baseline.buckets, options)
|
|
156
272
|
case options.fetch(:format)
|
|
157
|
-
when
|
|
273
|
+
when "json" then @out.puts(JSON.pretty_generate(dump_to_json(rows)))
|
|
158
274
|
else dump_text(rows, options)
|
|
159
275
|
end
|
|
160
276
|
0
|
|
161
277
|
end
|
|
162
278
|
|
|
163
279
|
def parse_dump_options
|
|
164
|
-
options = { baseline: DEFAULT_BASELINE_PATH, format:
|
|
165
|
-
|
|
166
|
-
opts.banner = "Usage: rigor baseline dump [options]"
|
|
167
|
-
opts.on("--baseline=PATH", "Path to the baseline file (default: #{DEFAULT_BASELINE_PATH})") do |v|
|
|
168
|
-
options[:baseline] = v
|
|
169
|
-
end
|
|
170
|
-
opts.on("--format=FORMAT", %i[text json], "Output format: text (default) or json") do |v|
|
|
171
|
-
options[:format] = v
|
|
172
|
-
end
|
|
173
|
-
opts.on("--rule=RULE", "Filter rows by exact rule id") { |v| options[:rule] = v }
|
|
174
|
-
opts.on("--file=GLOB", "Filter rows by File.fnmatch? glob") { |v| options[:file] = v }
|
|
175
|
-
end
|
|
176
|
-
parser.parse!(@argv)
|
|
280
|
+
options = { baseline: DEFAULT_BASELINE_PATH, format: "text", rule: nil, file: nil }
|
|
281
|
+
dump_parser(options).parse!(@argv)
|
|
177
282
|
options
|
|
178
283
|
end
|
|
179
284
|
|
|
@@ -226,6 +331,8 @@ module Rigor
|
|
|
226
331
|
|
|
227
332
|
def run_drift
|
|
228
333
|
options = parse_drift_options
|
|
334
|
+
return positional_paths_error("drift") unless @argv.empty?
|
|
335
|
+
|
|
229
336
|
baseline = load_baseline_or_exit(options.fetch(:baseline))
|
|
230
337
|
return EXIT_USAGE if baseline == :error
|
|
231
338
|
|
|
@@ -254,18 +361,7 @@ module Rigor
|
|
|
254
361
|
baseline: DEFAULT_BASELINE_PATH,
|
|
255
362
|
only: nil
|
|
256
363
|
}
|
|
257
|
-
|
|
258
|
-
opts.banner = "Usage: rigor baseline drift [options]"
|
|
259
|
-
Options.add_config(opts, options)
|
|
260
|
-
opts.on("--baseline=PATH", "Path to the baseline file (default: #{DEFAULT_BASELINE_PATH})") do |v|
|
|
261
|
-
options[:baseline] = v
|
|
262
|
-
end
|
|
263
|
-
opts.on("--only=STATUS", %i[within over cleared reducible],
|
|
264
|
-
"Show only buckets with the given status (within|over|cleared|reducible)") do |v|
|
|
265
|
-
options[:only] = v
|
|
266
|
-
end
|
|
267
|
-
end
|
|
268
|
-
parser.parse!(@argv)
|
|
364
|
+
drift_parser(options).parse!(@argv)
|
|
269
365
|
options
|
|
270
366
|
end
|
|
271
367
|
|
|
@@ -300,6 +396,8 @@ module Rigor
|
|
|
300
396
|
|
|
301
397
|
def run_prune
|
|
302
398
|
options = parse_prune_options
|
|
399
|
+
return positional_paths_error("prune") unless @argv.empty?
|
|
400
|
+
|
|
303
401
|
baseline = load_baseline_or_exit(options.fetch(:baseline))
|
|
304
402
|
return EXIT_USAGE if baseline == :error
|
|
305
403
|
|
|
@@ -328,15 +426,7 @@ module Rigor
|
|
|
328
426
|
baseline: DEFAULT_BASELINE_PATH,
|
|
329
427
|
dry_run: false
|
|
330
428
|
}
|
|
331
|
-
|
|
332
|
-
opts.banner = "Usage: rigor baseline prune [options]"
|
|
333
|
-
Options.add_config(opts, options)
|
|
334
|
-
opts.on("--baseline=PATH", "Path to the baseline file (default: #{DEFAULT_BASELINE_PATH})") do |v|
|
|
335
|
-
options[:baseline] = v
|
|
336
|
-
end
|
|
337
|
-
opts.on("--dry-run", "Show what would be dropped without writing the file") { options[:dry_run] = true }
|
|
338
|
-
end
|
|
339
|
-
parser.parse!(@argv)
|
|
429
|
+
prune_parser(options).parse!(@argv)
|
|
340
430
|
options
|
|
341
431
|
end
|
|
342
432
|
|
|
@@ -362,7 +452,7 @@ module Rigor
|
|
|
362
452
|
|
|
363
453
|
def help
|
|
364
454
|
<<~HELP
|
|
365
|
-
|
|
455
|
+
#{USAGE}
|
|
366
456
|
|
|
367
457
|
Subcommands:
|
|
368
458
|
generate Write a fresh baseline file from a `rigor check` run.
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "optparse"
|
|
4
|
+
|
|
5
|
+
require_relative "options"
|
|
6
|
+
require_relative "../version"
|
|
7
|
+
require_relative "json_document"
|
|
8
|
+
|
|
9
|
+
module Rigor
|
|
10
|
+
class CLI
|
|
11
|
+
# The machine-readable description of the CLI surface (ADR-126 WD3): one entry per command, with the one-line
|
|
12
|
+
# summary, usage, subcommands, flags, accepted formats and the MCP tool that mirrors it.
|
|
13
|
+
#
|
|
14
|
+
# Nothing here is a second hand-written list of flags. The commands that parse with an `OptionParser` expose the
|
|
15
|
+
# parser they build (`#option_parser`, built without parsing anything) and the catalogue reads its switches; the
|
|
16
|
+
# commands that read `argv` by hand declare theirs in {DECLARED}. The only prose kept here is the one-line
|
|
17
|
+
# {SUMMARIES}, which `rigor help` prints, so the text help and `rigor help --format=json` cannot disagree about
|
|
18
|
+
# which commands exist. `spec/rigor/cli/catalogue_spec.rb` gates the names against `CLI::HANDLERS`.
|
|
19
|
+
module Catalogue
|
|
20
|
+
SCHEMA_VERSION = JsonDocument::SCHEMA_VERSION
|
|
21
|
+
|
|
22
|
+
# `help` and `version` are dispatched by `CLI#run` itself, outside `CLI::HANDLERS`.
|
|
23
|
+
BUILTINS = %w[help version].freeze
|
|
24
|
+
|
|
25
|
+
# The `rigor help` listing, in print order.
|
|
26
|
+
SUMMARIES = {
|
|
27
|
+
"check" => "Analyze Ruby source files",
|
|
28
|
+
"init" => "Create a starter .rigor.yml",
|
|
29
|
+
"annotate" => "Print FILE with each line's last-expression type",
|
|
30
|
+
"type-of" => "Print inferred types at FILE:LINE[:COL] positions",
|
|
31
|
+
"lens" => "Map a file's declarations with their types and provenance (experimental)",
|
|
32
|
+
"trace" => "Replay how the engine typed FILE as a terminal animation",
|
|
33
|
+
"type-scan" => "Report Scope#type_of coverage across PATHs",
|
|
34
|
+
"effects" => "Report each method's effect labels, and the committed effect snapshot " \
|
|
35
|
+
"(opt-in; effects update/check/diff/explain)",
|
|
36
|
+
"explain" => "Print the description of one or all CheckRules",
|
|
37
|
+
"diff" => "Compare current diagnostics to a saved `rigor check --format=json` result",
|
|
38
|
+
"baseline" => "Manage the baseline file (baseline generate/regenerate/dump/drift/prune)",
|
|
39
|
+
"sig-gen" => "Emit RBS skeletons inferred from .rb sources",
|
|
40
|
+
"lsp" => "Run the Rigor Language Server (LSP) over stdio",
|
|
41
|
+
"mcp" => "Run the Rigor MCP server over stdio",
|
|
42
|
+
"triage" => "Summarise diagnostics: distribution, hotspots, hints",
|
|
43
|
+
"coverage" => "Report type-precision coverage (precise vs Dynamic ratio)",
|
|
44
|
+
"unused" => "Report unreferenced classes, modules and constants as removal candidates",
|
|
45
|
+
"plugins" => "Report activation status of every configured plugin",
|
|
46
|
+
"plugin" => "Browse bundled plugin source as worked examples (plugin <name>, plugin --list)",
|
|
47
|
+
"playground" => "Start the browser playground (requires rigor-playground gem)",
|
|
48
|
+
"describe" => "Recommend the next skill for this project (alias for `skill describe`)",
|
|
49
|
+
"skill" => "Recommend the next skill + list/print bundled Agent Skills (skill describe, skill <name>)",
|
|
50
|
+
"docs" => "Print the bundled docs offline (docs <name>, docs --list)",
|
|
51
|
+
"show-bleedingedge" => "Show the bleeding-edge overlay + what your config adopts",
|
|
52
|
+
"doctor" => "Classify setup problems vs clean run with routed next actions",
|
|
53
|
+
"upgrade" => "Migration command skeleton (queued)",
|
|
54
|
+
"version" => "Print the Rigor version",
|
|
55
|
+
"help" => "Print this help, or a command's help (help <command>, help --format=json)"
|
|
56
|
+
}.freeze
|
|
57
|
+
|
|
58
|
+
# The commands whose `argv` is read by hand, so there is no parser to read: their switches, subcommands and
|
|
59
|
+
# formats are stated here. `playground` is delegated to the optional `rigor-playground` gem and is listed without
|
|
60
|
+
# flags.
|
|
61
|
+
DECLARED = {
|
|
62
|
+
"describe" => {
|
|
63
|
+
usage: "Usage: rigor describe [--deep]",
|
|
64
|
+
flags: [
|
|
65
|
+
{ name: "--deep", arg: nil, desc: "Run `rigor check` first and route the recommendation on its result" }
|
|
66
|
+
]
|
|
67
|
+
},
|
|
68
|
+
"docs" => {
|
|
69
|
+
usage: "Usage: rigor docs [<name>] [--path <name>] [--list [<category>]]",
|
|
70
|
+
flags: [
|
|
71
|
+
{ name: "--path", arg: "NAME", desc: "Print the absolute path of a doc" },
|
|
72
|
+
{ name: "--list", arg: "CATEGORY", optional: true, desc: "List bundled docs (optionally one category)" }
|
|
73
|
+
]
|
|
74
|
+
},
|
|
75
|
+
"playground" => { usage: "Usage: rigor playground [options]" },
|
|
76
|
+
"version" => { usage: "Usage: rigor version" }
|
|
77
|
+
}.freeze
|
|
78
|
+
|
|
79
|
+
# The command files that build an `OptionParser`; the command class is the file's name in camel case.
|
|
80
|
+
PARSER_FILES = %w[
|
|
81
|
+
check annotate type_of lens trace type_scan effects explain diff sig_gen lsp mcp baseline triage coverage unused
|
|
82
|
+
plugins show_bleedingedge doctor skill plugin
|
|
83
|
+
].to_h { |file| [file.tr("_", "-"), "#{file}_command"] }.freeze
|
|
84
|
+
|
|
85
|
+
# The commands that have no parser of their own at the top, so their usage is their `USAGE` constant.
|
|
86
|
+
USAGE_FILES = { "baseline" => "baseline_command", "upgrade" => "upgrade_command" }.freeze
|
|
87
|
+
|
|
88
|
+
Ractor.make_shareable(SUMMARIES)
|
|
89
|
+
Ractor.make_shareable(USAGE_FILES)
|
|
90
|
+
Ractor.make_shareable(DECLARED)
|
|
91
|
+
Ractor.make_shareable(PARSER_FILES)
|
|
92
|
+
|
|
93
|
+
module_function
|
|
94
|
+
|
|
95
|
+
# Every command name the CLI dispatches, in `rigor help` order.
|
|
96
|
+
def names
|
|
97
|
+
SUMMARIES.keys - BUILTINS + BUILTINS
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# The `rigor help` text, rendered from {SUMMARIES} so its list is the catalogue's.
|
|
101
|
+
def help_text
|
|
102
|
+
width = names.map(&:length).max + 2
|
|
103
|
+
lines = names.map { |name| " #{name.ljust(width)}#{SUMMARIES.fetch(name)}" }
|
|
104
|
+
<<~HELP
|
|
105
|
+
Usage: rigor <command> [options]
|
|
106
|
+
|
|
107
|
+
Commands:
|
|
108
|
+
#{lines.join("\n")}
|
|
109
|
+
|
|
110
|
+
Run `rigor help <command>` (or `rigor <command> --help`) for a command's options.
|
|
111
|
+
Run `rigor describe` for the next step on this project, or `rigor help --format=json` for the full surface.
|
|
112
|
+
HELP
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# All entries, as plain hashes ready for JSON.
|
|
116
|
+
def entries
|
|
117
|
+
names.map { |name| entry(name) }
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# The document `rigor help --format=json` prints.
|
|
121
|
+
def document
|
|
122
|
+
{ "schema_version" => SCHEMA_VERSION, "version" => Rigor::VERSION, "commands" => entries }
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def entry(name)
|
|
126
|
+
declared = DECLARED.fetch(name, {})
|
|
127
|
+
parsed = parsed_entry(name)
|
|
128
|
+
{
|
|
129
|
+
"name" => name,
|
|
130
|
+
"summary" => SUMMARIES.fetch(name),
|
|
131
|
+
"usage" => declared[:usage] || parsed[:usage] || command_usage(name),
|
|
132
|
+
"subcommands" => parsed[:subcommands] || declared_subcommands(declared),
|
|
133
|
+
"flags" => parsed[:flags] || declared_flags(declared),
|
|
134
|
+
"formats" => parsed[:formats] || [],
|
|
135
|
+
"mcp_tool" => mcp_tool(name)
|
|
136
|
+
}
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# The `OptionParser` view of a command: its own flags and formats at the top, and under `subcommands` the same
|
|
140
|
+
# shape for each subcommand's parser. A command with no parser of its own (`baseline`) has empty top-level
|
|
141
|
+
# `flags` and `formats`; none of them is a union over its subcommands.
|
|
142
|
+
def parsed_entry(name)
|
|
143
|
+
pairs = parser_pairs(name)
|
|
144
|
+
return {} if pairs.nil?
|
|
145
|
+
|
|
146
|
+
own = pairs.find { |sub, _| sub.nil? }&.last
|
|
147
|
+
subs = pairs.reject { |sub, _| sub.nil? }
|
|
148
|
+
top = own ? describe_parser(own) : { flags: [], formats: [] }
|
|
149
|
+
return top if subs.empty?
|
|
150
|
+
|
|
151
|
+
top.merge(subcommands: subs.map do |sub, parser|
|
|
152
|
+
{ "name" => sub }.merge(stringify_keys(describe_parser(parser)))
|
|
153
|
+
end)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# The `[subcommand, parser]` pairs of a command (no name for its own parser), or nil when it has no parser.
|
|
157
|
+
def parser_pairs(name)
|
|
158
|
+
case name
|
|
159
|
+
when "init" then [[nil, CLI.init_option_parser]]
|
|
160
|
+
when "help" then [[nil, CLI.help_option_parser]]
|
|
161
|
+
else command_parser_pairs(name)
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def command_parser_pairs(name)
|
|
166
|
+
file = PARSER_FILES[name]
|
|
167
|
+
return nil unless file
|
|
168
|
+
|
|
169
|
+
require_relative file
|
|
170
|
+
klass = CLI.const_get(file.split("_").map(&:capitalize).join)
|
|
171
|
+
case name
|
|
172
|
+
when "effects" then effects_parser_pairs(klass)
|
|
173
|
+
when "skill"
|
|
174
|
+
[[nil, klass.new(argv: []).option_parser],
|
|
175
|
+
["describe", klass.describe_option_parser("Usage: rigor skill describe [--deep]")]]
|
|
176
|
+
when "baseline"
|
|
177
|
+
command = klass.new(argv: [])
|
|
178
|
+
klass::SUBCOMMANDS.map { |sub| [sub, command.option_parser(sub)] }
|
|
179
|
+
else [[nil, klass.new(argv: []).option_parser]]
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def effects_parser_pairs(klass)
|
|
184
|
+
[[nil, klass.new(argv: []).option_parser]] +
|
|
185
|
+
EffectsSnapshotCommand::VERBS.map do |verb|
|
|
186
|
+
[verb, EffectsSnapshotCommand.new(argv: [], verb: verb).option_parser]
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# The usage line of a command with no parser to take a banner from, read from the command's own `USAGE`.
|
|
191
|
+
def command_usage(name)
|
|
192
|
+
file = USAGE_FILES[name]
|
|
193
|
+
return nil unless file
|
|
194
|
+
|
|
195
|
+
require_relative file
|
|
196
|
+
CLI.const_get(file.split("_").map(&:capitalize).join)::USAGE
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def declared_flags(declared)
|
|
200
|
+
(declared[:flags] || []).map do |flag|
|
|
201
|
+
{ "name" => flag.fetch(:name), "arg" => flag.fetch(:arg), "optional" => flag.fetch(:optional, false),
|
|
202
|
+
"desc" => flag.fetch(:desc) }
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def declared_subcommands(declared)
|
|
207
|
+
(declared[:subcommands] || []).map do |sub|
|
|
208
|
+
{ "name" => sub.fetch(:name), "usage" => sub.fetch(:usage), "flags" => [], "formats" => [] }
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# Reads usage, flags and formats from the switches of `parser`.
|
|
213
|
+
def describe_parser(parser)
|
|
214
|
+
flags = parser.top.list.flat_map { |switch| flags_of(switch) }
|
|
215
|
+
format_switch = parser.top.list.any? { |switch| switch.long.first == "--format" }
|
|
216
|
+
{ usage: parser.banner.to_s.lines.first&.strip, flags: flags,
|
|
217
|
+
formats: format_switch ? Options.accepted_formats(parser) || [] : [] }
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# A switch's literal flags: `--[no-]color` is the two flags `--color` and `--no-color`.
|
|
221
|
+
def flags_of(switch)
|
|
222
|
+
long = switch.long.first
|
|
223
|
+
return [] unless long
|
|
224
|
+
|
|
225
|
+
arg, optional = arg_of(switch.arg)
|
|
226
|
+
desc = switch.desc.join(" ")
|
|
227
|
+
flag_names(long).map { |flag| { "name" => flag, "arg" => arg, "optional" => optional, "desc" => desc } }
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# `[arg, optional]`: `=[LIST]` is the argument `LIST`, optional.
|
|
231
|
+
def arg_of(raw)
|
|
232
|
+
arg = raw&.sub(/\A[= ]/, "")
|
|
233
|
+
optional = !arg.nil? && arg.start_with?("[") && arg.end_with?("]")
|
|
234
|
+
[optional ? arg[1..-2] : arg, optional]
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def flag_names(long)
|
|
238
|
+
return [long] unless long.start_with?("--[no-]")
|
|
239
|
+
|
|
240
|
+
bare = long.delete_prefix("--[no-]")
|
|
241
|
+
["--#{bare}", "--no-#{bare}"]
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# `rigor_check` mirrors `check`, `rigor_sig_gen` mirrors `sig-gen`: a tool is named for its command.
|
|
245
|
+
def mcp_tool(name)
|
|
246
|
+
require_relative "../mcp/server"
|
|
247
|
+
tool = "rigor_#{name.tr('-', '_')}"
|
|
248
|
+
tool if MCP::Server::TOOLS.any? { |candidate| candidate.fetch(:name) == tool }
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def stringify_keys(hash)
|
|
252
|
+
hash.transform_keys(&:to_s)
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
end
|