rigortype 0.3.8 → 0.3.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/data/capability_roles/capability_roles.rbs +36 -0
- data/data/gem_overlay/activesupport/core_ext.rbs +109 -13
- data/docs/handbook/02-everyday-types.md +17 -14
- data/docs/handbook/03-narrowing.md +37 -4
- data/docs/handbook/05-methods-and-blocks.md +1 -1
- data/docs/handbook/06-classes.md +2 -2
- data/docs/handbook/07-rbs-and-extended.md +14 -1
- data/docs/handbook/11-sig-gen.md +72 -1
- data/docs/handbook/README.md +1 -1
- data/docs/handbook/appendix-elixir.md +2 -2
- data/docs/handbook/appendix-go.md +2 -2
- data/docs/handbook/appendix-java-csharp.md +2 -2
- data/docs/handbook/appendix-mypy.md +2 -2
- data/docs/handbook/appendix-phpstan.md +1 -1
- data/docs/handbook/appendix-rust.md +1 -1
- data/docs/handbook/appendix-type-theory.md +4 -4
- data/docs/handbook/appendix-typescript.md +1 -1
- data/docs/llms.txt +2 -0
- data/docs/manual/02-cli-reference.md +32 -4
- data/docs/manual/03-configuration.md +11 -0
- data/docs/manual/04-diagnostics.md +11 -0
- data/docs/manual/07-plugins.md +15 -5
- data/docs/manual/08-skills.md +23 -2
- data/docs/manual/11-ci.md +9 -0
- data/docs/manual/15-type-protection-coverage.md +8 -0
- data/docs/manual/16-rbs-extended-annotations.md +6 -1
- data/docs/manual/18-removing-dead-code.md +10 -8
- data/docs/manual/plugins/rigor-actionpack.md +18 -0
- data/docs/manual/plugins/rigor-activerecord.md +28 -0
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +32 -2
- data/docs/manual/plugins/rigor-rbs-inline.md +42 -1
- data/lib/rigor/analysis/baseline.rb +2 -2
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules/dead_version_guard_arms.rb +1 -5
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +10 -2
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules/published_constant_guard.rb +15 -6
- data/lib/rigor/analysis/check_rules/rule_ids.rb +1 -1
- data/lib/rigor/analysis/check_rules/rule_walk.rb +39 -4
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +1 -1
- data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +0 -1
- data/lib/rigor/analysis/check_rules.rb +200 -32
- data/lib/rigor/analysis/crash_signature.rb +8 -24
- data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +1 -1
- data/lib/rigor/analysis/dependency_source_inference/builder.rb +0 -2
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +0 -2
- data/lib/rigor/analysis/dependency_source_inference/index.rb +5 -5
- data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +1 -2
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
- data/lib/rigor/analysis/effects_cache_probe.rb +3 -4
- data/lib/rigor/analysis/erb_template_detector.rb +1 -2
- data/lib/rigor/analysis/fact_store.rb +6 -1
- data/lib/rigor/analysis/incremental.rb +16 -0
- data/lib/rigor/analysis/incremental_session.rb +81 -22
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +0 -1
- data/lib/rigor/analysis/reachability/graph.rb +3 -5
- data/lib/rigor/analysis/reachability/plugin_roots.rb +6 -5
- data/lib/rigor/analysis/reachability/project_files.rb +2 -2
- data/lib/rigor/analysis/reachability/scan.rb +5 -5
- data/lib/rigor/analysis/reachability/scan_cache.rb +2 -2
- data/lib/rigor/analysis/reachability/signature_scan.rb +2 -2
- data/lib/rigor/analysis/result.rb +1 -3
- data/lib/rigor/analysis/rule_catalog.rb +4 -1
- data/lib/rigor/analysis/run_cache_key.rb +1 -1
- data/lib/rigor/analysis/run_cache_probe.rb +3 -5
- data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -8
- data/lib/rigor/analysis/runner/declaration_position.rb +1 -2
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +177 -26
- data/lib/rigor/analysis/runner/effect_annotation_residual_pass.rb +2 -3
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +9 -10
- data/lib/rigor/analysis/runner/pool_coordinator.rb +256 -88
- data/lib/rigor/analysis/runner/project_pre_passes.rb +12 -6
- data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
- data/lib/rigor/analysis/runner.rb +133 -35
- data/lib/rigor/analysis/worker_session.rb +9 -10
- data/lib/rigor/bleeding_edge.rb +12 -24
- data/lib/rigor/builtins/hkt_builtins.rb +2 -2
- data/lib/rigor/builtins/imported_refinements.rb +155 -21
- data/lib/rigor/builtins/predefined_constant_refinements.rb +5 -7
- data/lib/rigor/builtins/regex_refinement.rb +4 -4
- data/lib/rigor/builtins/static_return_refinements.rb +5 -6
- data/lib/rigor/cache/annotation_location.rb +2 -4
- data/lib/rigor/cache/descriptor.rb +58 -20
- data/lib/rigor/cache/engine_source.rb +4 -4
- data/lib/rigor/cache/file_digest.rb +9 -1
- data/lib/rigor/cache/incremental_snapshot.rb +40 -6
- data/lib/rigor/cache/rbs_class_ancestor_table.rb +0 -3
- data/lib/rigor/cache/rbs_class_type_param_names.rb +0 -3
- data/lib/rigor/cache/rbs_constant_table.rb +0 -3
- data/lib/rigor/cache/rbs_descriptor.rb +79 -11
- data/lib/rigor/cache/rbs_environment.rb +8 -4
- data/lib/rigor/cache/rbs_known_class_names.rb +0 -3
- data/lib/rigor/cache/store.rb +77 -32
- data/lib/rigor/cli/annotate_command.rb +5 -6
- data/lib/rigor/cli/check_command.rb +57 -10
- data/lib/rigor/cli/check_invocation.rb +6 -11
- data/lib/rigor/cli/check_runner_factory.rb +1 -5
- data/lib/rigor/cli/coverage_command.rb +1 -1
- data/lib/rigor/cli/coverage_mutation.rb +12 -3
- data/lib/rigor/cli/coverage_scan.rb +1 -5
- data/lib/rigor/cli/diff_command.rb +1 -1
- data/lib/rigor/cli/doc_links.rb +3 -3
- data/lib/rigor/cli/docs_command.rb +2 -2
- data/lib/rigor/cli/doctor_command.rb +85 -38
- data/lib/rigor/cli/effects_command.rb +2 -2
- data/lib/rigor/cli/effects_diff_renderer.rb +3 -3
- data/lib/rigor/cli/effects_snapshot_command.rb +1 -1
- data/lib/rigor/cli/explain_command.rb +34 -1
- data/lib/rigor/cli/fused_protection_report.rb +7 -1
- data/lib/rigor/cli/lsp_command.rb +1 -1
- data/lib/rigor/cli/mcp_command.rb +1 -1
- data/lib/rigor/cli/measurement_integrity_warning.rb +4 -5
- data/lib/rigor/cli/mutation_fork_scan.rb +6 -6
- data/lib/rigor/cli/mutation_protection_report.rb +7 -1
- data/lib/rigor/cli/plugin_command.rb +1 -1
- data/lib/rigor/cli/plugins_command.rb +2 -1
- data/lib/rigor/cli/prism_colorizer.rb +2 -2
- data/lib/rigor/cli/protection_fork_scan.rb +6 -6
- data/lib/rigor/cli/show_bleedingedge_command.rb +1 -1
- data/lib/rigor/cli/sig_gen_command.rb +2 -2
- data/lib/rigor/cli/skill_command.rb +1 -1
- data/lib/rigor/cli/skill_deep_probe.rb +4 -4
- data/lib/rigor/cli/skill_describe.rb +36 -35
- data/lib/rigor/cli/trace_command.rb +4 -4
- data/lib/rigor/cli/trace_renderer.rb +5 -6
- data/lib/rigor/cli/triage_command.rb +1 -1
- data/lib/rigor/cli/type_of_command.rb +10 -6
- data/lib/rigor/cli/type_scan_command.rb +4 -4
- data/lib/rigor/cli/unused_command.rb +11 -3
- data/lib/rigor/cli/upgrade_command.rb +1 -1
- data/lib/rigor/cli.rb +68 -6
- data/lib/rigor/config_audit.rb +30 -5
- data/lib/rigor/configuration/severity_profile.rb +6 -6
- data/lib/rigor/configuration.rb +43 -4
- data/lib/rigor/effects/attribution.rb +1 -3
- data/lib/rigor/effects/config_envelopes.rb +6 -8
- data/lib/rigor/effects/definition_lines.rb +25 -6
- data/lib/rigor/effects/effect_table.rb +0 -1
- data/lib/rigor/effects/entry_points.rb +0 -2
- data/lib/rigor/effects/envelope_check.rb +8 -8
- data/lib/rigor/effects/envelope_index.rb +5 -8
- data/lib/rigor/effects/framework_units.rb +4 -6
- data/lib/rigor/effects/identity.rb +4 -6
- data/lib/rigor/effects/inline_anchor.rb +7 -7
- data/lib/rigor/effects/label_intent.rb +3 -4
- data/lib/rigor/effects/liskov_check.rb +8 -8
- data/lib/rigor/effects/method_key.rb +1 -1
- data/lib/rigor/effects/plugin_facts.rb +4 -6
- data/lib/rigor/effects/propagator.rb +2 -3
- data/lib/rigor/effects/scanner.rb +1 -1
- data/lib/rigor/effects/signature_sources.rb +3 -5
- data/lib/rigor/effects/snapshot.rb +10 -10
- data/lib/rigor/effects/snapshot_diff.rb +1 -1
- data/lib/rigor/effects/unit_scan.rb +10 -10
- data/lib/rigor/effects/unknown_label_check.rb +3 -8
- data/lib/rigor/effects/unknown_label_report.rb +3 -4
- data/lib/rigor/environment/bundle_sig_discovery.rb +6 -6
- data/lib/rigor/environment/class_registry.rb +3 -1
- data/lib/rigor/environment/failure_slot.rb +2 -2
- data/lib/rigor/environment/installed_gem_set.rb +85 -0
- data/lib/rigor/environment/lockfile_resolver.rb +51 -4
- data/lib/rigor/environment/missing_gem_constant_index.rb +4 -4
- data/lib/rigor/environment/rbs_collection_discovery.rb +5 -5
- data/lib/rigor/environment/rbs_coverage_report.rb +5 -5
- data/lib/rigor/environment/rbs_hierarchy.rb +3 -1
- data/lib/rigor/environment/rbs_loader.rb +388 -59
- data/lib/rigor/environment.rb +105 -20
- data/lib/rigor/flow_contribution/merger.rb +0 -2
- data/lib/rigor/flow_contribution.rb +11 -13
- data/lib/rigor/inference/acceptance.rb +153 -8
- data/lib/rigor/inference/block_parameter_binder.rb +2 -3
- data/lib/rigor/inference/body_fixpoint.rb +5 -5
- data/lib/rigor/inference/budget_trace.rb +1 -5
- data/lib/rigor/inference/captured_locals.rb +2 -3
- data/lib/rigor/inference/closure_escape_analyzer.rb +2 -4
- data/lib/rigor/inference/coverage_scanner.rb +4 -6
- data/lib/rigor/inference/dynamic_origin.rb +1 -1
- data/lib/rigor/inference/element_read_widening.rb +168 -0
- data/lib/rigor/inference/expression_typer.rb +763 -143
- data/lib/rigor/inference/fork_map.rb +5 -7
- data/lib/rigor/inference/hkt_reducer.rb +2 -3
- data/lib/rigor/inference/hkt_registry.rb +4 -7
- data/lib/rigor/inference/index_write_widening.rb +1 -5
- data/lib/rigor/inference/macro_block_self_type.rb +1 -4
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +2 -9
- data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +305 -19
- data/lib/rigor/inference/method_dispatcher/data_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/file_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/json_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -1
- data/lib/rigor/inference/method_dispatcher/math_folding.rb +41 -3
- data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -5
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +61 -47
- data/lib/rigor/inference/method_dispatcher/random_folding.rb +82 -0
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +165 -31
- data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +25 -4
- data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +2 -8
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +1 -2
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +4 -5
- data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +85 -4
- data/lib/rigor/inference/method_dispatcher/time_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +1 -2
- data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher.rb +12 -13
- data/lib/rigor/inference/method_parameter_binder.rb +4 -6
- data/lib/rigor/inference/multi_target_binder.rb +1 -3
- data/lib/rigor/inference/mutation_rejoin.rb +168 -0
- data/lib/rigor/inference/mutation_widening.rb +100 -51
- data/lib/rigor/inference/narrowing.rb +299 -46
- data/lib/rigor/inference/optimistic_origin.rb +1 -7
- data/lib/rigor/inference/origin_lookup.rb +2 -3
- data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
- data/lib/rigor/inference/pre_eval_constants.rb +6 -5
- data/lib/rigor/inference/precision_scanner.rb +3 -4
- data/lib/rigor/inference/project_patched_methods.rb +2 -2
- data/lib/rigor/inference/project_patched_scanner.rb +3 -3
- data/lib/rigor/inference/protection_scanner.rb +1 -2
- data/lib/rigor/inference/range_constant.rb +57 -0
- data/lib/rigor/inference/rbs_type_translator.rb +49 -11
- data/lib/rigor/inference/receiver_alias.rb +3 -3
- data/lib/rigor/inference/refinement_mutation.rb +72 -0
- data/lib/rigor/inference/scope_indexer.rb +688 -151
- data/lib/rigor/inference/statement_evaluator.rb +221 -50
- data/lib/rigor/inference/string_mutation.rb +60 -0
- data/lib/rigor/inference/struct_fold_safety.rb +6 -7
- data/lib/rigor/inference/synthetic_method_index.rb +1 -2
- data/lib/rigor/inference/synthetic_method_scanner.rb +4 -6
- data/lib/rigor/inference/version_guard.rb +28 -21
- data/lib/rigor/inference/void_origin.rb +3 -3
- data/lib/rigor/inference/void_tail_summary.rb +2 -4
- data/lib/rigor/language_server/buffer_table.rb +4 -4
- data/lib/rigor/language_server/completion_provider.rb +1 -1
- data/lib/rigor/language_server/debouncer.rb +1 -1
- data/lib/rigor/language_server/diagnostic_publisher.rb +4 -4
- data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
- data/lib/rigor/language_server/folding_range_provider.rb +1 -1
- data/lib/rigor/language_server/hover_provider.rb +1 -1
- data/lib/rigor/language_server/hover_renderer.rb +2 -2
- data/lib/rigor/language_server/incremental_sync.rb +6 -6
- data/lib/rigor/language_server/project_context.rb +2 -5
- data/lib/rigor/language_server/publish_batcher.rb +2 -2
- data/lib/rigor/language_server/selection_range_provider.rb +2 -2
- data/lib/rigor/language_server/server.rb +9 -9
- data/lib/rigor/language_server/signature_help_provider.rb +1 -1
- data/lib/rigor/language_server/uri.rb +1 -1
- data/lib/rigor/plugin/base.rb +90 -21
- data/lib/rigor/plugin/bundled_catalog.rb +167 -0
- data/lib/rigor/plugin/effect_attribution.rb +7 -7
- data/lib/rigor/plugin/effect_entry_points.rb +3 -3
- data/lib/rigor/plugin/fact_store.rb +6 -6
- data/lib/rigor/plugin/io_boundary.rb +93 -8
- data/lib/rigor/plugin/isolation.rb +29 -6
- data/lib/rigor/plugin/loader.rb +31 -12
- data/lib/rigor/plugin/macro/heredoc_template.rb +1 -1
- data/lib/rigor/plugin/macro/trait_registry.rb +1 -1
- data/lib/rigor/plugin/manifest.rb +23 -1
- data/lib/rigor/plugin/registry.rb +42 -12
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +12 -1
- data/lib/rigor/plugin/trust_policy.rb +2 -4
- data/lib/rigor/plugin/type_node_resolver.rb +3 -3
- data/lib/rigor/plugin_gap_advisory.rb +96 -0
- data/lib/rigor/project_environment.rb +138 -0
- data/lib/rigor/protection/analysis_guard.rb +93 -14
- data/lib/rigor/protection/closure_kill_oracle.rb +67 -23
- data/lib/rigor/protection/dependency_closure.rb +5 -8
- data/lib/rigor/protection/diagnostic_oracle.rb +2 -2
- data/lib/rigor/protection/discovery_seed.rb +11 -11
- data/lib/rigor/protection/kill_signature.rb +2 -4
- data/lib/rigor/protection/measurement_integrity.rb +1 -3
- data/lib/rigor/protection/mutation_cache.rb +6 -9
- data/lib/rigor/protection/mutation_scanner.rb +12 -15
- data/lib/rigor/protection/mutator.rb +2 -1
- data/lib/rigor/protection/test_suite_oracle.rb +5 -5
- data/lib/rigor/rbs_extended/conformance_checker.rb +4 -3
- data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
- data/lib/rigor/rbs_extended/reporter.rb +36 -10
- data/lib/rigor/rbs_extended.rb +61 -21
- data/lib/rigor/reflection/constant_path.rb +126 -0
- data/lib/rigor/reflection.rb +26 -10
- data/lib/rigor/runtime/jit.rb +6 -8
- data/lib/rigor/scope/discovery_index.rb +30 -0
- data/lib/rigor/scope.rb +203 -4
- data/lib/rigor/sig_gen/generator.rb +115 -22
- data/lib/rigor/sig_gen/layout_index.rb +3 -4
- data/lib/rigor/sig_gen/meta_class_shape.rb +3 -5
- data/lib/rigor/sig_gen/observation_collector.rb +17 -15
- data/lib/rigor/sig_gen/path_mapper.rb +4 -6
- data/lib/rigor/sig_gen/rbs_validity.rb +4 -4
- data/lib/rigor/sig_gen/renderer.rb +4 -5
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +111 -0
- data/lib/rigor/sig_gen/superclass_spelling.rb +27 -0
- data/lib/rigor/sig_gen/type_elaborator.rb +1 -3
- data/lib/rigor/sig_gen/writer.rb +23 -6
- data/lib/rigor/signature_path_audit.rb +153 -6
- data/lib/rigor/source/literals.rb +0 -23
- data/lib/rigor/source/node_children.rb +0 -2
- data/lib/rigor/source/node_locator.rb +5 -11
- data/lib/rigor/source/node_walker.rb +2 -5
- data/lib/rigor/triage/catalogue.rb +1 -3
- data/lib/rigor/triage.rb +3 -5
- data/lib/rigor/type/accepts_result.rb +24 -6
- data/lib/rigor/type/combinator.rb +68 -5
- data/lib/rigor/type/data_class.rb +2 -2
- data/lib/rigor/type/data_instance.rb +4 -4
- data/lib/rigor/type/float_range.rb +128 -0
- data/lib/rigor/type/hash_shape.rb +5 -5
- data/lib/rigor/type/integer_range.rb +13 -8
- data/lib/rigor/type/nominal.rb +8 -2
- data/lib/rigor/type/refined.rb +3 -3
- data/lib/rigor/type/struct_class.rb +3 -3
- data/lib/rigor/type/struct_instance.rb +4 -4
- data/lib/rigor/type.rb +1 -0
- data/lib/rigor/type_node/generic.rb +1 -1
- data/lib/rigor/type_node/range_literal.rb +25 -0
- data/lib/rigor/type_node/resolver_chain.rb +1 -1
- data/lib/rigor/type_node.rb +1 -0
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +0 -4
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +0 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +0 -2
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -0
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +0 -4
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +3 -5
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +1 -4
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +1 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +33 -30
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +0 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -3
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +28 -2
- data/plugins/rigor-actionpack/sig/action_controller.rbs +71 -0
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +0 -4
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +0 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +0 -2
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +1 -0
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +3 -4
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +1 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +1 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +42 -7
- data/plugins/rigor-activerecord/sig/active_record/framework.rbs +123 -0
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +1 -0
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +9 -2
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +119 -14
- data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +1 -0
- data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +1 -0
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +9 -7
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +7 -3
- data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +1 -0
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +8 -6
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +11 -7
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -11
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +9 -4
- data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +1 -0
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +0 -5
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +0 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +0 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +8 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +36 -4
- data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +1 -0
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +8 -6
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +6 -3
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +1 -0
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -1
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +0 -3
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +1 -0
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +0 -5
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -2
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +0 -1
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +0 -1
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +1 -0
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +0 -12
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +1 -3
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +0 -1
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +1 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/acronyms.rb +5 -5
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -10
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +3 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +2 -2
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +2 -2
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +2 -2
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +9 -10
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +2 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -0
- data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +1 -0
- data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +13 -1
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +128 -3
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +0 -3
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +0 -3
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -5
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +2 -3
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +1 -0
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +0 -3
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +1 -0
- data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +1 -0
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +1 -9
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +1 -0
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +0 -4
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/effects.rb +1 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +1 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +0 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +0 -2
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +1 -0
- data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -0
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +2 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +1 -4
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +3 -9
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +4 -4
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +1 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +4 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +7 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +5 -5
- data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +1 -0
- data/sig/rigor/analysis/reachability/scan_cache.rbs +12 -0
- data/sig/rigor/cache.rbs +2 -3
- data/sig/rigor/environment.rbs +5 -0
- data/sig/rigor/inference.rbs +3 -10
- data/sig/rigor/plugin/io_boundary.rbs +4 -0
- data/sig/rigor/plugin/manifest.rbs +2 -1
- data/sig/rigor/reflection.rbs +3 -1
- data/sig/rigor/scope.rbs +24 -3
- data/sig/rigor/sig_gen/skip_reason_catalog.rbs +14 -0
- data/sig/rigor/type.rbs +27 -1
- data/sig/rigor.rbs +1 -0
- data/skills/rigor-ask/SKILL.md +8 -6
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +13 -2
- data/skills/rigor-project-init/SKILL.md +6 -3
- data/skills/rigor-project-init/references/02-configure.md +10 -6
- data/skills/rigor-project-init/references/06-agent-contract.md +67 -0
- data/skills/rigor-type-oracle/SKILL.md +228 -0
- data/skills/rigor-type-oracle/references/01-oracle-commands.md +261 -0
- data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +52 -0
- data/skills/rigor-type-oracle/references/03-gap-protocol.md +128 -0
- data/skills/rigor-unused-adjudicate/SKILL.md +8 -5
- metadata +26 -3
- data/lib/rigor/cli/probe_environment.rb +0 -85
- data/sig/rigor/inference/builtins/numeric_catalog.rbs +0 -3
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "blueprint"
|
|
4
|
+
# Issue #806 — {Registry#guarded_manifest} constructs one. `Rigor::Plugin::LoadError` MUST be defined by
|
|
5
|
+
# then: an unresolved bare `LoadError` in this lexical scope silently falls back to Ruby's own ::LoadError.
|
|
6
|
+
require_relative "load_error"
|
|
4
7
|
|
|
5
8
|
module Rigor
|
|
6
9
|
module Plugin
|
|
@@ -217,23 +220,26 @@ module Rigor
|
|
|
217
220
|
class Registry
|
|
218
221
|
attr_reader :plugins, :load_errors, :blueprints, :contribution_index, :resolved_gem_paths
|
|
219
222
|
|
|
220
|
-
# @param plugins
|
|
221
|
-
# @param load_errors
|
|
223
|
+
# @param plugins — instantiated plugin instances in deterministic order.
|
|
224
|
+
# @param load_errors — failures surfaced during loading. Each error is
|
|
222
225
|
# also turned into a diagnostic by the runner.
|
|
223
|
-
# @param blueprints
|
|
226
|
+
# @param blueprints — frozen, Ractor-shareable replay descriptors
|
|
224
227
|
# aligned 1:1 with `plugins`. The loader fills this in; callers that construct Registry manually MAY
|
|
225
228
|
# pass `[]` and accept that {.materialize} cannot replay the set.
|
|
226
|
-
# @param resolved_gem_paths
|
|
229
|
+
# @param resolved_gem_paths — #194 slice 1 — `gem name => resolved file path`
|
|
227
230
|
# for each successfully required plugin gem, so `rigor plugins` can print where a loaded (and
|
|
228
231
|
# possibly frozen) plugin actually loaded from. Defaults to empty; a worker registry built by
|
|
229
232
|
# {.materialize} carries none (the provenance surface runs only on the coordinator).
|
|
230
233
|
def initialize(plugins: [], load_errors: [], blueprints: [], resolved_gem_paths: {})
|
|
231
234
|
@plugins = plugins.dup.freeze
|
|
232
|
-
@load_errors = load_errors.dup.freeze
|
|
233
235
|
@blueprints = blueprints.dup.freeze
|
|
234
236
|
@resolved_gem_paths = resolved_gem_paths.dup.freeze
|
|
235
237
|
@contribution_index = ContributionIndex.new(@plugins)
|
|
236
|
-
|
|
238
|
+
# Issue #806 — every manifest the construction-time aggregates need is read ONCE here, behind a
|
|
239
|
+
# per-plugin rescue, and a plugin that raises joins `load_errors` instead of taking the run down.
|
|
240
|
+
manifest_errors = []
|
|
241
|
+
compile_aggregates(@plugins.map { |plugin| guarded_manifest(plugin, manifest_errors) })
|
|
242
|
+
@load_errors = (load_errors + manifest_errors).freeze
|
|
237
243
|
# ADR-52 WD4 — the single engine-owned node-rule walk, compiled once per run from the node-rule
|
|
238
244
|
# plugin subset (registry order). The runner reuses it for every file; it builds fresh per-file
|
|
239
245
|
# state internally, so it is safe to freeze and share.
|
|
@@ -306,9 +312,14 @@ module Rigor
|
|
|
306
312
|
# in plugin registration order. `Environment#build_name_scope` builds a `TypeNode::ResolverChain` from
|
|
307
313
|
# this list (environment.rb). The first non-nil `#resolve(node, scope)` return wins per ADR-13 WD3 /
|
|
308
314
|
# WD5 — registration order is the user's lever.
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
315
|
+
#
|
|
316
|
+
# Issue #806 — compiled at construction alongside its `compile_aggregates` siblings, and for a second
|
|
317
|
+
# reason beyond theirs: `Environment#build_name_scope` demands it during Environment CONSTRUCTION, so
|
|
318
|
+
# the previous per-call `plugin.manifest` read had no rescue above it anywhere — a plugin raising here
|
|
319
|
+
# aborted every `Environment.for_project`, i.e. the whole run, before any seam could record it. The
|
|
320
|
+
# guard has to sit at registry construction because that is the last point at which a failure can still
|
|
321
|
+
# join `load_errors` (the registry freezes immediately after).
|
|
322
|
+
attr_reader :type_node_resolvers
|
|
312
323
|
|
|
313
324
|
# ADR-20 slice 6 — aggregate every loaded plugin's manifest-declared HKT registrations + definitions
|
|
314
325
|
# into a single `Inference::HktRegistry` overlay that `Environment#hkt_registry` merges on top of the
|
|
@@ -419,10 +430,14 @@ module Rigor
|
|
|
419
430
|
# compiled once at construction (the registry is frozen, so the flat_map-on-every-call versions
|
|
420
431
|
# re-derived an invariant). `@contracts_by_path` is a mutable per-path memo inside the frozen registry
|
|
421
432
|
# — safe because the contract set and the glob semantics are fixed for the lifetime of the run.
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
433
|
+
#
|
|
434
|
+
# @param manifests — one entry per plugin, positionally aligned with `@plugins`;
|
|
435
|
+
# nil where {#guarded_manifest} caught a raising manifest read.
|
|
436
|
+
def compile_aggregates(manifests)
|
|
437
|
+
@additional_initializers = manifests.flat_map { |m| m&.additional_initializers || [] }.freeze
|
|
438
|
+
@open_receivers = manifests.flat_map { |m| (m&.open_receivers || []).map(&:to_s) }.uniq.freeze
|
|
425
439
|
@open_receivers_set = @open_receivers.to_set.freeze
|
|
440
|
+
@type_node_resolvers = manifests.flat_map { |m| m&.type_node_resolvers || [] }.freeze
|
|
426
441
|
@protocol_contracts = @plugins.flat_map { |p| safe_protocol_contracts(p) }.freeze
|
|
427
442
|
@contracts_by_path = {}
|
|
428
443
|
@effect_memo = {}
|
|
@@ -466,6 +481,21 @@ module Rigor
|
|
|
466
481
|
nil
|
|
467
482
|
end
|
|
468
483
|
|
|
484
|
+
# Issue #806 — {#safe_manifest} with a receipt. The construction-time aggregates degraded silently
|
|
485
|
+
# when a manifest read raised, so a plugin that contributed nothing looked identical to a plugin that
|
|
486
|
+
# was never asked; the load-error channel is where "a plugin could not be loaded" already lives, and
|
|
487
|
+
# `Analysis::Runner` turns each entry into one `plugin_loader.load-error` row naming the plugin. The
|
|
488
|
+
# plugin ref is the CLASS, not `manifest.id`: the manifest is the surface that just failed.
|
|
489
|
+
def guarded_manifest(plugin, errors)
|
|
490
|
+
plugin.manifest
|
|
491
|
+
rescue StandardError => e
|
|
492
|
+
errors << LoadError.new(
|
|
493
|
+
"plugin #{plugin.class} raised while reading its manifest: #{e.class}: #{e.message}",
|
|
494
|
+
plugin_ref: plugin.class.to_s, cause: e
|
|
495
|
+
)
|
|
496
|
+
nil
|
|
497
|
+
end
|
|
498
|
+
|
|
469
499
|
def safe_protocol_contracts(plugin)
|
|
470
500
|
plugin.protocol_contracts || []
|
|
471
501
|
rescue StandardError
|
|
@@ -25,15 +25,26 @@ module Rigor
|
|
|
25
25
|
|
|
26
26
|
def initialize
|
|
27
27
|
@entries = []
|
|
28
|
+
@seen = {}
|
|
28
29
|
end
|
|
29
30
|
|
|
31
|
+
# Identical entries are collapsed. Each becomes one `:info` row, and two rows saying the same thing
|
|
32
|
+
# about the same file carry nothing the first does not: the synthesizer's own per-file notices are
|
|
33
|
+
# already `uniq`, but a worker pool re-runs the whole env build in EVERY worker over the same project
|
|
34
|
+
# file list and then drains each worker's stream into this one reporter, so without the dedup a
|
|
35
|
+
# `--workers N` run printed N copies of a row a `--workers=0` run printed once. The same collapse the
|
|
36
|
+
# {RbsExtended::Reporter} drain documents, for the same reason.
|
|
30
37
|
def record(plugin_id:, path:, message:, kind: :failed)
|
|
31
|
-
|
|
38
|
+
entry = Entry.new(
|
|
32
39
|
plugin_id: plugin_id.to_s.dup.freeze,
|
|
33
40
|
path: path.to_s.dup.freeze,
|
|
34
41
|
message: message.to_s.dup.freeze,
|
|
35
42
|
kind: kind
|
|
36
43
|
)
|
|
44
|
+
return nil if @seen.key?(entry)
|
|
45
|
+
|
|
46
|
+
@seen[entry] = true
|
|
47
|
+
@entries << entry
|
|
37
48
|
nil
|
|
38
49
|
end
|
|
39
50
|
|
|
@@ -45,8 +45,7 @@ module Rigor
|
|
|
45
45
|
freeze
|
|
46
46
|
end
|
|
47
47
|
|
|
48
|
-
# @
|
|
49
|
-
# @return [Boolean] true when the absolute path falls inside any allowed read root. Symlinks are
|
|
48
|
+
# @return true when the absolute path falls inside any allowed read root. Symlinks are
|
|
50
49
|
# resolved through `File.expand_path` only (no `realpath`); plugins with adversarial intent are out of
|
|
51
50
|
# scope per ADR-2.
|
|
52
51
|
def allow_read?(path)
|
|
@@ -58,8 +57,7 @@ module Rigor
|
|
|
58
57
|
@network_policy != :disabled
|
|
59
58
|
end
|
|
60
59
|
|
|
61
|
-
# @
|
|
62
|
-
# @return [Boolean] true when the URL scheme is `https` and the parsed hostname is in
|
|
60
|
+
# @return true when the URL scheme is `https` and the parsed hostname is in
|
|
63
61
|
# `allowed_url_hosts`. Always `false` while `network_policy` is `:disabled`.
|
|
64
62
|
def allow_url?(url)
|
|
65
63
|
return false if @network_policy == :disabled
|
|
@@ -28,11 +28,11 @@ module Rigor
|
|
|
28
28
|
# Resolvers SHOULD be stateless and re-entrant; the registry builds the chain once per
|
|
29
29
|
# `Analysis::Runner.run` and may consult any resolver multiple times for the same node.
|
|
30
30
|
class TypeNodeResolver
|
|
31
|
-
# @param node
|
|
31
|
+
# @param node — the parser-emitted node the chain is
|
|
32
32
|
# asking about.
|
|
33
|
-
# @param scope
|
|
33
|
+
# @param scope — companion value object (slice 3); slice 2 invocations MAY pass
|
|
34
34
|
# `nil` because the chain doesn't exist yet.
|
|
35
|
-
# @return
|
|
35
|
+
# @return resolved type, or `nil` to fall through.
|
|
36
36
|
def resolve(node, scope) # rubocop:disable Lint/UnusedMethodArgument
|
|
37
37
|
nil
|
|
38
38
|
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "plugin/bundled_catalog"
|
|
4
|
+
require_relative "environment/lockfile_resolver"
|
|
5
|
+
|
|
6
|
+
module Rigor
|
|
7
|
+
# ADR-96 WD2 — the plugin-gap advisory: a gem the project itself depends on that a bundled plugin declares
|
|
8
|
+
# in `target_gems:`, with that plugin absent from `plugins:`.
|
|
9
|
+
#
|
|
10
|
+
# One home for a fact that ADR-96 found in four hand-maintained copies, none of which was the plugin.
|
|
11
|
+
# `rigor doctor` and `rigor skill describe`'s project probe both route through here, so the two can no
|
|
12
|
+
# longer disagree — they were byte-identical Rails tables by luck, not by construction.
|
|
13
|
+
#
|
|
14
|
+
# The advisory is deliberately advice, not activation: naming a plugin never loads it (ADR-96 Criterion 2),
|
|
15
|
+
# and declining a plugin is a legitimate choice, which is why a gap is a warning and never a failure.
|
|
16
|
+
module PluginGapAdvisory
|
|
17
|
+
# One unenabled plugin and the depended-on gems that make it relevant.
|
|
18
|
+
Gap = Data.define(:plugin_gem, :plugin_id, :locked_gems)
|
|
19
|
+
|
|
20
|
+
# A gem whose whole point is to pull its constituents in. A Rails application's `Gemfile` says `rails`
|
|
21
|
+
# and never `activerecord`, so a direct-dependency match alone would silence the entire Rails family for
|
|
22
|
+
# the projects the advisory exists for. The table is explicit rather than derived: expanding whatever a
|
|
23
|
+
# gem happens to resolve to is the transitive read this advisory is here to avoid, and the set of gems
|
|
24
|
+
# that are genuinely umbrellas is small and known.
|
|
25
|
+
UMBRELLA_GEMS = {
|
|
26
|
+
"rails" => %w[
|
|
27
|
+
actioncable actionmailer actionpack actiontext actionview activejob activemodel
|
|
28
|
+
activerecord activestorage activesupport railties
|
|
29
|
+
].freeze
|
|
30
|
+
}.freeze
|
|
31
|
+
|
|
32
|
+
class << self
|
|
33
|
+
# @param project_root — the directory whose `Gemfile.lock` and `plugins:` list are read.
|
|
34
|
+
# @param plugins — the configuration's raw `plugins:` entries (String or Hash form).
|
|
35
|
+
# @return the gaps, sorted by plugin gem name; empty when the project declares no dependency.
|
|
36
|
+
def gaps(project_root:, plugins:)
|
|
37
|
+
depended = depended_gems(project_root)
|
|
38
|
+
return [] if depended.empty?
|
|
39
|
+
|
|
40
|
+
listed = listed_plugin_names(plugins)
|
|
41
|
+
Plugin::BundledCatalog.entries.filter_map do |entry|
|
|
42
|
+
next if listed?(entry, listed)
|
|
43
|
+
|
|
44
|
+
matched = entry.target_gems.select { |gem| depended.include?(gem) }
|
|
45
|
+
next if matched.empty?
|
|
46
|
+
|
|
47
|
+
Gap.new(plugin_gem: entry.gem_name, plugin_id: entry.plugin_id, locked_gems: matched.freeze)
|
|
48
|
+
end.sort_by(&:plugin_gem)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Whether the project enables no bundled plugin at all for the gems it depends on — ADR-96's preserved
|
|
52
|
+
# `:fail` case, generalised off the Rails-only table it replaces. A project that enables *some* of the
|
|
53
|
+
# matching plugins has made choices; one that enables none of them is unconfigured for its own stack.
|
|
54
|
+
def unconfigured?(project_root:, plugins:)
|
|
55
|
+
found = gaps(project_root: project_root, plugins: plugins)
|
|
56
|
+
return false if found.empty?
|
|
57
|
+
|
|
58
|
+
listed = listed_plugin_names(plugins)
|
|
59
|
+
Plugin::BundledCatalog.entries.none? { |entry| listed?(entry, listed) }
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
# The gems the project chose, which is the `DEPENDENCIES` section and never the resolved graph. A
|
|
65
|
+
# transitive `minitest` / `i18n` / `ffi` is somebody else's dependency, and advising on it fires on
|
|
66
|
+
# correct configuration — including, through {unconfigured?}, as a hard failure.
|
|
67
|
+
def depended_gems(project_root)
|
|
68
|
+
direct = Environment::LockfileResolver.direct_dependency_names(
|
|
69
|
+
lockfile_path: nil, project_root: project_root
|
|
70
|
+
)
|
|
71
|
+
return direct if direct.empty?
|
|
72
|
+
|
|
73
|
+
direct + direct.flat_map { |gem| UMBRELLA_GEMS.fetch(gem, []) }
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Both spellings a `plugins:` entry may use for the same plugin — the gem name and the manifest id.
|
|
77
|
+
# An `enabled: false` entry still counts as naming it: the user has demonstrably found the plugin, so
|
|
78
|
+
# advising them to add what is already there would be worse than staying quiet.
|
|
79
|
+
def listed_plugin_names(plugins)
|
|
80
|
+
Array(plugins).flat_map do |raw|
|
|
81
|
+
case raw
|
|
82
|
+
when String then [raw]
|
|
83
|
+
when Hash
|
|
84
|
+
keyed = raw.to_h { |key, value| [key.to_s, value] }
|
|
85
|
+
[keyed["gem"], keyed["id"]].compact.map(&:to_s)
|
|
86
|
+
else []
|
|
87
|
+
end
|
|
88
|
+
end.to_set
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def listed?(entry, listed)
|
|
92
|
+
listed.include?(entry.gem_name) || listed.include?(entry.plugin_id)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "environment"
|
|
4
|
+
require_relative "plugin"
|
|
5
|
+
require_relative "plugin/loader"
|
|
6
|
+
require_relative "plugin/services"
|
|
7
|
+
require_relative "reflection"
|
|
8
|
+
require_relative "type/combinator"
|
|
9
|
+
|
|
10
|
+
module Rigor
|
|
11
|
+
# Shared construction of the project environment for every command that types against the project WITHOUT
|
|
12
|
+
# driving a full {Analysis::Runner}: the single-shot probes (`type-of`, `type-scan`, `trace`, `annotate`) and
|
|
13
|
+
# the `sig-gen` generator / observation collector. Each of these answers "what does the engine see here?" for
|
|
14
|
+
# a hand-picked file, position or class, so the type universe they read MUST match the one `rigor check`
|
|
15
|
+
# analyses against — otherwise the command reports, or declines to emit, against a universe the real run never
|
|
16
|
+
# had.
|
|
17
|
+
#
|
|
18
|
+
# Two gaps this closes, both the same shape:
|
|
19
|
+
#
|
|
20
|
+
# - The probes historically built their environment with `Environment.for_project(libraries:,
|
|
21
|
+
# signature_paths:)` only — no plugin registry, no `source_files:`. The `source_rbs_synthesizer` plugin hook
|
|
22
|
+
# (ADR-32 / ADR-93's auto-wired `rigor-rbs-inline`) therefore never ran there, so a class whose only
|
|
23
|
+
# signature comes from an inline `#: () -> void` annotation typed as `Dynamic[top]` under a probe while
|
|
24
|
+
# `check` resolved it through the synthesized RBS. That divergence misattributed a dispatch tier during the
|
|
25
|
+
# #162 transitive-void design pass (see the 2026-07-19 addendum in
|
|
26
|
+
# docs/adr/100-static-diagnostic-family-and-void-origins.md).
|
|
27
|
+
# - Neither the probes nor `sig-gen` passed the dependency-discovery axes — the `rbs collection` lockfile, the
|
|
28
|
+
# bundle's per-gem `sig/`, the Gemfile.lock gating them — that `check` reads straight off the same
|
|
29
|
+
# configuration. On a Rails project with `rbs collection install` run, `sig-gen` therefore could not see
|
|
30
|
+
# `ActiveRecord::Base` at all, and its superclass-without-RBS skip guard declined to emit every model
|
|
31
|
+
# (issue #821). The advice the guard prints — install the collection — had no effect, because the command
|
|
32
|
+
# never looked at it.
|
|
33
|
+
#
|
|
34
|
+
# {.dependency_discovery_options} is the single spelling of those axes; `Runner`'s build path
|
|
35
|
+
# (`Analysis::Runner::PoolCoordinator#build_runner_environment`), the worker session and the LSP project
|
|
36
|
+
# context read it too, so a new axis cannot be added to `check` and silently missed here.
|
|
37
|
+
#
|
|
38
|
+
# A non-`check` command legitimately simplifies in two places: no synthesis-failure reporter (there is no
|
|
39
|
+
# diagnostic pipeline) and no synthesis cache store (a single-position probe recomputes cheaply). Plugin
|
|
40
|
+
# loading itself matches check — same `.rigor.yml` `plugins:` config, same ADR-93 auto-wire (already applied
|
|
41
|
+
# by `Configuration.load`), same `enabled:` / `require_magic_comment` semantics via `Plugin::Loader.load`.
|
|
42
|
+
#
|
|
43
|
+
# Fail-soft is the invariant: a project with no plugins, an unresolvable plugin gem, or any error during the
|
|
44
|
+
# plugin-aware build degrades — never crashes. `Plugin::Loader` already isolates per-entry load failures onto
|
|
45
|
+
# the registry; the `rescue` here is the belt-and-suspenders around anything the loader or the plugin-aware
|
|
46
|
+
# env build itself might raise.
|
|
47
|
+
module ProjectEnvironment
|
|
48
|
+
module_function
|
|
49
|
+
|
|
50
|
+
# Builds the plugin-aware, dependency-aware {Rigor::Environment} a non-`check` command works against.
|
|
51
|
+
#
|
|
52
|
+
# @param configuration — the loaded project configuration (already carries the
|
|
53
|
+
# ADR-93 auto-wired `rigor-rbs-inline` entry when the library is resolvable).
|
|
54
|
+
# @param source_files — the file(s) the command inspects. Threaded so each loaded plugin's
|
|
55
|
+
# `source_rbs_synthesizer` runs over them at env-build time; an empty list contributes no synthesized RBS.
|
|
56
|
+
def build(configuration:, source_files:)
|
|
57
|
+
Environment.for_project(
|
|
58
|
+
libraries: configuration.libraries,
|
|
59
|
+
signature_paths: configuration.signature_paths,
|
|
60
|
+
plugin_registry: load_plugin_registry(configuration),
|
|
61
|
+
source_files: source_files,
|
|
62
|
+
**dependency_discovery_options(configuration)
|
|
63
|
+
)
|
|
64
|
+
rescue StandardError
|
|
65
|
+
bare(configuration)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# The dependency-discovery axes `check` reads off the configuration, as a keyword hash. Extracted so the
|
|
69
|
+
# list lives in one place: every command that types against the project's real dependencies passes exactly
|
|
70
|
+
# these, and adding an axis to `Environment.for_project` means adding it here once.
|
|
71
|
+
def dependency_discovery_options(configuration)
|
|
72
|
+
{
|
|
73
|
+
bundler_bundle_path: configuration.bundler_bundle_path,
|
|
74
|
+
bundler_auto_detect: configuration.bundler_auto_detect,
|
|
75
|
+
bundler_lockfile: configuration.bundler_lockfile,
|
|
76
|
+
rbs_collection_lockfile: configuration.rbs_collection_lockfile,
|
|
77
|
+
rbs_collection_auto_detect: configuration.rbs_collection_auto_detect
|
|
78
|
+
}
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Loads the project's configured plugins the same way the plugin-inspection commands (`rigor plugins`,
|
|
82
|
+
# `rigor doctor`) do: a `Plugin::Services` with no cache store (these commands recompute) driving
|
|
83
|
+
# `Plugin::Loader.load`. Returns `nil` — so `Environment.for_project` skips the plugin tier entirely — when
|
|
84
|
+
# the project declares no plugins.
|
|
85
|
+
def load_plugin_registry(configuration)
|
|
86
|
+
return nil if configuration.plugins.empty?
|
|
87
|
+
|
|
88
|
+
services = Plugin::Services.new(
|
|
89
|
+
reflection: Reflection,
|
|
90
|
+
type: Type::Combinator,
|
|
91
|
+
configuration: configuration,
|
|
92
|
+
cache_store: nil
|
|
93
|
+
)
|
|
94
|
+
registry = Plugin::Loader.load(configuration: configuration, services: services)
|
|
95
|
+
run_plugin_prepare(registry)
|
|
96
|
+
registry
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# `Analysis::WorkerSession#run_plugin_prepare` runs every plugin's `#prepare(services)` before the real
|
|
100
|
+
# `check` pipeline reads `services.fact_store` — an ADR-9 fact published there (#921's `:factory_index`,
|
|
101
|
+
# `rigor-activerecord`'s `:model_index`, …) is otherwise invisible. The probe commands built on
|
|
102
|
+
# {ProjectEnvironment} share the same plugin registry and the same `read_fact` call sites, so they need
|
|
103
|
+
# the same step; skipping it made every cross-plugin fact silently read nil under `type-of` / `annotate`
|
|
104
|
+
# / `trace` / `sig-gen` even once a plugin published it correctly. Per-plugin `rescue` keeps one plugin's
|
|
105
|
+
# `#prepare` failure from costing the rest — the probes have no diagnostic pipeline to report it through,
|
|
106
|
+
# so it is swallowed the same way the rest of this module fails soft.
|
|
107
|
+
def run_plugin_prepare(registry)
|
|
108
|
+
return if registry.nil? || registry.empty?
|
|
109
|
+
|
|
110
|
+
registry.plugins.each do |plugin|
|
|
111
|
+
plugin.prepare(plugin.services)
|
|
112
|
+
rescue StandardError
|
|
113
|
+
next
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# The first fail-soft floor: no plugin tier and no synthesized RBS, but still the project's own dependency
|
|
118
|
+
# sources — dropping those is what issue #821 was, so a plugin-loading failure must not cost them.
|
|
119
|
+
def bare(configuration)
|
|
120
|
+
Environment.for_project(
|
|
121
|
+
libraries: configuration.libraries,
|
|
122
|
+
signature_paths: configuration.signature_paths,
|
|
123
|
+
**dependency_discovery_options(configuration)
|
|
124
|
+
)
|
|
125
|
+
rescue StandardError
|
|
126
|
+
minimal(configuration)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# The last floor: RBS core plus the project's own signature paths, which cannot fail on anything outside
|
|
130
|
+
# the project itself.
|
|
131
|
+
def minimal(configuration)
|
|
132
|
+
Environment.for_project(
|
|
133
|
+
libraries: configuration.libraries,
|
|
134
|
+
signature_paths: configuration.signature_paths
|
|
135
|
+
)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
@@ -10,7 +10,24 @@ module Rigor
|
|
|
10
10
|
# A named class, not a bare RuntimeError: {MutationScanner#classify} already folds a per-mutant
|
|
11
11
|
# `StandardError` into the `:harness_error` bucket, and the scanner's file-level rescue must catch THIS
|
|
12
12
|
# and nothing else, so the two failure modes stay distinguishable at every site that handles either.
|
|
13
|
-
|
|
13
|
+
#
|
|
14
|
+
# Issue #790 — it says WHICH of the two refusals below fired, because they want different recovery. A
|
|
15
|
+
# crashed check rule left the Environment as it found it; an analyzer defect (the #784 HKT registry
|
|
16
|
+
# build) recorded itself ON that Environment and is memoised there, so the caller that owns the
|
|
17
|
+
# Environment has to replace it before the next mutant is scored. {#analyzer_defect?} is how a caller
|
|
18
|
+
# tells the two apart without re-deriving the classification from the message it was handed.
|
|
19
|
+
class AnalyzerCrashed < StandardError
|
|
20
|
+
def initialize(message = nil, analyzer_defect: false)
|
|
21
|
+
super(message)
|
|
22
|
+
@analyzer_defect = analyzer_defect
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# True when the refusal was an analyzer defect rather than a crashed check rule — the shape whose
|
|
26
|
+
# cause outlives the run that observed it.
|
|
27
|
+
def analyzer_defect?
|
|
28
|
+
@analyzer_defect
|
|
29
|
+
end
|
|
30
|
+
end
|
|
14
31
|
|
|
15
32
|
# The kill oracles' guard against judging a mutant by a run that never happened.
|
|
16
33
|
#
|
|
@@ -27,34 +44,96 @@ module Rigor
|
|
|
27
44
|
# let {MutationScanner} put the mutant in the `harness_errors` bucket (#264), which is already excluded
|
|
28
45
|
# from `killed + survived` and already surfaced by the CLI. A crash then READS as a crash.
|
|
29
46
|
#
|
|
30
|
-
#
|
|
31
|
-
#
|
|
47
|
+
# Two shapes are armed, and only two.
|
|
48
|
+
#
|
|
49
|
+
# {Analysis::Result#crashed?} — the check-rule rescue, the one shape that REPLACES a file's whole
|
|
50
|
+
# diagnostic list. Refusing more than the shapes below is the same error pointed the other way, and it
|
|
32
51
|
# was measured: on a project whose plugin `prepare` raises, refusing the `:plugin` row took a file from
|
|
33
52
|
# `killed=1 survived=6` to `killed=0 survived=0 harness_errors=7` — the builtin rules had run, their
|
|
34
53
|
# diagnostics were all present, and a real measurement was thrown away. `invoke_plugin_prepare` appends
|
|
35
54
|
# its row to every sequential run, so arming it would have refused every run for the life of the
|
|
36
|
-
# process. `:rbs_build`
|
|
37
|
-
# mutations already drops receivers whose type did not resolve, so a class whose
|
|
38
|
-
# build contributes no measured sites to begin with.
|
|
55
|
+
# process. The user-caused `:rbs_build` rungs are excluded for the same reason plus one more: the site
|
|
56
|
+
# filter that admits mutations already drops receivers whose type did not resolve, so a class whose
|
|
57
|
+
# definition failed to build contributes no measured sites to begin with.
|
|
58
|
+
#
|
|
59
|
+
# {Analysis::CrashSignature.analyzer_defect?} (issue #790) — the one `:rbs_build` rung whose cause is
|
|
60
|
+
# Rigor rather than the user's `sig/`: post-#788 a mutant that makes the implicit HKT scan raise no
|
|
61
|
+
# longer produces a per-file `internal analyzer error` but ONE readable `rbs.coverage.hkt-scan-failed`
|
|
62
|
+
# row, so `Result#crashed?` stays false and the mutant was scored as a measurement over a silently
|
|
63
|
+
# degraded universe — issue #776 was exactly such a mutant. The run is readable (every rule fired), so
|
|
64
|
+
# it stays out of `#crashed?`, which answers the user-facing "may I read these diagnostics?"; what this
|
|
65
|
+
# guard asks is the narrower "is this a valid measurement OF RIGOR?", and it is not.
|
|
66
|
+
#
|
|
67
|
+
# The refusal is asked off {Analysis::CrashSignature} rather than off a second string match here, so a
|
|
68
|
+
# reworded row or a fourth rung added to `ANALYZER_DEFECT_RULES` cannot disarm one copy and leave the
|
|
69
|
+
# others armed (#696).
|
|
70
|
+
#
|
|
71
|
+
# The defect half is asked TWICE, of two surfaces, because each covers the other's blind spot. The row
|
|
72
|
+
# is severity-stamped like every other diagnostic, so `severity_overrides:` resolving `rbs` (or the
|
|
73
|
+
# exact rule id) to `off` drops it from the run entirely — and a harness that exists to find analyzer
|
|
74
|
+
# defects must not be the one thing a project's config can hide one from. {Rigor::Environment} records
|
|
75
|
+
# the failure BEFORE any severity resolution, so `#hkt_scan_failure` still answers. Conversely that slot
|
|
76
|
+
# only ever describes THIS process's Environment: under the fork pool the defect happens in a worker's
|
|
77
|
+
# own Environment and reaches the parent as the row alone.
|
|
39
78
|
module AnalysisGuard
|
|
40
79
|
module_function
|
|
41
80
|
|
|
42
|
-
# Takes the whole {Analysis::Result} rather than its diagnostics so the question is asked
|
|
43
|
-
# {Analysis::Result#crashed?} — the same predicate the spec-side guard reads, off the same
|
|
81
|
+
# Takes the whole {Analysis::Result} rather than its diagnostics so the crash question is asked
|
|
82
|
+
# through {Analysis::Result#crashed?} — the same predicate the spec-side guard reads, off the same
|
|
44
83
|
# {Analysis::CrashSignature} table. Hands back the diagnostics, which is all a kill comparison wants.
|
|
45
84
|
#
|
|
46
|
-
# @param result
|
|
47
|
-
# @param context
|
|
48
|
-
# @
|
|
49
|
-
#
|
|
50
|
-
|
|
51
|
-
|
|
85
|
+
# @param result — one analysis run.
|
|
86
|
+
# @param context — which oracle call produced it, so the raise points at the right seam.
|
|
87
|
+
# @param environment — the Environment the run was made over, read for the pre-severity record of a
|
|
88
|
+
# shared-build failure. Optional: a caller that has none loses only the severity-override case.
|
|
89
|
+
# @return the run's diagnostics, when the run was healthy.
|
|
90
|
+
# @raise AnalyzerCrashed
|
|
91
|
+
def checked(result, context:, environment: nil)
|
|
92
|
+
refuse_crash(result, context) if result.crashed?
|
|
93
|
+
defect = result.diagnostics.find { |diagnostic| Analysis::CrashSignature.analyzer_defect?(diagnostic) }
|
|
94
|
+
refuse_defect(context, Analysis::CrashSignature.describe(defect)) if defect
|
|
52
95
|
|
|
96
|
+
failure = shared_build_failure(environment)
|
|
97
|
+
refuse_defect(context, failure) if failure
|
|
98
|
+
|
|
99
|
+
result.diagnostics
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# The pre-severity record, described the way {Analysis::CrashSignature.describe} describes a row, so
|
|
103
|
+
# the two refusals read alike whichever surface saw the defect.
|
|
104
|
+
def shared_build_failure(environment)
|
|
105
|
+
return nil unless environment.respond_to?(:hkt_scan_failure)
|
|
106
|
+
|
|
107
|
+
failure = environment.hkt_scan_failure
|
|
108
|
+
return nil if failure.nil?
|
|
109
|
+
|
|
110
|
+
error_class, message, frame, stage = failure
|
|
111
|
+
"#{stage} HKT registry build raised #{error_class}: #{message}#{" at #{frame}" if frame}"
|
|
112
|
+
end
|
|
113
|
+
private_class_method :shared_build_failure
|
|
114
|
+
|
|
115
|
+
def refuse_crash(result, context)
|
|
53
116
|
raise AnalyzerCrashed,
|
|
54
117
|
"#{context}: the analyzer crashed, so this run's diagnostics say nothing about the code " \
|
|
55
118
|
"(#{Analysis::CrashSignature.describe(result.crash_diagnostics.first)}). A kill comparison " \
|
|
56
119
|
"against it would score the mutant a survivor it was never measured against. See issue #686."
|
|
57
120
|
end
|
|
121
|
+
private_class_method :refuse_crash
|
|
122
|
+
|
|
123
|
+
# The run is readable, so the message says what is wrong with it instead: the universe it was produced
|
|
124
|
+
# over is missing the type constructors the RBS `type` aliases and the loaded plugins would have
|
|
125
|
+
# contributed, and the BASELINE this mutant is compared against carries the identical degradation — so
|
|
126
|
+
# the set difference is empty for a reason that has nothing to do with the mutation.
|
|
127
|
+
def refuse_defect(context, description)
|
|
128
|
+
refusal = AnalyzerCrashed.new(
|
|
129
|
+
"#{context}: the analyzer degraded its own type universe, so this run is readable but is not a " \
|
|
130
|
+
"measurement of Rigor (#{description}). Scoring a mutant against it reports the analyzer's own " \
|
|
131
|
+
"defect as the mutant's survival. See issue #790.",
|
|
132
|
+
analyzer_defect: true
|
|
133
|
+
)
|
|
134
|
+
raise refusal
|
|
135
|
+
end
|
|
136
|
+
private_class_method :refuse_defect
|
|
58
137
|
end
|
|
59
138
|
end
|
|
60
139
|
end
|