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
|
@@ -17,8 +17,9 @@ require_relative "command"
|
|
|
17
17
|
require_relative "options"
|
|
18
18
|
require_relative "diagnostic_formats"
|
|
19
19
|
require_relative "../ci_detector"
|
|
20
|
+
require_relative "json_document"
|
|
20
21
|
# ADR-87 WD4 — `coverage_scan` and `check_runner_factory` both pull the inference engine, so they are required
|
|
21
|
-
# lazily (in `
|
|
22
|
+
# lazily (in `invoke_check` / `compute_coverage`) rather than at load time: an ordinary check whose
|
|
22
23
|
# result the run-cache probe serves must reach the hit verdict without `rigor/inference` in $LOADED_FEATURES.
|
|
23
24
|
|
|
24
25
|
module Rigor
|
|
@@ -92,6 +93,97 @@ module Rigor
|
|
|
92
93
|
exit_code
|
|
93
94
|
end
|
|
94
95
|
|
|
96
|
+
# The parser this command parses with, built without parsing anything: the catalogue reads the
|
|
97
|
+
# flags and formats from it (ADR-126 WD3). `options` is only written by the switches' blocks.
|
|
98
|
+
def option_parser(options = {}) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
99
|
+
OptionParser.new do |opts| # rubocop:disable Metrics/BlockLength
|
|
100
|
+
opts.banner = "Usage: rigor check [options] [paths]"
|
|
101
|
+
Options.add_config(opts, options)
|
|
102
|
+
Options.add_format(opts, options,
|
|
103
|
+
"Output format: text, json, sarif, github, gitlab, checkstyle, junit, teamcity",
|
|
104
|
+
formats: %w[text json] + DiagnosticFormats::FORMATS)
|
|
105
|
+
opts.on("--explain", "Surface fail-soft fallback events as :info diagnostics") { options[:explain] = true }
|
|
106
|
+
opts.on("--cache-stats", "Print on-disk cache inventory at end of run") { options[:cache_stats] = true }
|
|
107
|
+
opts.on("--coverage",
|
|
108
|
+
"Add a type-precision coverage block (an extra precision pass over the analyzed files)") do
|
|
109
|
+
options[:coverage] = true
|
|
110
|
+
end
|
|
111
|
+
opts.on("--clear-cache", "Remove the .rigor/cache directory before running") { options[:clear_cache] = true }
|
|
112
|
+
opts.on("--no-cache",
|
|
113
|
+
"Disable the persistent cache for this run (with --incremental: no snapshot is read or written)") do
|
|
114
|
+
options[:no_cache] = true
|
|
115
|
+
end
|
|
116
|
+
opts.on("--[no-]stats",
|
|
117
|
+
"Print run summary (files, classes, memory, wall time) to stderr (default: on)") do |value|
|
|
118
|
+
options[:stats] = value
|
|
119
|
+
end
|
|
120
|
+
opts.on("--workers=N", Integer,
|
|
121
|
+
"Dispatch per-file analysis across N Ractor workers (default: 0; sequential)") do |value|
|
|
122
|
+
options[:workers] = value
|
|
123
|
+
end
|
|
124
|
+
Options.add_editor_mode(opts, options)
|
|
125
|
+
opts.on("--baseline=PATH",
|
|
126
|
+
"load baseline from PATH (overrides .rigor.yml `baseline:`)") do |value|
|
|
127
|
+
options[:baseline] = value
|
|
128
|
+
end
|
|
129
|
+
opts.on("--no-baseline",
|
|
130
|
+
"ignore any configured baseline for this run") do
|
|
131
|
+
options[:baseline] = false
|
|
132
|
+
end
|
|
133
|
+
opts.on("--baseline-strict",
|
|
134
|
+
"fail the run on any baseline drift (CI gate)") do
|
|
135
|
+
options[:baseline_strict] = true
|
|
136
|
+
end
|
|
137
|
+
opts.on("--fail-on=SEVERITY",
|
|
138
|
+
"exit non-zero on a diagnostic at or above SEVERITY: error (default), warning, or info") do |value|
|
|
139
|
+
# Validated post-parse (`valid_fail_on_option?`, called from `run`) rather than here — see its
|
|
140
|
+
# comment for why an unrecognised value is not raised from inside this block.
|
|
141
|
+
options[:fail_on] = value.to_s.downcase.to_sym
|
|
142
|
+
end
|
|
143
|
+
opts.on("--treat-all-as-inline-rbs",
|
|
144
|
+
"force-load rigor-rbs-inline with require_magic_comment: false") do
|
|
145
|
+
options[:treat_all_as_inline_rbs] = true
|
|
146
|
+
end
|
|
147
|
+
opts.on("--verify-incremental",
|
|
148
|
+
"assert incremental analysis matches a full run, then exit") do
|
|
149
|
+
options[:verify_incremental] = true
|
|
150
|
+
end
|
|
151
|
+
opts.on("--verify-incremental-edits=N", Integer,
|
|
152
|
+
"with --verify-incremental, also replay edits (empty, class/module skeleton, up to 2 " \
|
|
153
|
+
"one-statement drops) on N files") do |value|
|
|
154
|
+
if value.negative?
|
|
155
|
+
raise OptionParser::InvalidArgument, "--verify-incremental-edits=#{value} (must be 0 or more)"
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
options[:verify_incremental] = true
|
|
159
|
+
options[:verify_incremental_edits] = value
|
|
160
|
+
end
|
|
161
|
+
opts.on("--incremental",
|
|
162
|
+
"re-analyze only files changed since the last run (cross-process cache)") do
|
|
163
|
+
options[:incremental] = true
|
|
164
|
+
end
|
|
165
|
+
opts.on("--no-ci-detect",
|
|
166
|
+
"do not auto-emit CI-native output when a CI environment is detected") do
|
|
167
|
+
options[:ci_detect] = false
|
|
168
|
+
end
|
|
169
|
+
# ADR-50 § WD2 — `=[LIST]` (not ` [LIST]`) so a bare `--bleeding-edge` never swallows a following positional
|
|
170
|
+
# path: `rigor check --bleeding-edge lib` adopts the whole overlay and checks `lib`.
|
|
171
|
+
opts.on("--bleeding-edge=[LIST]",
|
|
172
|
+
"adopt the bleeding-edge overlay for this run " \
|
|
173
|
+
"(all features, or a comma-separated feature-id list)") do |value|
|
|
174
|
+
options[:bleeding_edge] = value.nil? || value.split(",").map(&:strip).reject(&:empty?)
|
|
175
|
+
end
|
|
176
|
+
opts.on("--no-bleeding-edge",
|
|
177
|
+
"ignore any configured bleeding_edge: selection for this run") do
|
|
178
|
+
options[:bleeding_edge] = false
|
|
179
|
+
end
|
|
180
|
+
opts.on("--no-tolerated-effects",
|
|
181
|
+
"check effect envelopes as if effects.tolerated: were empty") do
|
|
182
|
+
options[:no_tolerated_effects] = true
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
95
187
|
private
|
|
96
188
|
|
|
97
189
|
# True when `diagnostics` (already baseline-filtered and severity-resolved — the same list
|
|
@@ -219,10 +311,9 @@ module Rigor
|
|
|
219
311
|
exit_code
|
|
220
312
|
end
|
|
221
313
|
|
|
222
|
-
# ADR-46 — the two incremental-analysis check modes
|
|
223
|
-
# short-circuits
|
|
224
|
-
#
|
|
225
|
-
# also returns nil when it declines (no reusable snapshot), so the run falls back to single-file scope.
|
|
314
|
+
# ADR-46 — the two incremental-analysis check modes handle the run and return its exit code, so `run`
|
|
315
|
+
# short-circuits. Returns nil for an ordinary check, and for editor mode option B when it declines (no reusable
|
|
316
|
+
# snapshot), so the run falls back to single-file scope.
|
|
226
317
|
def dispatch_special_check_mode(configuration, options, cache_root, buffer)
|
|
227
318
|
if options.fetch(:verify_incremental)
|
|
228
319
|
# The gate compares an incremental recheck against a full-run oracle, and a buffer makes the two
|
|
@@ -283,8 +374,10 @@ module Rigor
|
|
|
283
374
|
|
|
284
375
|
# Issue #1967 — the partition check above re-analyses an unchanged tree, so every served row is still right and a
|
|
285
376
|
# missing invalidation edge goes unseen. Here each of `--verify-incremental-edits` sampled files is edited through
|
|
286
|
-
# an editor buffer (never on disk) by each of {Analysis::EditReplay.edits} — emptied,
|
|
287
|
-
# `class` / `module` skeleton
|
|
377
|
+
# an editor buffer (never on disk) by each of {Analysis::EditReplay.edits} — emptied, reduced to its
|
|
378
|
+
# `class` / `module` skeleton, and (#2277) with one body statement dropped, at most
|
|
379
|
+
# {Analysis::EditReplay::MAX_DROPS} times — and the warm recheck of the baseline
|
|
380
|
+
# ({IncrementalSession#replay_edit}) must equal
|
|
288
381
|
# a full analysis of the edited tree: a dependent the recheck failed to re-analyse is served from the baseline and
|
|
289
382
|
# differs. A run in which every replay declined compared nothing, so it fails rather than report OK.
|
|
290
383
|
def verify_edit_replays(session, configuration, paths, options)
|
|
@@ -346,8 +439,8 @@ module Rigor
|
|
|
346
439
|
return 1
|
|
347
440
|
end
|
|
348
441
|
|
|
349
|
-
line = "rigor: --verify-incremental OK — #{replayed} edits replayed (files emptied
|
|
350
|
-
"class/module skeleton), every recheck matches full"
|
|
442
|
+
line = "rigor: --verify-incremental OK — #{replayed} edits replayed (files emptied, reduced to their " \
|
|
443
|
+
"class/module skeleton, and with a body statement dropped), every recheck matches full"
|
|
351
444
|
line = "#{line}; #{declined} declined (the recheck would have been a full analysis)" if declined.positive?
|
|
352
445
|
side_output(format).puts(line)
|
|
353
446
|
0
|
|
@@ -386,9 +479,7 @@ module Rigor
|
|
|
386
479
|
return run_editor_mode_option_b(session, snapshot, fingerprint, configuration, options) if buffer
|
|
387
480
|
|
|
388
481
|
diagnostics, warm = session.run_incremental(snapshot: snapshot, fingerprint: fingerprint)
|
|
389
|
-
# The banner's file count comes from the session's analyzed set
|
|
390
|
-
# `@analyzed` advances to the current file set), so a dedicated probe Runner + `Dir.glob` is no longer
|
|
391
|
-
# built just to size the banner (recon §3 — the analysis tree was expanded three times per recheck).
|
|
482
|
+
# The banner's file count comes from the session's analyzed set, so no probe Runner is built just to size it.
|
|
392
483
|
# A warm banner reports the recheck's own sets (#1526), which only the session knows.
|
|
393
484
|
report_incremental_run(warm, session.analyzed_files.size, recheck: session.last_recheck)
|
|
394
485
|
emit_incremental_fact_surface_notes(session)
|
|
@@ -688,7 +779,7 @@ module Rigor
|
|
|
688
779
|
)
|
|
689
780
|
end
|
|
690
781
|
|
|
691
|
-
def parse_check_options
|
|
782
|
+
def parse_check_options
|
|
692
783
|
options = {
|
|
693
784
|
config: nil,
|
|
694
785
|
format: "text",
|
|
@@ -698,7 +789,7 @@ module Rigor
|
|
|
698
789
|
no_cache: false,
|
|
699
790
|
stats: true,
|
|
700
791
|
# ADR-15 Phase 4c — when nil, falls back to `RIGOR_RACTOR_WORKERS` then `.rigor.yml` `parallel.workers:` then
|
|
701
|
-
# 0 (sequential). See `resolve_workers` for the precedence chain.
|
|
792
|
+
# 0 (sequential). See `CheckRunnerFactory.resolve_workers` for the precedence chain.
|
|
702
793
|
workers: nil,
|
|
703
794
|
tmp_file: nil,
|
|
704
795
|
instead_of: nil,
|
|
@@ -747,93 +838,7 @@ module Rigor
|
|
|
747
838
|
# one-line coverage summary.
|
|
748
839
|
coverage: false
|
|
749
840
|
}
|
|
750
|
-
|
|
751
|
-
opts.banner = "Usage: rigor check [options] [paths]"
|
|
752
|
-
Options.add_config(opts, options)
|
|
753
|
-
opts.on("--format=FORMAT",
|
|
754
|
-
"Output format: text, json, sarif, github, gitlab, checkstyle, junit, teamcity") do |value|
|
|
755
|
-
options[:format] = value
|
|
756
|
-
end
|
|
757
|
-
opts.on("--explain", "Surface fail-soft fallback events as :info diagnostics") { options[:explain] = true }
|
|
758
|
-
opts.on("--cache-stats", "Print on-disk cache inventory at end of run") { options[:cache_stats] = true }
|
|
759
|
-
opts.on("--coverage",
|
|
760
|
-
"Add a type-precision coverage block (an extra precision pass over the analyzed files)") do
|
|
761
|
-
options[:coverage] = true
|
|
762
|
-
end
|
|
763
|
-
opts.on("--clear-cache", "Remove the .rigor/cache directory before running") { options[:clear_cache] = true }
|
|
764
|
-
opts.on("--no-cache",
|
|
765
|
-
"Disable the persistent cache for this run (with --incremental: no snapshot is read or written)") do
|
|
766
|
-
options[:no_cache] = true
|
|
767
|
-
end
|
|
768
|
-
opts.on("--[no-]stats",
|
|
769
|
-
"Print run summary (files, classes, memory, wall time) to stderr (default: on)") do |value|
|
|
770
|
-
options[:stats] = value
|
|
771
|
-
end
|
|
772
|
-
opts.on("--workers=N", Integer,
|
|
773
|
-
"Dispatch per-file analysis across N Ractor workers (default: 0; sequential)") do |value|
|
|
774
|
-
options[:workers] = value
|
|
775
|
-
end
|
|
776
|
-
Options.add_editor_mode(opts, options)
|
|
777
|
-
opts.on("--baseline=PATH",
|
|
778
|
-
"load baseline from PATH (overrides .rigor.yml `baseline:`)") do |value|
|
|
779
|
-
options[:baseline] = value
|
|
780
|
-
end
|
|
781
|
-
opts.on("--no-baseline",
|
|
782
|
-
"ignore any configured baseline for this run") do
|
|
783
|
-
options[:baseline] = false
|
|
784
|
-
end
|
|
785
|
-
opts.on("--baseline-strict",
|
|
786
|
-
"fail the run on any baseline drift (CI gate)") do
|
|
787
|
-
options[:baseline_strict] = true
|
|
788
|
-
end
|
|
789
|
-
opts.on("--fail-on=SEVERITY",
|
|
790
|
-
"exit non-zero on a diagnostic at or above SEVERITY: error (default), warning, or info") do |value|
|
|
791
|
-
# Validated post-parse (`valid_fail_on_option?`, called from `run`) rather than here — see its
|
|
792
|
-
# comment for why an unrecognised value is not raised from inside this block.
|
|
793
|
-
options[:fail_on] = value.to_s.downcase.to_sym
|
|
794
|
-
end
|
|
795
|
-
opts.on("--treat-all-as-inline-rbs",
|
|
796
|
-
"force-load rigor-rbs-inline with require_magic_comment: false") do
|
|
797
|
-
options[:treat_all_as_inline_rbs] = true
|
|
798
|
-
end
|
|
799
|
-
opts.on("--verify-incremental",
|
|
800
|
-
"assert incremental analysis matches a full run, then exit") do
|
|
801
|
-
options[:verify_incremental] = true
|
|
802
|
-
end
|
|
803
|
-
opts.on("--verify-incremental-edits=N", Integer,
|
|
804
|
-
"with --verify-incremental, also replay 2 edits (empty, class/module skeleton) on N files") do |value|
|
|
805
|
-
if value.negative?
|
|
806
|
-
raise OptionParser::InvalidArgument, "--verify-incremental-edits=#{value} (must be 0 or more)"
|
|
807
|
-
end
|
|
808
|
-
|
|
809
|
-
options[:verify_incremental] = true
|
|
810
|
-
options[:verify_incremental_edits] = value
|
|
811
|
-
end
|
|
812
|
-
opts.on("--incremental",
|
|
813
|
-
"re-analyze only files changed since the last run (cross-process cache)") do
|
|
814
|
-
options[:incremental] = true
|
|
815
|
-
end
|
|
816
|
-
opts.on("--no-ci-detect",
|
|
817
|
-
"do not auto-emit CI-native output when a CI environment is detected") do
|
|
818
|
-
options[:ci_detect] = false
|
|
819
|
-
end
|
|
820
|
-
# ADR-50 § WD2 — `=[LIST]` (not ` [LIST]`) so a bare `--bleeding-edge` never swallows a following positional
|
|
821
|
-
# path: `rigor check --bleeding-edge lib` adopts the whole overlay and checks `lib`.
|
|
822
|
-
opts.on("--bleeding-edge=[LIST]",
|
|
823
|
-
"adopt the bleeding-edge overlay for this run " \
|
|
824
|
-
"(all features, or a comma-separated feature-id list)") do |value|
|
|
825
|
-
options[:bleeding_edge] = value.nil? || value.split(",").map(&:strip).reject(&:empty?)
|
|
826
|
-
end
|
|
827
|
-
opts.on("--no-bleeding-edge",
|
|
828
|
-
"ignore any configured bleeding_edge: selection for this run") do
|
|
829
|
-
options[:bleeding_edge] = false
|
|
830
|
-
end
|
|
831
|
-
opts.on("--no-tolerated-effects",
|
|
832
|
-
"check effect envelopes as if effects.tolerated: were empty") do
|
|
833
|
-
options[:no_tolerated_effects] = true
|
|
834
|
-
end
|
|
835
|
-
end
|
|
836
|
-
parser.parse!(@argv)
|
|
841
|
+
option_parser(options).parse!(@argv)
|
|
837
842
|
options
|
|
838
843
|
end
|
|
839
844
|
|
|
@@ -943,6 +948,7 @@ module Rigor
|
|
|
943
948
|
@err.puts(" recursion-unroll-fuel hits: #{counts[Inference::BudgetTrace::RECURSION_UNROLL_FUEL]}")
|
|
944
949
|
@err.puts(" recursion-fixpoint-cap hits: #{counts[Inference::BudgetTrace::RECURSION_FIXPOINT_CAP]}")
|
|
945
950
|
@err.puts(" block-writeback-cap hits: #{counts[Inference::BudgetTrace::BLOCK_WRITEBACK_CAP]}")
|
|
951
|
+
@err.puts(" callee-chain-cap hits: #{counts[Inference::BudgetTrace::CALLEE_CHAIN_CAP]}")
|
|
946
952
|
write_budget_distributions
|
|
947
953
|
write_memo_profile(counts)
|
|
948
954
|
end
|
|
@@ -1153,7 +1159,7 @@ module Rigor
|
|
|
1153
1159
|
payload["fail_on"] = fail_on.to_s
|
|
1154
1160
|
payload["coverage"] = coverage_payload(coverage) if coverage
|
|
1155
1161
|
payload["config_warnings"] = config_warnings.map(&:to_h) unless config_warnings.empty?
|
|
1156
|
-
@out.puts(JSON.pretty_generate(payload))
|
|
1162
|
+
@out.puts(JSON.pretty_generate(JsonDocument.stamp(payload)))
|
|
1157
1163
|
when "text"
|
|
1158
1164
|
write_text_result(result)
|
|
1159
1165
|
write_coverage_summary(coverage) if coverage
|
|
@@ -7,8 +7,8 @@ module Rigor
|
|
|
7
7
|
class CLI
|
|
8
8
|
# Shared factory for building an {Analysis::Runner} from CLI option hashes + a loaded {Configuration}.
|
|
9
9
|
#
|
|
10
|
-
# Extracted from {CheckCommand} so that `rigor doctor`
|
|
11
|
-
# runner
|
|
10
|
+
# Extracted from {CheckCommand} so that other commands (`rigor doctor`, `rigor skill describe --deep`) reuse the
|
|
11
|
+
# same runner setup (cache, workers, buffer, explain) instead of diverging from the `rigor check` path (ADR-77 WD3).
|
|
12
12
|
module CheckRunnerFactory
|
|
13
13
|
module_function
|
|
14
14
|
|
data/lib/rigor/cli/command.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../rooted_glob"
|
|
4
|
+
require_relative "../analysis/path_expansion"
|
|
4
5
|
|
|
5
6
|
module Rigor
|
|
6
7
|
class CLI
|
|
@@ -11,7 +12,7 @@ module Rigor
|
|
|
11
12
|
# `$stderr`, most not at all). Centralising it here gives one consistent shape and lets a test instantiate a command
|
|
12
13
|
# with just `argv:` (the streams default so a spec can pass a `StringIO` for one and ignore the other).
|
|
13
14
|
#
|
|
14
|
-
# Subclasses read the `@argv` / `@out` / `@err` ivars directly
|
|
15
|
+
# Subclasses read the `@argv` / `@out` / `@err` ivars directly.
|
|
15
16
|
class Command
|
|
16
17
|
def initialize(argv:, out: $stdout, err: $stderr)
|
|
17
18
|
@argv = argv
|
|
@@ -38,6 +39,19 @@ module Rigor
|
|
|
38
39
|
end
|
|
39
40
|
paths.uniq
|
|
40
41
|
end
|
|
42
|
+
|
|
43
|
+
# #2206 — the `.rb` paths `args` names, with a path that is neither a directory nor a `.rb` file reported the way
|
|
44
|
+
# `rigor check` reports it ({Analysis::PathExpansion.diagnostics}): a warning beside files that will be read, an
|
|
45
|
+
# error when nothing will be. The diagnostics go to stderr. Returns nil when one is an error, so the caller exits.
|
|
46
|
+
def collect_paths_reporting(args)
|
|
47
|
+
bad = Analysis::PathExpansion.unusable(args)
|
|
48
|
+
files = Analysis::PathExpansion.ruby_files(args, [])
|
|
49
|
+
diagnostics = Analysis::PathExpansion.diagnostics(bad, any_files: files.any?)
|
|
50
|
+
diagnostics.each { |diagnostic| @err.puts(diagnostic.to_s) }
|
|
51
|
+
return nil if diagnostics.any?(&:error?)
|
|
52
|
+
|
|
53
|
+
files.uniq
|
|
54
|
+
end
|
|
41
55
|
end
|
|
42
56
|
end
|
|
43
57
|
end
|
|
@@ -68,7 +68,7 @@ module Rigor
|
|
|
68
68
|
configuration = Configuration.load(options.fetch(:config))
|
|
69
69
|
paths = resolve_paths(configuration)
|
|
70
70
|
return CLI::EXIT_USAGE if paths.nil?
|
|
71
|
-
return
|
|
71
|
+
return no_ruby_files_error(configuration) if paths.empty?
|
|
72
72
|
|
|
73
73
|
return run_protection(paths, options, configuration) if options[:protection]
|
|
74
74
|
|
|
@@ -77,27 +77,31 @@ module Rigor
|
|
|
77
77
|
determine_exit(report, options)
|
|
78
78
|
end
|
|
79
79
|
|
|
80
|
+
# The parser this command parses with, built without parsing anything (the catalogue reads it, ADR-126 WD3).
|
|
81
|
+
def option_parser(options = {})
|
|
82
|
+
OptionParser.new { |opts| define_options(opts, options) }
|
|
83
|
+
end
|
|
84
|
+
|
|
80
85
|
private
|
|
81
86
|
|
|
82
|
-
# Like `rigor check`, fall back to the configured `paths:` when no path is given
|
|
83
|
-
#
|
|
84
|
-
# differ on). The mutation mode keeps its own git-changed-files default and returns before this.
|
|
87
|
+
# Like `rigor check`, fall back to the configured `paths:` when no path is given. The mutation mode keeps its own
|
|
88
|
+
# git-changed-files default and returns before this.
|
|
85
89
|
def resolve_paths(configuration)
|
|
86
90
|
args = @argv.empty? ? configuration.paths : @argv
|
|
87
|
-
|
|
91
|
+
collect_paths_reporting(args)
|
|
88
92
|
end
|
|
89
93
|
|
|
90
94
|
def parse_options
|
|
91
95
|
options = { format: "text", threshold: nil, config: nil, protection: false, mutation: false,
|
|
92
96
|
with_tests: false, test_command: DEFAULT_TEST_COMMAND, include_dynamic: false,
|
|
93
97
|
limit: nil, seed: 1, workers: nil, no_cache: false }
|
|
94
|
-
|
|
98
|
+
option_parser(options).parse!(@argv)
|
|
95
99
|
options
|
|
96
100
|
end
|
|
97
101
|
|
|
98
102
|
def define_options(opts, options)
|
|
99
103
|
opts.banner = USAGE
|
|
100
|
-
|
|
104
|
+
Options.add_format(opts, options, "Output format: text or json")
|
|
101
105
|
Options.add_config(opts, options)
|
|
102
106
|
opts.on("--protection", "Report type-protection coverage (ADR-63 Tier 1) instead of type precision") do
|
|
103
107
|
options[:protection] = true
|
|
@@ -203,7 +207,7 @@ module Rigor
|
|
|
203
207
|
# `singleton(Account)` instead of `Dynamic`. Without this, a
|
|
204
208
|
# single-file scan cannot see a class it does not itself declare,
|
|
205
209
|
# so every cross-file class-constant dispatch was miscounted as
|
|
206
|
-
# unprotected
|
|
210
|
+
# unprotected.
|
|
207
211
|
# - `param_inferred_types` (ADR-67 WD3) — an inferred-parameter
|
|
208
212
|
# receiver (`node.loc` where `node` is a `def compile(node)`
|
|
209
213
|
# parameter) counts as protected when its call sites resolve to
|
|
@@ -253,6 +257,15 @@ module Rigor
|
|
|
253
257
|
CLI::EXIT_USAGE
|
|
254
258
|
end
|
|
255
259
|
|
|
260
|
+
# #2206 — paths were named, or configured, but none holds a Ruby file. Said as such, not as "no path given".
|
|
261
|
+
def no_ruby_files_error(configuration)
|
|
262
|
+
return usage_error if @argv.empty? && Array(configuration.paths).empty?
|
|
263
|
+
|
|
264
|
+
@err.puts("coverage: no Ruby files under the given paths")
|
|
265
|
+
@err.puts(USAGE)
|
|
266
|
+
CLI::EXIT_USAGE
|
|
267
|
+
end
|
|
268
|
+
|
|
256
269
|
def scan_paths(paths, configuration)
|
|
257
270
|
CoverageScan.precision_report(files: paths, configuration: configuration)
|
|
258
271
|
end
|
|
@@ -24,7 +24,6 @@ module Rigor
|
|
|
24
24
|
DISCOVERY_SEEDED_MUTATION_SITES = "discovery-seeded-mutation-sites"
|
|
25
25
|
|
|
26
26
|
# ADR-50 § WD2 — the bleeding-edge feature id gating the Tier-2 dependent-closure kill oracle (#254).
|
|
27
|
-
# Same reason as above for naming it here rather than inlining the string.
|
|
28
27
|
DEPENDENT_CLOSURE_KILL_ORACLE = "dependent-closure-kill-oracle"
|
|
29
28
|
|
|
30
29
|
# #264 — the "loud" threshold for a rescued-harness-failure count. Below it, a rescued mutant reads as
|
|
@@ -52,8 +51,7 @@ module Rigor
|
|
|
52
51
|
MeasurementIntegrityWarning.emit(report, err: @err, floor: HARNESS_ERROR_WARN_FLOOR)
|
|
53
52
|
end
|
|
54
53
|
|
|
55
|
-
# The cross-file knowledge Tier 2 measures with
|
|
56
|
-
# knows a feature id exists.
|
|
54
|
+
# The cross-file knowledge Tier 2 measures with, gated on the #253 feature.
|
|
57
55
|
#
|
|
58
56
|
# Returns nil (today's behaviour: an unseeded per-file view on both halves of the measurement) unless the
|
|
59
57
|
# project has adopted `discovery-seeded-mutation-sites`. With it adopted, returns the table set
|
|
@@ -81,7 +79,7 @@ module Rigor
|
|
|
81
79
|
seed.empty? ? nil : seed
|
|
82
80
|
end
|
|
83
81
|
|
|
84
|
-
# The kill oracle Tier 2 measures with
|
|
82
|
+
# The kill oracle Tier 2 measures with, gated on the #254 feature.
|
|
85
83
|
#
|
|
86
84
|
# Returns nil (today's behaviour: {Protection::DiagnosticOracle}, which re-analyses the mutated file
|
|
87
85
|
# alone) unless the project has adopted `dependent-closure-kill-oracle`. With it adopted, returns the
|
|
@@ -139,7 +137,7 @@ module Rigor
|
|
|
139
137
|
# warm loop), then re-analyses each target file's mutants against its clean baseline. Defaults to the git-changed
|
|
140
138
|
# `.rb` files; explicit paths override (and enable the whole-project opt-in, which is minutes).
|
|
141
139
|
def run_mutation_protection(options)
|
|
142
|
-
explicit =
|
|
140
|
+
explicit = collect_paths_reporting(@argv)
|
|
143
141
|
return CLI::EXIT_USAGE if explicit.nil?
|
|
144
142
|
|
|
145
143
|
target_files = explicit.empty? ? changed_ruby_files : explicit
|
|
@@ -204,8 +202,8 @@ module Rigor
|
|
|
204
202
|
determine_protection_exit(report, options)
|
|
205
203
|
end
|
|
206
204
|
|
|
207
|
-
# An explicit `--workers=N` on the fused path cannot be honoured, so say so rather than
|
|
208
|
-
#
|
|
205
|
+
# An explicit `--workers=N` on the fused path cannot be honoured, so say so rather than drop it silently. Only the
|
|
206
|
+
# explicit flag warns — a project-wide
|
|
209
207
|
# `parallel.workers:` or `RIGOR_RACTOR_WORKERS` default is not a request about *this* run.
|
|
210
208
|
def warn_workers_ignored_under_tests(options)
|
|
211
209
|
return unless options[:workers].to_i > 1
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "json"
|
|
4
4
|
require_relative "renderable"
|
|
5
|
+
require_relative "json_document"
|
|
5
6
|
|
|
6
7
|
module Rigor
|
|
7
8
|
class CLI
|
|
@@ -93,7 +94,7 @@ module Rigor
|
|
|
93
94
|
end
|
|
94
95
|
|
|
95
96
|
def render_json(report)
|
|
96
|
-
@out.puts(JSON.pretty_generate(json_payload(report)))
|
|
97
|
+
@out.puts(JSON.pretty_generate(JsonDocument.stamp(json_payload(report), mode: "precision")))
|
|
97
98
|
end
|
|
98
99
|
|
|
99
100
|
def json_payload(report)
|
|
@@ -23,11 +23,9 @@ module Rigor
|
|
|
23
23
|
|
|
24
24
|
# @param files — explicit `.rb` file paths to scan.
|
|
25
25
|
#
|
|
26
|
-
# Issue #513 — the environment is
|
|
27
|
-
# `--protection` uses)
|
|
28
|
-
#
|
|
29
|
-
# engine by ~1.2 points on a Rails target — the defect the protection path documented and fixed for
|
|
30
|
-
# itself on 2026-07-04, never applied here.
|
|
26
|
+
# Issue #513 — the environment is plugin-aware (`LanguageServer::ProjectContext`, the same builder
|
|
27
|
+
# `--protection` uses). A bare RBS environment read every plugin-typed receiver as `Dynamic` and understated
|
|
28
|
+
# the precision ratio by ~1.2 points on a Rails target.
|
|
31
29
|
def precision_report(files:, configuration:)
|
|
32
30
|
scope = discovery_seeded_scope(
|
|
33
31
|
files: files,
|
|
@@ -5,11 +5,12 @@ require_relative "options"
|
|
|
5
5
|
|
|
6
6
|
require "json"
|
|
7
7
|
require "optionparser"
|
|
8
|
+
require_relative "json_document"
|
|
8
9
|
|
|
9
10
|
module Rigor
|
|
10
11
|
class CLI
|
|
11
|
-
# Executes `rigor diff <
|
|
12
|
-
#
|
|
12
|
+
# Executes `rigor diff <rigor-check.json> [paths...]`. Compares the current `rigor check` diagnostics against a
|
|
13
|
+
# saved `rigor check --format=json` result and prints the delta:
|
|
13
14
|
#
|
|
14
15
|
# - **new** — diagnostics in the current run that were not
|
|
15
16
|
# in the baseline (typically a regression introduced in
|
|
@@ -21,11 +22,11 @@ module Rigor
|
|
|
21
22
|
# diagnostic to a new line surfaces as one fixed + one new pair, which lines up with the user's mental model of "you
|
|
22
23
|
# changed the line, the analyzer's position changed too."
|
|
23
24
|
#
|
|
24
|
-
# CI usage: commit a `rigor.
|
|
25
|
-
#
|
|
25
|
+
# CI usage: commit a `rigor-check.json` produced once with `rigor check --format=json > rigor-check.json`, then
|
|
26
|
+
# run `rigor diff rigor-check.json` in CI. Exit code is `1` when any new diagnostic appears, `0` otherwise —
|
|
26
27
|
# so adding new errors fails CI but legacy errors recorded in the baseline don't.
|
|
27
28
|
class DiffCommand < Command
|
|
28
|
-
USAGE = "Usage: rigor diff [options] <
|
|
29
|
+
USAGE = "Usage: rigor diff [options] <rigor-check.json> [paths...]"
|
|
29
30
|
|
|
30
31
|
# @return CLI exit status.
|
|
31
32
|
def run
|
|
@@ -51,22 +52,26 @@ module Rigor
|
|
|
51
52
|
diff[:new].any? ? 1 : 0
|
|
52
53
|
end
|
|
53
54
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
def
|
|
57
|
-
options = { format: "text", current_path: nil, config: nil }
|
|
55
|
+
# The parser this command parses with, built without parsing anything: the catalogue reads the
|
|
56
|
+
# flags and formats from it (ADR-126 WD3). `options` is only written by the switches' blocks.
|
|
57
|
+
def option_parser(options = {})
|
|
58
58
|
OptionParser.new do |opt|
|
|
59
59
|
opt.banner = USAGE
|
|
60
|
-
|
|
61
|
-
options[:format] = fmt
|
|
62
|
-
end
|
|
60
|
+
Options.add_format(opt, options, "Output format (text | json). Default: text.")
|
|
63
61
|
opt.on("--current=PATH", "Compare to the saved current JSON instead of running `rigor check`.") do |path|
|
|
64
62
|
options[:current_path] = path
|
|
65
63
|
end
|
|
66
64
|
opt.on("--config=PATH", "Path to .rigor.yml. Forwarded to the implicit `rigor check` run.") do |path|
|
|
67
65
|
options[:config] = Options.config_path(path)
|
|
68
66
|
end
|
|
69
|
-
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
def parse_options
|
|
73
|
+
options = { format: "text", current_path: nil, config: nil }
|
|
74
|
+
option_parser(options).parse!(@argv)
|
|
70
75
|
options
|
|
71
76
|
end
|
|
72
77
|
|
|
@@ -121,13 +126,13 @@ module Rigor
|
|
|
121
126
|
end
|
|
122
127
|
|
|
123
128
|
def write_diff_json(diff, baseline_path, baseline_count, current_count)
|
|
124
|
-
@out.puts(JSON.pretty_generate(
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
129
|
+
@out.puts(JSON.pretty_generate(JsonDocument.stamp({
|
|
130
|
+
"baseline" => baseline_path,
|
|
131
|
+
"baseline_count" => baseline_count,
|
|
132
|
+
"current_count" => current_count,
|
|
133
|
+
"new" => diff[:new],
|
|
134
|
+
"fixed" => diff[:fixed]
|
|
135
|
+
})))
|
|
131
136
|
end
|
|
132
137
|
|
|
133
138
|
def write_diff_text(diff, baseline_path, baseline_count, current_count)
|
|
@@ -203,8 +203,7 @@ module Rigor
|
|
|
203
203
|
relative: relative,
|
|
204
204
|
path: path,
|
|
205
205
|
category: category,
|
|
206
|
-
# Always-unique addresses: the `docs/`-relative path and the prefixed basename.
|
|
207
|
-
# `03-narrowing`.
|
|
206
|
+
# Always-unique addresses: the `docs/`-relative path and the prefixed basename.
|
|
208
207
|
exact_aliases: [relative, base].uniq,
|
|
209
208
|
# The prefix-stripped short name (`narrowing`); ambiguous when two categories carry the same chapter slug
|
|
210
209
|
# (`plugins`).
|
|
@@ -17,6 +17,7 @@ require_relative "../type/combinator"
|
|
|
17
17
|
require_relative "check_invocation"
|
|
18
18
|
require_relative "command"
|
|
19
19
|
require_relative "options"
|
|
20
|
+
require_relative "json_document"
|
|
20
21
|
|
|
21
22
|
module Rigor
|
|
22
23
|
class CLI
|
|
@@ -86,23 +87,22 @@ module Rigor
|
|
|
86
87
|
CLI::EXIT_USAGE
|
|
87
88
|
end
|
|
88
89
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
def
|
|
92
|
-
options = { config: nil, format: "text" }
|
|
90
|
+
# The parser this command parses with, built without parsing anything: the catalogue reads the
|
|
91
|
+
# flags and formats from it (ADR-126 WD3). `options` is only written by the switches' blocks.
|
|
92
|
+
def option_parser(options = {})
|
|
93
93
|
OptionParser.new do |opts|
|
|
94
94
|
opts.banner = USAGE
|
|
95
95
|
Options.add_config(opts, options)
|
|
96
|
-
|
|
97
|
-
end
|
|
98
|
-
validate!(options)
|
|
99
|
-
options
|
|
96
|
+
Options.add_format(opts, options, "Output format: text (default) or json")
|
|
97
|
+
end
|
|
100
98
|
end
|
|
101
99
|
|
|
102
|
-
|
|
103
|
-
return if %w[text json].include?(options.fetch(:format))
|
|
100
|
+
private
|
|
104
101
|
|
|
105
|
-
|
|
102
|
+
def parse_options
|
|
103
|
+
options = { config: nil, format: "text" }
|
|
104
|
+
option_parser(options).parse!(@argv)
|
|
105
|
+
options
|
|
106
106
|
end
|
|
107
107
|
|
|
108
108
|
# ------------------------------------------------------------------
|
|
@@ -437,7 +437,7 @@ module Rigor
|
|
|
437
437
|
}
|
|
438
438
|
end
|
|
439
439
|
}
|
|
440
|
-
@out.puts(JSON.pretty_generate(payload))
|
|
440
|
+
@out.puts(JSON.pretty_generate(JsonDocument.stamp(payload)))
|
|
441
441
|
end
|
|
442
442
|
|
|
443
443
|
def report_text(findings)
|