rigortype 0.3.7 → 0.3.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +96 -15
- 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 +39 -4
- data/docs/manual/03-configuration.md +11 -0
- data/docs/manual/04-diagnostics.md +12 -1
- 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 +69 -13
- 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 +100 -29
- 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 +316 -40
- 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 +430 -77
- data/lib/rigor/analysis/runner/project_pre_passes.rb +12 -6
- data/lib/rigor/analysis/runner/run_snapshots.rb +13 -3
- data/lib/rigor/analysis/runner.rb +200 -43
- data/lib/rigor/analysis/worker_session.rb +36 -12
- data/lib/rigor/bleeding_edge.rb +12 -24
- data/lib/rigor/builtins/hkt_builtins.rb +2 -2
- data/lib/rigor/builtins/imported_refinements.rb +166 -30
- 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 +70 -0
- 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_environment_marshal_patch.rb +39 -0
- data/lib/rigor/cache/rbs_known_class_names.rb +0 -3
- data/lib/rigor/cache/store.rb +85 -33
- 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 +31 -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 +28 -0
- 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 +613 -113
- data/lib/rigor/environment.rb +189 -27
- 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 +19 -10
- data/lib/rigor/inference/hkt_sugar_translator.rb +15 -21
- 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 +67 -14
- 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 +12 -13
- data/lib/rigor/rbs_extended/hkt_directives.rb +16 -1
- data/lib/rigor/rbs_extended/reporter.rb +122 -16
- data/lib/rigor/rbs_extended.rb +67 -22
- 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 +181 -102
- 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 +17 -11
- 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 +11 -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 +28 -3
- data/lib/rigor/cli/probe_environment.rb +0 -85
- data/sig/rigor/inference/builtins/numeric_catalog.rbs +0 -3
data/lib/rigor/cache/store.rb
CHANGED
|
@@ -38,7 +38,14 @@ module Rigor
|
|
|
38
38
|
# `PAYLOAD_ABI_VERSION` already rebuilds across a RELEASE, so the exposure is a same-version tree; this
|
|
39
39
|
# closes that window too, because "the same project reports differently depending on how you ran it" is
|
|
40
40
|
# the defect the diagnostic exists to end and a stale blob reintroduces it.
|
|
41
|
-
|
|
41
|
+
#
|
|
42
|
+
# v4 (issue #799): `RBS::AST::Annotation` now carries its own `marshal_dump` / `marshal_load`, which
|
|
43
|
+
# keep the annotation's POSITION as well as its file, so `rbs_extended.unsatisfied-conformance` points
|
|
44
|
+
# at the `%a{rigor:v1:conforms-to …}` line warm as well as cold. A pre-change blob loads perfectly well
|
|
45
|
+
# — Marshal encodes an ivar dump and a `marshal_dump` payload differently, and only the latter reaches
|
|
46
|
+
# `marshal_load` — which is exactly the problem: without a bump it would keep reporting the row at
|
|
47
|
+
# `1:1` forever, and `--verify-incremental` would keep failing on that project.
|
|
48
|
+
FORMAT_VERSION = 4
|
|
42
49
|
|
|
43
50
|
# Payload ABI version. Store values are mostly Marshal blobs of Rigor/RBS objects, so a Rigor release
|
|
44
51
|
# upgrade is an ABI boundary even when the byte layout and descriptor schema are unchanged. Folding the
|
|
@@ -58,8 +65,8 @@ module Rigor
|
|
|
58
65
|
|
|
59
66
|
VALID_PRODUCER_ID = /\A[a-z][a-z0-9._-]*\z/
|
|
60
67
|
|
|
61
|
-
# @param root
|
|
62
|
-
# @param read_only
|
|
68
|
+
# @param root — cache root directory.
|
|
69
|
+
# @param read_only — when true, every disk-side side-effect is suppressed: `fetch_or_compute`
|
|
63
70
|
# still reads existing entries (hits, gated on a current `schema_version.txt` marker — see
|
|
64
71
|
# {#ensure_schema_version!}) and still runs the producer block on miss, but it does NOT write the
|
|
65
72
|
# produced value to disk, does NOT update the marker, and does NOT touch the on-disk root directory.
|
|
@@ -100,7 +107,7 @@ module Rigor
|
|
|
100
107
|
|
|
101
108
|
attr_reader :root
|
|
102
109
|
|
|
103
|
-
# @return
|
|
110
|
+
# @return whether this Store suppresses disk writes (`schema_version.txt`, entry creation).
|
|
104
111
|
# Reads are unaffected.
|
|
105
112
|
def read_only?
|
|
106
113
|
@read_only
|
|
@@ -111,7 +118,7 @@ module Rigor
|
|
|
111
118
|
# specific instance rather than the on-disk cache state. Disk-level state is reported separately by
|
|
112
119
|
# {.disk_inventory}.
|
|
113
120
|
#
|
|
114
|
-
# @return
|
|
121
|
+
# @return `{ hits:, misses:, writes:, by_producer: { id => { hits:, misses:, writes: } } }`
|
|
115
122
|
def stats
|
|
116
123
|
@monitor.synchronize do
|
|
117
124
|
per_producer = @by_producer.transform_values { |counts| counts.dup.freeze }.freeze
|
|
@@ -123,7 +130,7 @@ module Rigor
|
|
|
123
130
|
# `rigor check --cache-stats` to surface cache size and per-producer entry counts without depending on
|
|
124
131
|
# in-process counters (which only reflect the current run).
|
|
125
132
|
#
|
|
126
|
-
# @return
|
|
133
|
+
# @return `{ root:, schema_version:, total_entries:, total_bytes:, producers: [{ id:, entries:,
|
|
127
134
|
# bytes: }, ...] }`. When the root does not exist or has no schema-version marker, `schema_version` is
|
|
128
135
|
# nil and the producer list is empty.
|
|
129
136
|
#
|
|
@@ -169,21 +176,21 @@ module Rigor
|
|
|
169
176
|
end
|
|
170
177
|
private_class_method :collect_producers
|
|
171
178
|
|
|
172
|
-
# @param producer_id
|
|
173
|
-
# @param generation_cap
|
|
179
|
+
# @param producer_id — stable cache namespace; only `[a-z][a-z0-9._-]*` is accepted.
|
|
180
|
+
# @param generation_cap — how many generations of this producer survive a compaction
|
|
174
181
|
# pass — a positive `Integer` for a whole-project producer (one live entry, older ones unreachable),
|
|
175
182
|
# or {UNBOUNDED_GENERATIONS} for a producer with many simultaneously-live entries. REQUIRED, and
|
|
176
183
|
# sourced from the producer's own declaration (`RbsCacheProducer.generation_cap`,
|
|
177
184
|
# `RunCacheKey::GENERATION_CAP`, `Plugin::Base.producer generation_cap:`) rather than invented at the
|
|
178
185
|
# call site. See {#evict!}.
|
|
179
|
-
# @param params
|
|
180
|
-
# @param descriptor
|
|
181
|
-
# @param serialize
|
|
186
|
+
# @param params — producer inputs; mixed into the cache key via {Descriptor#cache_key_for}.
|
|
187
|
+
# @param descriptor — the invalidation descriptor for the value being cached.
|
|
188
|
+
# @param serialize — optional callable that turns the producer's return value into a binary
|
|
182
189
|
# `String`. Defaults to `Marshal.dump(value).b`. Producers whose return values are not
|
|
183
190
|
# `Marshal`-clean (RBS-native objects with `RBS::Location` members, raw `IO`, …) MUST provide a
|
|
184
191
|
# serialiser. The pair `(serialize, deserialize)` MUST round-trip — a producer that reads with one
|
|
185
192
|
# strategy and writes with another corrupts its own cache slice.
|
|
186
|
-
# @param deserialize
|
|
193
|
+
# @param deserialize — optional callable that turns bytes back into the producer's value.
|
|
187
194
|
# Defaults to `Marshal.load`. Any exception (`StandardError`) raised by the deserialiser is treated as
|
|
188
195
|
# a cache miss — the entry is considered corrupt, the producer block reruns, and the next write
|
|
189
196
|
# overwrites it. This is consistent with the fault-tolerance contract for the default `Marshal.load`
|
|
@@ -387,10 +394,22 @@ module Rigor
|
|
|
387
394
|
|
|
388
395
|
# Reads and validates one entry file. Any failure (missing, short, bad magic, bad version, bad
|
|
389
396
|
# checksum, unmarshal-able) returns nil so the caller treats it as a cache miss.
|
|
397
|
+
#
|
|
398
|
+
# The `ENOENT` rescue covers the gap between the existence check and the open: a concurrent process
|
|
399
|
+
# compacting or clearing the root can unlink the file in between, and a file that is gone reads as the
|
|
400
|
+
# same miss the existence check above already reports for one that was never there (issue #807). It is
|
|
401
|
+
# deliberately narrow — every other `SystemCallError` (a permission error, an I/O error) still raises,
|
|
402
|
+
# so a genuinely broken filesystem stays visible instead of degrading into an endless recompute. A
|
|
403
|
+
# present-but-corrupt file is not touched by this either: it still goes through the envelope and
|
|
404
|
+
# payload checks below.
|
|
390
405
|
def read_entry(path, deserialize: nil)
|
|
391
406
|
return nil unless File.file?(path)
|
|
392
407
|
|
|
393
|
-
|
|
408
|
+
begin
|
|
409
|
+
bytes = File.binread(path)
|
|
410
|
+
rescue Errno::ENOENT
|
|
411
|
+
return nil
|
|
412
|
+
end
|
|
394
413
|
return nil unless envelope_valid?(bytes)
|
|
395
414
|
|
|
396
415
|
body = bytes.byteslice(HEADER.bytesize, bytes.bytesize - HEADER.bytesize - 32)
|
|
@@ -479,22 +498,34 @@ module Rigor
|
|
|
479
498
|
def atomically_replace(path, body)
|
|
480
499
|
File.open(path, File::RDWR | File::CREAT, 0o644) do |lock_fd|
|
|
481
500
|
lock_fd.flock(File::LOCK_EX)
|
|
482
|
-
|
|
483
|
-
begin
|
|
484
|
-
File.open(tmp, "wb") do |f|
|
|
485
|
-
f.write(body)
|
|
486
|
-
f.fsync
|
|
487
|
-
end
|
|
488
|
-
File.rename(tmp, path)
|
|
489
|
-
ensure
|
|
490
|
-
# A failed write/rename must not leak its temp file — rely on the 1-hour `cleanup_stale_temp_files`
|
|
491
|
-
# sweep only as a backstop for crashes that skip this ensure entirely.
|
|
492
|
-
unlink_entry(tmp) if File.exist?(tmp)
|
|
493
|
-
end
|
|
501
|
+
publish_by_rename(path, body)
|
|
494
502
|
fsync_directory(File.dirname(path))
|
|
495
503
|
end
|
|
496
504
|
end
|
|
497
505
|
|
|
506
|
+
# The rename-into-place half of {#atomically_replace}, without the destination lock: writes `body` to a
|
|
507
|
+
# sibling temp file, fsyncs it, and renames it over `path`. POSIX makes the rename atomic within one
|
|
508
|
+
# filesystem, so an unlocked concurrent reader sees either the previous contents in full or the new
|
|
509
|
+
# ones in full — never a truncated or half-written file.
|
|
510
|
+
#
|
|
511
|
+
# {#repair_writable_marker!} uses this half alone. It needs the atomicity (its reader is another
|
|
512
|
+
# process's constructor, which does not lock) but not the ordering a lock would buy: every writer of
|
|
513
|
+
# `schema_version.txt` writes the same value, so there is no last-writer-wins question to settle.
|
|
514
|
+
def publish_by_rename(path, body)
|
|
515
|
+
tmp = "#{path}.tmp.#{Process.pid}.#{SecureRandom.hex(4)}"
|
|
516
|
+
begin
|
|
517
|
+
File.open(tmp, "wb") do |f|
|
|
518
|
+
f.write(body)
|
|
519
|
+
f.fsync
|
|
520
|
+
end
|
|
521
|
+
File.rename(tmp, path)
|
|
522
|
+
ensure
|
|
523
|
+
# A failed write/rename must not leak its temp file — rely on the 1-hour `cleanup_stale_temp_files`
|
|
524
|
+
# sweep only as a backstop for crashes that skip this ensure entirely.
|
|
525
|
+
unlink_entry(tmp) if File.exist?(tmp)
|
|
526
|
+
end
|
|
527
|
+
end
|
|
528
|
+
|
|
498
529
|
# Checks (and, for a writable store, repairs) the `schema_version.txt` marker, and reports whether the
|
|
499
530
|
# disk tier is usable for the rest of this Store's lifetime. The result is memoized in `@disk_available`
|
|
500
531
|
# — one check per Store is enough; a benign double-check under a thread race would just repeat
|
|
@@ -508,8 +539,6 @@ module Rigor
|
|
|
508
539
|
# (permission, disk full, deleted root) also reports unavailable, degrading this Store instance to
|
|
509
540
|
# in-memory-memo-only for the rest of its lifetime — the producer block still runs, its result still
|
|
510
541
|
# lands in `@memo`, and disk reads/writes are simply skipped.
|
|
511
|
-
#
|
|
512
|
-
# @return [Boolean]
|
|
513
542
|
def ensure_schema_version!
|
|
514
543
|
return @disk_available unless @disk_available.nil?
|
|
515
544
|
|
|
@@ -525,25 +554,48 @@ module Rigor
|
|
|
525
554
|
false
|
|
526
555
|
end
|
|
527
556
|
|
|
557
|
+
# The marker is PUBLISHED BY RENAME rather than written in place. Its reader is another process's
|
|
558
|
+
# constructor, which takes no lock, so an `O_TRUNC`-then-write would expose a window in which the file
|
|
559
|
+
# exists and is empty; a reader landing there concludes the marker disagrees and clears the whole cache
|
|
560
|
+
# root — under, among others, a sibling that is between its own `File.file?` and `File.binread`, which
|
|
561
|
+
# is the `Errno::ENOENT` of issue #807. Two `rigor check` processes over one fresh `.rigor/cache` are
|
|
562
|
+
# enough to hit it.
|
|
563
|
+
#
|
|
564
|
+
# An EMPTY marker is treated as an ABSENT one — rewritten, never cleared on. Emptiness carries no
|
|
565
|
+
# information: it is what a torn write by an older Rigor looks like, what a crash between the create
|
|
566
|
+
# and the write leaves, and what this method's own clear-then-rewrite passes through. Answering an
|
|
567
|
+
# ambiguous signal by destroying a cache root is the wrong trade, and declining grants the entries
|
|
568
|
+
# below no trust they did not already have — a marker that is simply MISSING over existing entries is
|
|
569
|
+
# already kept and served (see the spec's writable-store rules), and `clear_cache_root!` removes the
|
|
570
|
+
# marker before rewriting it, so it produces that state itself.
|
|
528
571
|
def repair_writable_marker!
|
|
529
572
|
FileUtils.mkdir_p(@root)
|
|
530
573
|
marker = File.join(@root, "schema_version.txt")
|
|
531
574
|
current = self.class.schema_marker_value
|
|
532
575
|
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
return true if on_disk == current
|
|
576
|
+
on_disk = read_marker(marker)
|
|
577
|
+
return true if on_disk == current
|
|
536
578
|
|
|
537
|
-
|
|
538
|
-
end
|
|
579
|
+
clear_cache_root! unless on_disk.nil? || on_disk.empty?
|
|
539
580
|
|
|
540
581
|
FileUtils.mkdir_p(@root)
|
|
541
|
-
|
|
582
|
+
publish_by_rename(marker, "#{current}\n")
|
|
583
|
+
fsync_directory(@root)
|
|
542
584
|
true
|
|
543
585
|
rescue StandardError
|
|
544
586
|
false
|
|
545
587
|
end
|
|
546
588
|
|
|
589
|
+
# @return the marker's content, or nil when there is none to read — including the case of a concurrent
|
|
590
|
+
# {#clear_cache_root!} removing it between the check and the open.
|
|
591
|
+
def read_marker(path)
|
|
592
|
+
return nil unless File.file?(path)
|
|
593
|
+
|
|
594
|
+
File.read(path).strip
|
|
595
|
+
rescue Errno::ENOENT
|
|
596
|
+
nil
|
|
597
|
+
end
|
|
598
|
+
|
|
547
599
|
def clear_cache_root!
|
|
548
600
|
Dir.children(@root).each do |entry|
|
|
549
601
|
FileUtils.rm_rf(File.join(@root, entry))
|
|
@@ -15,7 +15,7 @@ require_relative "../inference/scope_indexer"
|
|
|
15
15
|
require_relative "../inference/statement_evaluator"
|
|
16
16
|
require_relative "prism_colorizer"
|
|
17
17
|
require_relative "command"
|
|
18
|
-
require_relative "
|
|
18
|
+
require_relative "../project_environment"
|
|
19
19
|
|
|
20
20
|
module Rigor
|
|
21
21
|
class CLI
|
|
@@ -43,7 +43,7 @@ module Rigor
|
|
|
43
43
|
# paging stays off because the CLI may itself sit in a pipeline.
|
|
44
44
|
BAT_ARGS = %w[--language=ruby --style=plain --paging=never --color=always].freeze
|
|
45
45
|
|
|
46
|
-
# @return
|
|
46
|
+
# @return CLI exit status.
|
|
47
47
|
def run
|
|
48
48
|
options = parse_options
|
|
49
49
|
file = @argv.shift
|
|
@@ -132,10 +132,10 @@ module Rigor
|
|
|
132
132
|
|
|
133
133
|
# The plugin-aware environment for the annotated file, so a `#=> <type>` matches what `rigor check`
|
|
134
134
|
# infers on that line — including types synthesized from the file's own inline RBS annotations (the file
|
|
135
|
-
# is threaded as the synthesizer's `source_files:`; see {
|
|
135
|
+
# is threaded as the synthesizer's `source_files:`; see {ProjectEnvironment}).
|
|
136
136
|
def base_scope(configuration, file)
|
|
137
137
|
Scope.empty(
|
|
138
|
-
environment:
|
|
138
|
+
environment: ProjectEnvironment.build(configuration: configuration, source_files: [file])
|
|
139
139
|
)
|
|
140
140
|
end
|
|
141
141
|
|
|
@@ -220,8 +220,7 @@ module Rigor
|
|
|
220
220
|
@scope_index = scope_index
|
|
221
221
|
end
|
|
222
222
|
|
|
223
|
-
# @
|
|
224
|
-
# @return [Hash{Integer => Rigor::Type}] 1-indexed line => type.
|
|
223
|
+
# @return 1-indexed line => type.
|
|
225
224
|
def collect(program)
|
|
226
225
|
by_line = {}
|
|
227
226
|
each_statement(program) do |statement|
|
|
@@ -32,14 +32,21 @@ module Rigor
|
|
|
32
32
|
# The class-length budget is relaxed (as on `Rigor::CLI` itself) because `check` aggregates several independent
|
|
33
33
|
# concerns that are clearer read together than split across micro-classes.
|
|
34
34
|
class CheckCommand < Command # rubocop:disable Metrics/ClassLength
|
|
35
|
-
#
|
|
35
|
+
# Issue #812 — `--fail-on=SEVERITY` raises the exit-status bar above the default (`:error`-only).
|
|
36
|
+
# Orders the three severities a diagnostic can carry once it survives {SeverityStamp} (`:off` rows are
|
|
37
|
+
# dropped upstream and never reach a `Result`, so they are not a member here).
|
|
38
|
+
FAIL_ON_RANK = { info: 0, warning: 1, error: 2 }.freeze
|
|
39
|
+
|
|
40
|
+
# @return CLI exit status.
|
|
36
41
|
#
|
|
37
42
|
# Deferred YJIT enablement (Runtime::Jit) is armed by `CLI#dispatch` for every command, this
|
|
38
43
|
# one included, before any analysis work runs.
|
|
39
|
-
def run # rubocop:disable Metrics/AbcSize
|
|
44
|
+
def run # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
|
|
40
45
|
# ADR-87 WD4 — parse options + resolve config WITHOUT the inference engine, so the run-cache hit probe
|
|
41
46
|
# can run first. The heavy engine (`load_check_dependencies`) loads only on a miss / non-cacheable run.
|
|
42
47
|
options = parse_check_options
|
|
48
|
+
return CLI::EXIT_USAGE unless valid_fail_on_option?(options)
|
|
49
|
+
|
|
43
50
|
buffer = Options.resolve_buffer_binding(options, err: @err)
|
|
44
51
|
return CLI::EXIT_USAGE if buffer == :usage_error
|
|
45
52
|
|
|
@@ -68,7 +75,8 @@ module Rigor
|
|
|
68
75
|
result = apply_baseline_filter(raw_result, configuration, options)
|
|
69
76
|
|
|
70
77
|
coverage = compute_coverage(runner, configuration, options)
|
|
71
|
-
write_result(result, options.fetch(:format), coverage: coverage, config_warnings: config_warnings
|
|
78
|
+
write_result(result, options.fetch(:format), coverage: coverage, config_warnings: config_warnings,
|
|
79
|
+
fail_on: options.fetch(:fail_on))
|
|
72
80
|
emit_ci_detected_output(result, options)
|
|
73
81
|
write_run_stats(result.stats) if result.stats
|
|
74
82
|
write_trace_appendices
|
|
@@ -77,11 +85,34 @@ module Rigor
|
|
|
77
85
|
|
|
78
86
|
exit_code = result.success? ? 0 : 1
|
|
79
87
|
exit_code = 1 if baseline_strict_violation?(raw_result.diagnostics, configuration, options)
|
|
88
|
+
exit_code = 1 if fail_on_violation?(result.diagnostics, options)
|
|
80
89
|
exit_code
|
|
81
90
|
end
|
|
82
91
|
|
|
83
92
|
private
|
|
84
93
|
|
|
94
|
+
# True when `diagnostics` (already baseline-filtered and severity-resolved — the same list
|
|
95
|
+
# `Result#success?` reads) carries a row at or above the `--fail-on` threshold. With the default
|
|
96
|
+
# `:error` threshold this is exactly `!diagnostics.any?(&:error?)`, i.e. a no-op over the existing exit
|
|
97
|
+
# code — `--fail-on` only ever makes a run *stricter*, never masks an `:error` the old exit code caught.
|
|
98
|
+
def fail_on_violation?(diagnostics, options)
|
|
99
|
+
threshold = FAIL_ON_RANK.fetch(options.fetch(:fail_on))
|
|
100
|
+
diagnostics.any? { |diagnostic| FAIL_ON_RANK.fetch(diagnostic.severity, FAIL_ON_RANK[:error]) >= threshold }
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# True when `options[:fail_on]` (an arbitrary user-typed Symbol at this point — parsing only lower-cases
|
|
104
|
+
# and interns it, see `parse_check_options`) is one of {FAIL_ON_RANK}'s keys; writes a usage note to
|
|
105
|
+
# `@err` and returns false otherwise. Validated as a plain post-parse check rather than raised from
|
|
106
|
+
# inside the `opts.on` block: OptionParser rescues a `ParseError` raised from a switch's own block and
|
|
107
|
+
# rebuilds it as a generic `"invalid argument: --fail-on=<value>"`, discarding any custom message — this
|
|
108
|
+
# way the usage note actually names the accepted values.
|
|
109
|
+
def valid_fail_on_option?(options)
|
|
110
|
+
return true if FAIL_ON_RANK.key?(options.fetch(:fail_on))
|
|
111
|
+
|
|
112
|
+
@err.puts("rigor: invalid --fail-on value: #{options.fetch(:fail_on)} (expected error, warning, or info)")
|
|
113
|
+
false
|
|
114
|
+
end
|
|
115
|
+
|
|
85
116
|
# ADR-87 WD4 — attempt the boot-slimming run-cache hit. Returns the cached {Analysis::Result} (severity
|
|
86
117
|
# profile applied, no stats — matching a cache-served `Runner#run`) on a hit, or nil to fall through to
|
|
87
118
|
# the full engine path. Only an ordinary sequential check whose result IS cache-served the same way is
|
|
@@ -129,11 +160,13 @@ module Rigor
|
|
|
129
160
|
# gate reads the same diagnostics the full path would.
|
|
130
161
|
def finalize_cache_hit(raw_result, configuration, options, config_warnings)
|
|
131
162
|
result = apply_baseline_filter(raw_result, configuration, options)
|
|
132
|
-
write_result(result, options.fetch(:format), config_warnings: config_warnings
|
|
163
|
+
write_result(result, options.fetch(:format), config_warnings: config_warnings,
|
|
164
|
+
fail_on: options.fetch(:fail_on))
|
|
133
165
|
emit_ci_detected_output(result, options)
|
|
134
166
|
|
|
135
167
|
exit_code = result.success? ? 0 : 1
|
|
136
168
|
exit_code = 1 if baseline_strict_violation?(raw_result.diagnostics, configuration, options)
|
|
169
|
+
exit_code = 1 if fail_on_violation?(result.diagnostics, options)
|
|
137
170
|
exit_code
|
|
138
171
|
end
|
|
139
172
|
|
|
@@ -233,8 +266,8 @@ module Rigor
|
|
|
233
266
|
|
|
234
267
|
result = apply_baseline_filter(Analysis::Result.new(diagnostics: diagnostics, stats: nil), configuration,
|
|
235
268
|
options)
|
|
236
|
-
write_result(result, options.fetch(:format))
|
|
237
|
-
result.success? ?
|
|
269
|
+
write_result(result, options.fetch(:format), fail_on: options.fetch(:fail_on))
|
|
270
|
+
!result.success? || fail_on_violation?(result.diagnostics, options) ? 1 : 0
|
|
238
271
|
end
|
|
239
272
|
|
|
240
273
|
# Editor mode option B (#146) — `--incremental` plus an editor buffer. The whole project is in scope with
|
|
@@ -260,8 +293,8 @@ module Rigor
|
|
|
260
293
|
filtered = apply_baseline_filter(
|
|
261
294
|
Analysis::Result.new(diagnostics: result.diagnostics, stats: nil), configuration, options
|
|
262
295
|
)
|
|
263
|
-
write_result(filtered, options.fetch(:format))
|
|
264
|
-
filtered.success? ?
|
|
296
|
+
write_result(filtered, options.fetch(:format), fail_on: options.fetch(:fail_on))
|
|
297
|
+
!filtered.success? || fail_on_violation?(filtered.diagnostics, options) ? 1 : 0
|
|
265
298
|
end
|
|
266
299
|
|
|
267
300
|
# ADR-88 WD1 — a one-line stderr note when the plugin fact surface (an ADR-9 fact, an ADR-60 producer
|
|
@@ -439,6 +472,10 @@ module Rigor
|
|
|
439
472
|
baseline: :unset,
|
|
440
473
|
# ADR-22 slice 5 — `--baseline-strict` CI gate: fail the run on any baseline drift, in either direction.
|
|
441
474
|
baseline_strict: false,
|
|
475
|
+
# Issue #812 — `--fail-on=SEVERITY` raises the exit-status bar: the run exits non-zero when a
|
|
476
|
+
# diagnostic AT OR ABOVE this severity survives baseline filtering and severity resolution, not only
|
|
477
|
+
# on `:error` (today's default and behaviour). See `FAIL_ON_RANK`.
|
|
478
|
+
fail_on: :error,
|
|
442
479
|
# ADR-32 WD10 carry-over — `--treat-all-as-inline-rbs` forces the `rigor-rbs-inline` plugin into the loaded
|
|
443
480
|
# plugin set with `require_magic_comment: false` so a single ad-hoc `rigor check` invocation treats every
|
|
444
481
|
# analysed file as inline-RBS without the user editing `.rigor.yml`. Intended for single-file / ad-hoc CI use;
|
|
@@ -508,6 +545,12 @@ module Rigor
|
|
|
508
545
|
"fail the run on any baseline drift (CI gate)") do
|
|
509
546
|
options[:baseline_strict] = true
|
|
510
547
|
end
|
|
548
|
+
opts.on("--fail-on=SEVERITY",
|
|
549
|
+
"exit non-zero on a diagnostic at or above SEVERITY: error (default), warning, or info") do |value|
|
|
550
|
+
# Validated post-parse (`valid_fail_on_option?`, called from `run`) rather than here — see its
|
|
551
|
+
# comment for why an unrecognised value is not raised from inside this block.
|
|
552
|
+
options[:fail_on] = value.to_s.downcase.to_sym
|
|
553
|
+
end
|
|
511
554
|
opts.on("--treat-all-as-inline-rbs",
|
|
512
555
|
"force-load rigor-rbs-inline with require_magic_comment: false") do
|
|
513
556
|
options[:treat_all_as_inline_rbs] = true
|
|
@@ -661,7 +704,6 @@ module Rigor
|
|
|
661
704
|
@err.puts(" body evals: #{counts[bt::MEMO_BODY_EVALS]}")
|
|
662
705
|
@err.puts(" non-stored: on-stack #{counts[bt::MEMO_REFUSE_ON_STACK]} " \
|
|
663
706
|
"unroll-in-flight #{counts[bt::MEMO_REFUSE_UNROLL]} " \
|
|
664
|
-
"consult-tainted #{counts[bt::MEMO_REFUSE_CONSULT_TAINTED]} " \
|
|
665
707
|
"transient-tainted #{counts[bt::MEMO_REFUSE_TRANSIENT]}")
|
|
666
708
|
write_memo_signature_table
|
|
667
709
|
end
|
|
@@ -841,10 +883,15 @@ module Rigor
|
|
|
841
883
|
format == "text" ? @out : @err
|
|
842
884
|
end
|
|
843
885
|
|
|
844
|
-
def write_result(result, format, coverage: nil, config_warnings: [])
|
|
886
|
+
def write_result(result, format, coverage: nil, config_warnings: [], fail_on: :error)
|
|
845
887
|
case format
|
|
846
888
|
when "json"
|
|
847
889
|
payload = enrich_json(result.to_h)
|
|
890
|
+
# Issue #812 — the exit code can be stricter than `payload["success"]` (which stays the `:error`-only
|
|
891
|
+
# reading `Result#success?` always gave, per the other consumers already reading it); a JSON
|
|
892
|
+
# consumer that wants to know WHY a run exited non-zero reads this alongside it rather than
|
|
893
|
+
# re-deriving `--fail-on`'s value from the invocation.
|
|
894
|
+
payload["fail_on"] = fail_on.to_s
|
|
848
895
|
payload["coverage"] = coverage_payload(coverage) if coverage
|
|
849
896
|
payload["config_warnings"] = config_warnings.map(&:to_h) unless config_warnings.empty?
|
|
850
897
|
@out.puts(JSON.pretty_generate(payload))
|
|
@@ -22,7 +22,7 @@ module Rigor
|
|
|
22
22
|
# The outcome of {.attempt} — the best-effort variant. Exactly one of the two is non-nil, so a caller can never
|
|
23
23
|
# read "the check could not run" as "the check ran clean": `result` is nil precisely when `error` is set.
|
|
24
24
|
Outcome = Data.define(:result, :error) do
|
|
25
|
-
# @return
|
|
25
|
+
# @return true when an analysis actually completed.
|
|
26
26
|
def ran?
|
|
27
27
|
!result.nil?
|
|
28
28
|
end
|
|
@@ -42,12 +42,9 @@ module Rigor
|
|
|
42
42
|
|
|
43
43
|
# Runs the analysis the way `rigor check` does and returns the runner + its raw result.
|
|
44
44
|
#
|
|
45
|
-
# @param
|
|
46
|
-
# @param
|
|
47
|
-
# @param
|
|
48
|
-
# @param buffer [Rigor::Analysis::BufferBinding, nil]
|
|
49
|
-
# @param cache_root [String, nil] nil falls back to the configuration's cache path.
|
|
50
|
-
# @return [Invocation]
|
|
45
|
+
# @param options — at least `:no_cache`, `:explain`, `:stats`, `:workers` (see {CheckRunnerFactory.build}).
|
|
46
|
+
# @param paths — analysed paths; nil falls back to the configuration's `paths:`.
|
|
47
|
+
# @param cache_root — nil falls back to the configuration's cache path.
|
|
51
48
|
def run(configuration:, options:, paths: nil, buffer: nil, cache_root: nil)
|
|
52
49
|
require_relative "check_runner_factory"
|
|
53
50
|
runner = CheckRunnerFactory.build(
|
|
@@ -68,10 +65,8 @@ module Rigor
|
|
|
68
65
|
# routing hint is outside the false-positive envelope, but a crash in it is not — `describe` must stay a command
|
|
69
66
|
# an agent can run freely.
|
|
70
67
|
#
|
|
71
|
-
# @param config_path
|
|
72
|
-
# @param options
|
|
73
|
-
# @param paths [Array<String>, nil]
|
|
74
|
-
# @return [Outcome]
|
|
68
|
+
# @param config_path — path to the config file; nil uses {Configuration.discover}.
|
|
69
|
+
# @param options — runner options (defaults to {DEEP_OPTIONS}).
|
|
75
70
|
def attempt(config_path: nil, options: DEEP_OPTIONS, paths: nil)
|
|
76
71
|
require_relative "../configuration"
|
|
77
72
|
configuration = Configuration.load(config_path)
|
|
@@ -14,12 +14,8 @@ module Rigor
|
|
|
14
14
|
|
|
15
15
|
# Builds a runner matching the configuration/plugin/cache resolution that `rigor check` itself uses.
|
|
16
16
|
#
|
|
17
|
-
# @param
|
|
18
|
-
# @param options [Hash] parsed CLI options (must contain at least
|
|
17
|
+
# @param options — parsed CLI options (must contain at least
|
|
19
18
|
# `:no_cache`, `:explain`, `:stats`, `:workers`)
|
|
20
|
-
# @param buffer [Rigor::Analysis::BufferBinding, nil]
|
|
21
|
-
# @param cache_root [String]
|
|
22
|
-
# @return [Rigor::Analysis::Runner]
|
|
23
19
|
def build(configuration:, options:, buffer:, cache_root:)
|
|
24
20
|
cache_store = if options.fetch(:no_cache)
|
|
25
21
|
nil
|
|
@@ -53,7 +53,7 @@ module Rigor
|
|
|
53
53
|
# `--test-command`.
|
|
54
54
|
DEFAULT_TEST_COMMAND = %w[bundle exec rake].freeze
|
|
55
55
|
|
|
56
|
-
# @return
|
|
56
|
+
# @return CLI exit status.
|
|
57
57
|
#
|
|
58
58
|
# Deferred YJIT enablement (Runtime::Jit) is armed by `CLI#dispatch` for every command, this
|
|
59
59
|
# one included: a scan long enough to amortize JIT compile cost enables mid-flight; a short one
|
|
@@ -48,8 +48,6 @@ module Rigor
|
|
|
48
48
|
# Both measurement-integrity warnings, and the escalation rule between them, live in
|
|
49
49
|
# {MeasurementIntegrityWarning}. Only the floor stays here: it is this command's policy, and
|
|
50
50
|
# `coverage_command_spec` reads it off this module.
|
|
51
|
-
#
|
|
52
|
-
# @param report [MutationProtectionReport, FusedProtectionReport]
|
|
53
51
|
def warn_harness_errors(report)
|
|
54
52
|
MeasurementIntegrityWarning.emit(report, err: @err, floor: HARNESS_ERROR_WARN_FLOOR)
|
|
55
53
|
end
|
|
@@ -113,10 +111,21 @@ module Rigor
|
|
|
113
111
|
cache_store: context.cache_store, workers: workers
|
|
114
112
|
),
|
|
115
113
|
seed_bundles: Protection::DiscoverySeed.bundles(paths: paths),
|
|
116
|
-
discovery_seed: seed
|
|
114
|
+
discovery_seed: seed,
|
|
115
|
+
rebuild_session: -> { rebuilt_session(context) }
|
|
117
116
|
)
|
|
118
117
|
end
|
|
119
118
|
|
|
119
|
+
# Issue #790 — how the oracle replaces an Environment a mutant degraded. {LanguageServer::ProjectContext}
|
|
120
|
+
# already has the operation: `invalidate!` drops the cached environment AND the project scan together,
|
|
121
|
+
# which is the pair the oracle measures over, and the next reader rebuilds both. Under
|
|
122
|
+
# {MutationForkScan} this runs in a forked child against its copy-on-write context, so one file's
|
|
123
|
+
# defect never costs the parent or its siblings a rebuild.
|
|
124
|
+
def rebuilt_session(context)
|
|
125
|
+
context.invalidate!
|
|
126
|
+
[context.environment, context.project_scan]
|
|
127
|
+
end
|
|
128
|
+
|
|
120
129
|
# The scope {Protection::Mutator} judges a mutation site's receiver against, derived from the same seed
|
|
121
130
|
# the oracle gets. nil (the gate off, or an empty seed) keeps the bare `Scope.empty` per file.
|
|
122
131
|
def mutation_base_scope(seed, environment)
|
|
@@ -21,9 +21,7 @@ module Rigor
|
|
|
21
21
|
module CoverageScan
|
|
22
22
|
module_function
|
|
23
23
|
|
|
24
|
-
# @param files
|
|
25
|
-
# @param configuration [Rigor::Configuration]
|
|
26
|
-
# @return [Rigor::CLI::CoverageReport]
|
|
24
|
+
# @param files — explicit `.rb` file paths to scan.
|
|
27
25
|
#
|
|
28
26
|
# Issue #513 — the environment is PLUGIN-AWARE (`LanguageServer::ProjectContext`, the same builder
|
|
29
27
|
# `--protection` uses), not the bare RBS environment this path carried until 2026-09-01. Without the
|
|
@@ -57,8 +55,6 @@ module Rigor
|
|
|
57
55
|
# the precision lens must mirror the walk it describes (`check` leaves the table empty unless
|
|
58
56
|
# `parameter_inference:` is on, ADR-67 WD6a), while `--protection` seeds it unconditionally by ADR-67's own
|
|
59
57
|
# wiring — it only ever reclassifies sites it already counted.
|
|
60
|
-
#
|
|
61
|
-
# @return [Rigor::Scope]
|
|
62
58
|
def discovery_seeded_scope(files:, configuration:, environment:, parameter_inference:, workers: nil)
|
|
63
59
|
base = Scope.empty(environment: environment)
|
|
64
60
|
seed = Protection::DiscoverySeed.discovery_tables(files).dup
|
data/lib/rigor/cli/doc_links.rb
CHANGED
|
@@ -36,9 +36,9 @@ module Rigor
|
|
|
36
36
|
|
|
37
37
|
module_function
|
|
38
38
|
|
|
39
|
-
# @param body
|
|
40
|
-
# @param from
|
|
41
|
-
# @return
|
|
39
|
+
# @param body — the page as it is stored.
|
|
40
|
+
# @param from — its absolute path, so relative targets resolve.
|
|
41
|
+
# @return the page with every relative link turned into a `rigor docs` key.
|
|
42
42
|
def rewrite(body, from:)
|
|
43
43
|
dir = File.dirname(from)
|
|
44
44
|
body.gsub(MARKDOWN_LINK) do
|
|
@@ -52,7 +52,7 @@ module Rigor
|
|
|
52
52
|
HANDBOOK_ROOT = File.join(DOCS_ROOT, "handbook")
|
|
53
53
|
LLMS_INDEX = File.join(DOCS_ROOT, "llms.txt")
|
|
54
54
|
|
|
55
|
-
# @return
|
|
55
|
+
# @return CLI exit status.
|
|
56
56
|
def run
|
|
57
57
|
case @argv.first
|
|
58
58
|
when nil
|
|
@@ -136,7 +136,7 @@ module Rigor
|
|
|
136
136
|
# Resolve a query to a single doc. Exact relative-path and prefixed-basename aliases are unique; a short
|
|
137
137
|
# (prefix-stripped) name is accepted only when one category owns it.
|
|
138
138
|
#
|
|
139
|
-
# @return
|
|
139
|
+
# @return the doc entry, or an error exit status
|
|
140
140
|
# after the error has been written to `@err`.
|
|
141
141
|
def resolve_doc(query)
|
|
142
142
|
# A key printed by a rendered page may carry the section it pointed at; resolve the page and say
|