rigortype 0.4.6 → 0.4.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/data/gem_overlay/activesupport/core_ext.rbs +16 -13
- data/data/vendored_gem_sigs/bundler/bundler.rbs +1 -1
- data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +4 -3
- data/docs/handbook/01-getting-started.md +5 -2
- data/docs/handbook/11-sig-gen.md +50 -3
- data/docs/handbook/appendix-phpstan.md +4 -4
- data/docs/manual/02-cli-reference.md +152 -26
- data/docs/manual/03-configuration.md +36 -3
- data/docs/manual/06-baseline.md +6 -4
- data/docs/manual/09-editor-integration.md +13 -2
- data/docs/manual/10-mcp-server.md +38 -2
- data/docs/manual/12-caching.md +4 -3
- data/docs/manual/plugins/rigor-actionpack.md +19 -11
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +20 -1
- data/lib/rigor/analysis/baseline.rb +24 -27
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +5 -6
- data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +4 -4
- data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +85 -13
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +3 -2
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +0 -1
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -8
- data/lib/rigor/analysis/check_rules/published_constant_guard.rb +1 -1
- data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +3 -3
- data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
- data/lib/rigor/analysis/check_rules/rule_ids.rb +1 -1
- data/lib/rigor/analysis/check_rules/rule_walk.rb +9 -7
- data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +3 -4
- data/lib/rigor/analysis/check_rules/special_global_setters.rb +3 -3
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +1 -1
- data/lib/rigor/analysis/check_rules.rb +124 -44
- data/lib/rigor/analysis/crash_signature.rb +2 -2
- data/lib/rigor/analysis/dependency_recorder.rb +4 -4
- data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +14 -3
- data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +1 -1
- data/lib/rigor/analysis/dependency_source_inference/index.rb +6 -8
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
- data/lib/rigor/analysis/edit_replay.rb +93 -6
- data/lib/rigor/analysis/fact_store.rb +3 -3
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +2 -2
- data/lib/rigor/analysis/incremental_run_slot.rb +2 -2
- data/lib/rigor/analysis/incremental_session.rb +27 -16
- data/lib/rigor/analysis/rule_catalog.rb +2 -2
- data/lib/rigor/analysis/run_stats.rb +2 -2
- data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +2 -2
- data/lib/rigor/analysis/runner/declaration_position.rb +1 -1
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +193 -93
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +1 -1
- data/lib/rigor/analysis/runner/pool_coordinator.rb +147 -323
- data/lib/rigor/analysis/runner/project_pre_passes.rb +3 -3
- data/lib/rigor/analysis/runner/run_level_facts.rb +304 -0
- data/lib/rigor/analysis/runner/run_snapshots.rb +44 -54
- data/lib/rigor/analysis/runner/worker_payload.rb +36 -0
- data/lib/rigor/analysis/runner.rb +77 -71
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +1 -1
- data/lib/rigor/analysis/template_unit_positions.rb +2 -3
- data/lib/rigor/analysis/template_units.rb +9 -1
- data/lib/rigor/analysis/worker_session.rb +24 -31
- data/lib/rigor/bleeding_edge.rb +3 -6
- data/lib/rigor/builtins/imported_refinements.rb +6 -8
- data/lib/rigor/builtins/predefined_constant_refinements.rb +1 -1
- data/lib/rigor/builtins/regex_refinement.rb +4 -6
- data/lib/rigor/cache/descriptor.rb +6 -5
- data/lib/rigor/cache/incremental_snapshot.rb +31 -12
- data/lib/rigor/cache/rbs_cache_producer.rb +2 -2
- data/lib/rigor/cache/rbs_class_ancestor_table.rb +1 -1
- data/lib/rigor/cache/rbs_class_type_param_names.rb +2 -2
- data/lib/rigor/cache/rbs_constant_table.rb +1 -1
- data/lib/rigor/cache/rbs_descriptor.rb +54 -3
- data/lib/rigor/cache/rbs_environment.rb +4 -1
- data/lib/rigor/cache/rbs_environment_marshal_patch.rb +2 -2
- data/lib/rigor/cache/rbs_known_class_names.rb +1 -1
- data/lib/rigor/cache/store.rb +11 -10
- data/lib/rigor/ci_detector.rb +1 -1
- data/lib/rigor/cli/annotate_command.rb +24 -19
- data/lib/rigor/cli/baseline_command.rb +145 -55
- data/lib/rigor/cli/catalogue.rb +256 -0
- data/lib/rigor/cli/check_command.rb +108 -102
- data/lib/rigor/cli/check_runner_factory.rb +2 -2
- data/lib/rigor/cli/command.rb +15 -1
- data/lib/rigor/cli/coverage_command.rb +21 -8
- data/lib/rigor/cli/coverage_mutation.rb +5 -7
- data/lib/rigor/cli/coverage_renderer.rb +2 -1
- data/lib/rigor/cli/coverage_scan.rb +3 -5
- data/lib/rigor/cli/diff_command.rb +25 -20
- data/lib/rigor/cli/docs_command.rb +1 -2
- data/lib/rigor/cli/doctor_command.rb +12 -12
- data/lib/rigor/cli/effects_command.rb +50 -40
- data/lib/rigor/cli/effects_diff_renderer.rb +18 -17
- data/lib/rigor/cli/effects_explain_renderer.rb +13 -12
- data/lib/rigor/cli/effects_renderer.rb +2 -1
- data/lib/rigor/cli/effects_snapshot_command.rb +18 -17
- data/lib/rigor/cli/explain_command.rb +18 -8
- data/lib/rigor/cli/fused_protection_renderer.rb +2 -1
- data/lib/rigor/cli/json_document.rb +41 -0
- data/lib/rigor/cli/lens_command.rb +12 -8
- data/lib/rigor/cli/lsp_command.rb +250 -23
- data/lib/rigor/cli/mcp_command.rb +12 -7
- data/lib/rigor/cli/mutation_fork_scan.rb +1 -1
- data/lib/rigor/cli/mutation_protection_renderer.rb +3 -2
- data/lib/rigor/cli/options.rb +40 -1
- data/lib/rigor/cli/plugin_command.rb +93 -46
- data/lib/rigor/cli/plugins_command.rb +17 -18
- data/lib/rigor/cli/plugins_renderer.rb +10 -6
- data/lib/rigor/cli/protection_fork_scan.rb +2 -2
- data/lib/rigor/cli/protection_renderer.rb +2 -1
- data/lib/rigor/cli/show_bleedingedge_command.rb +21 -15
- data/lib/rigor/cli/sig_gen_command.rb +84 -27
- data/lib/rigor/cli/skill_command.rb +32 -3
- data/lib/rigor/cli/skill_deep_probe.rb +1 -1
- data/lib/rigor/cli/skill_describe.rb +1 -1
- data/lib/rigor/cli/trace_command.rb +14 -9
- data/lib/rigor/cli/triage_command.rb +10 -11
- data/lib/rigor/cli/triage_renderer.rb +2 -1
- data/lib/rigor/cli/type_of_command.rb +16 -11
- data/lib/rigor/cli/type_of_project_scope.rb +1 -4
- data/lib/rigor/cli/type_of_renderer.rb +4 -1
- data/lib/rigor/cli/type_scan_command.rb +14 -9
- data/lib/rigor/cli/type_scan_renderer.rb +2 -1
- data/lib/rigor/cli/unused_command.rb +27 -17
- data/lib/rigor/cli/upgrade_command.rb +5 -0
- data/lib/rigor/cli.rb +209 -54
- data/lib/rigor/config_audit.rb +1 -1
- data/lib/rigor/configuration/severity_profile.rb +5 -5
- data/lib/rigor/configuration.rb +72 -30
- data/lib/rigor/effects/definition_context.rb +0 -2
- data/lib/rigor/effects/envelope.rb +1 -1
- data/lib/rigor/effects/framework_units.rb +1 -1
- data/lib/rigor/effects/mutation_classifier.rb +4 -7
- data/lib/rigor/effects/propagator.rb +1 -1
- data/lib/rigor/effects/registry.rb +2 -2
- data/lib/rigor/effects/snapshot.rb +1 -1
- data/lib/rigor/effects/taint_cause.rb +0 -2
- data/lib/rigor/effects/unit_scan.rb +9 -13
- data/lib/rigor/environment/bundle_sig_discovery.rb +5 -9
- data/lib/rigor/environment/class_registry.rb +3 -3
- data/lib/rigor/environment/core_root.rb +77 -0
- data/lib/rigor/environment/default_libraries.rb +1 -1
- data/lib/rigor/environment/deferred_signature_plan.rb +283 -0
- data/lib/rigor/environment/gated_signature_guard.rb +1 -1
- data/lib/rigor/environment/gated_signature_plan.rb +5 -2
- data/lib/rigor/environment/lockfile_resolver.rb +28 -12
- data/lib/rigor/environment/member_consistency/comparator.rb +18 -1
- data/lib/rigor/environment/member_consistency.rb +56 -7
- data/lib/rigor/environment/missing_gem_constant_index.rb +3 -2
- data/lib/rigor/environment/project_path_gems.rb +124 -0
- data/lib/rigor/environment/rbs_hierarchy.rb +6 -15
- data/lib/rigor/environment/rbs_loader.rb +480 -67
- data/lib/rigor/environment/reflection.rb +7 -7
- data/lib/rigor/environment/required_features.rb +0 -3
- data/lib/rigor/environment/ruby_library_roots.rb +1 -1
- data/lib/rigor/environment/vendored_signature_plan.rb +153 -0
- data/lib/rigor/environment.rb +41 -10
- data/lib/rigor/flow_contribution/merger.rb +4 -4
- data/lib/rigor/inference/acceptance.rb +93 -32
- data/lib/rigor/inference/block_parameter_binder.rb +2 -3
- data/lib/rigor/inference/block_run_count.rb +178 -0
- data/lib/rigor/inference/budget_trace.rb +8 -2
- data/lib/rigor/inference/builtins/exception_catalog.rb +2 -2
- data/lib/rigor/inference/builtins/numeric_catalog.rb +2 -5
- data/lib/rigor/inference/builtins/random_catalog.rb +2 -2
- data/lib/rigor/inference/callee_mutation_chain.rb +88 -77
- data/lib/rigor/inference/callee_write_census.rb +815 -0
- data/lib/rigor/inference/construction_escape.rb +217 -0
- data/lib/rigor/inference/declaration_walk/traversal.rb +1 -1
- data/lib/rigor/inference/deep_erasure.rb +39 -7
- data/lib/rigor/inference/def_handle.rb +1 -1
- data/lib/rigor/inference/def_node_resolver.rb +1 -1
- data/lib/rigor/inference/def_return_typer.rb +46 -4
- data/lib/rigor/inference/definer_resolution.rb +4 -2
- data/lib/rigor/inference/definers_index.rb +106 -0
- data/lib/rigor/inference/direct_ivar_writes.rb +300 -0
- data/lib/rigor/inference/element_read_widening.rb +49 -3
- data/lib/rigor/inference/expression_typer.rb +116 -49
- data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
- data/lib/rigor/inference/global_write_census.rb +76 -2
- data/lib/rigor/inference/global_writer_reach.rb +260 -0
- data/lib/rigor/inference/hkt_body.rb +1 -2
- data/lib/rigor/inference/hkt_body_parser.rb +3 -5
- data/lib/rigor/inference/hkt_reducer.rb +2 -2
- data/lib/rigor/inference/hkt_registry.rb +3 -5
- data/lib/rigor/inference/indexed_narrowing.rb +4 -7
- data/lib/rigor/inference/intervening_ivar_reset.rb +26 -4
- data/lib/rigor/inference/ivar_writer_census.rb +51 -0
- data/lib/rigor/inference/key_presence_guard.rb +2 -1
- data/lib/rigor/inference/loop_value_record.rb +8 -4
- data/lib/rigor/inference/match_rebinding/frame.rb +46 -0
- data/lib/rigor/inference/match_rebinding/yielding_match.rb +45 -0
- data/lib/rigor/inference/match_rebinding.rb +11 -1
- data/lib/rigor/inference/memo_audit.rb +55 -0
- data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/argument_distribution.rb +40 -0
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +3 -3
- data/lib/rigor/inference/method_dispatcher/contested_overloads.rb +89 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/no_match_fallback.rb +54 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +39 -27
- data/lib/rigor/inference/method_dispatcher/random_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +203 -17
- data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/self_substitute.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +9 -7
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +83 -20
- data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +0 -1
- data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +50 -17
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -5
- data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +4 -6
- data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher.rb +64 -14
- data/lib/rigor/inference/module_function_state.rb +11 -1
- data/lib/rigor/inference/multi_target_binder.rb +1 -1
- data/lib/rigor/inference/mutation_rejoin.rb +240 -4
- data/lib/rigor/inference/mutation_widening.rb +157 -27
- data/lib/rigor/inference/narrowing.rb +47 -33
- data/lib/rigor/inference/optimistic_origin.rb +1 -1
- data/lib/rigor/inference/out_buffer_mutation.rb +72 -0
- data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
- data/lib/rigor/inference/pre_eval_constants.rb +1 -1
- data/lib/rigor/inference/project_method_ownership.rb +6 -3
- data/lib/rigor/inference/project_yielder.rb +11 -17
- data/lib/rigor/inference/receiver_alias.rb +20 -0
- data/lib/rigor/inference/refined_dispatch/constructed_class.rb +3 -1
- data/lib/rigor/inference/refined_dispatch/own_method.rb +2 -1
- data/lib/rigor/inference/remover_holder_scan.rb +233 -0
- data/lib/rigor/inference/remover_narrowing.rb +224 -0
- data/lib/rigor/inference/rewrite_mutation.rb +53 -2
- data/lib/rigor/inference/scope_indexer/certainty.rb +0 -1
- data/lib/rigor/inference/scope_indexer/constant_ivar_value.rb +93 -0
- data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +58 -15
- data/lib/rigor/inference/scope_indexer/derived_param_value.rb +112 -0
- data/lib/rigor/inference/scope_indexer/empty_seed.rb +99 -0
- data/lib/rigor/inference/scope_indexer/ivar_contributions.rb +135 -0
- data/lib/rigor/inference/scope_indexer.rb +137 -46
- data/lib/rigor/inference/self_call_ivar_writes.rb +84 -12
- data/lib/rigor/inference/set_mutation.rb +189 -0
- data/lib/rigor/inference/singleton_hook_decline.rb +1 -2
- data/lib/rigor/inference/statement_evaluator.rb +2328 -138
- data/lib/rigor/inference/struct_fold_safety.rb +4 -6
- data/lib/rigor/inference/synthetic_method_scanner.rb +5 -6
- data/lib/rigor/inference/toplevel_def_census.rb +59 -0
- data/lib/rigor/inference/unknown_store_widening.rb +103 -4
- data/lib/rigor/inference/unthreaded_rebinds.rb +2 -3
- data/lib/rigor/inference/void_tail_summary.rb +2 -1
- data/lib/rigor/language_server/buffer_table.rb +3 -3
- data/lib/rigor/language_server/completion_provider.rb +26 -20
- data/lib/rigor/language_server/config_warning_announcer.rb +28 -0
- data/lib/rigor/language_server/diagnostic_publisher.rb +8 -3
- data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
- data/lib/rigor/language_server/folding_range_provider.rb +1 -2
- data/lib/rigor/language_server/hover_provider.rb +1 -7
- data/lib/rigor/language_server/hover_renderer.rb +6 -8
- data/lib/rigor/language_server/project_context.rb +39 -1
- data/lib/rigor/language_server/publish_batcher.rb +3 -4
- data/lib/rigor/language_server/selection_range_provider.rb +2 -4
- data/lib/rigor/language_server/server.rb +7 -3
- data/lib/rigor/language_server/signature_help_provider.rb +4 -4
- data/lib/rigor/lens/plugins.rb +1 -1
- data/lib/rigor/mcp/server.rb +73 -2
- data/lib/rigor/mcp.rb +1 -1
- data/lib/rigor/plugin/access_denied_error.rb +1 -2
- data/lib/rigor/plugin/bundled_catalog.rb +1 -12
- data/lib/rigor/plugin/io_boundary.rb +1 -1
- data/lib/rigor/plugin/loader.rb +25 -8
- data/lib/rigor/plugin/macro/heredoc_template.rb +7 -8
- data/lib/rigor/plugin/macro/trait_registry.rb +6 -9
- data/lib/rigor/plugin/manifest.rb +9 -13
- data/lib/rigor/plugin/registry.rb +3 -3
- data/lib/rigor/plugin/services.rb +2 -3
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -0
- data/lib/rigor/plugin/trust_policy.rb +2 -2
- data/lib/rigor/plugin/type_node_resolver.rb +2 -4
- data/lib/rigor/plugin.rb +6 -8
- data/lib/rigor/plugin_gap_advisory.rb +5 -1
- data/lib/rigor/project_environment.rb +16 -2
- data/lib/rigor/protection/analysis_guard.rb +1 -1
- data/lib/rigor/protection/discovery_seed.rb +44 -5
- data/lib/rigor/protection/mutator.rb +1 -1
- data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
- data/lib/rigor/rbs_extended/hkt_directives.rb +6 -10
- data/lib/rigor/rbs_extended/reporter.rb +27 -3
- data/lib/rigor/rbs_extended.rb +2 -3
- data/lib/rigor/reflection/constant_ancestors.rb +5 -3
- data/lib/rigor/rooted_glob.rb +15 -1
- data/lib/rigor/run_scoped_slots.rb +38 -0
- data/lib/rigor/runtime/jit.rb +1 -1
- data/lib/rigor/scope/discovery_index.rb +19 -6
- data/lib/rigor/scope/resolution_chain.rb +3 -1
- data/lib/rigor/scope.rb +365 -31
- data/lib/rigor/sig_gen/classification.rb +4 -0
- data/lib/rigor/sig_gen/effect_annotation.rb +3 -3
- data/lib/rigor/sig_gen/generator.rb +371 -58
- data/lib/rigor/sig_gen/generic_header_scan.rb +189 -0
- data/lib/rigor/sig_gen/inline_declarations.rb +2 -2
- data/lib/rigor/sig_gen/layout_index.rb +3 -3
- data/lib/rigor/sig_gen/method_candidate.rb +23 -4
- data/lib/rigor/sig_gen/observation_collector.rb +3 -5
- data/lib/rigor/sig_gen/observed_call.rb +1 -1
- data/lib/rigor/sig_gen/path_mapper.rb +6 -0
- data/lib/rigor/sig_gen/renderer.rb +15 -9
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +15 -2
- data/lib/rigor/sig_gen/type_name_qualification.rb +69 -0
- data/lib/rigor/sig_gen/write_reporter.rb +63 -14
- data/lib/rigor/sig_gen/write_result.rb +23 -12
- data/lib/rigor/sig_gen/writer.rb +217 -37
- data/lib/rigor/type/acceptance_router.rb +2 -2
- data/lib/rigor/type/accepts_result.rb +1 -1
- data/lib/rigor/type/app.rb +4 -6
- data/lib/rigor/type/combinator.rb +9 -13
- data/lib/rigor/type/constant.rb +4 -5
- data/lib/rigor/type/hash_shape.rb +21 -40
- data/lib/rigor/type/union.rb +1 -1
- data/lib/rigor/type_node/identifier.rb +1 -2
- data/lib/rigor/type_node/resolver_chain.rb +1 -1
- data/lib/rigor/type_node/string_literal.rb +1 -2
- data/lib/rigor/type_node/symbol_literal.rb +1 -2
- data/lib/rigor/type_node/union.rb +3 -6
- data/lib/rigor/value_semantics.rb +1 -10
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +2 -2
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +2 -3
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -1
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +38 -36
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +4 -2
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +6 -4
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -5
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +0 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +2 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +66 -19
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +4 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +3 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +2 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +21 -31
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +10 -16
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +2 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +0 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +5 -4
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +3 -4
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +1 -1
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +7 -12
- data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +1 -1
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +1 -1
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/symbolize_keys.rb +102 -0
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +19 -6
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +26 -13
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +3 -3
- data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +1 -3
- data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +6 -6
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +5 -6
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +1 -1
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/params_shape.rb +2 -3
- data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -8
- data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +0 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +10 -18
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +0 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +0 -2
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +4 -5
- data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +0 -2
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +6 -7
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +2 -4
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +2 -5
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -2
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +6 -6
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +3 -8
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +2 -3
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -8
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -3
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +1 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +4 -11
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -1
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +2 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +1 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +2 -2
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +3 -4
- data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +6 -8
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +3 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +4 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +6 -7
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +1 -1
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +2 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +17 -21
- data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +2 -3
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +3 -2
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +4 -4
- data/sig/rigor/analysis/baseline.rbs +0 -3
- data/sig/rigor/analysis/fact_store.rbs +1 -0
- data/sig/rigor/analysis/reachability/scan_cache.rbs +1 -1
- data/sig/rigor/cli/diff_command.rbs +1 -1
- data/sig/rigor/cli/explain_command.rbs +1 -1
- data/sig/rigor/effects/registry.rbs +2 -2
- data/sig/rigor/inference.rbs +4 -1
- data/sig/rigor/plugin/additional_initializer.rbs +0 -5
- data/sig/rigor/plugin/base.rbs +1 -0
- data/sig/rigor/plugin/effect_attribution.rbs +3 -3
- data/sig/rigor/plugin/effect_edge.rbs +1 -1
- data/sig/rigor/plugin/effect_entry_points.rbs +3 -3
- data/sig/rigor/plugin/io_boundary.rbs +2 -1
- data/sig/rigor/plugin/manifest.rbs +9 -9
- data/sig/rigor/plugin/protocol_contract.rbs +3 -7
- data/sig/rigor/scope.rbs +23 -3
- data/skills/rigor-plugin-author/SKILL.md +4 -4
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +5 -5
- data/skills/rigor-plugin-review/SKILL.md +3 -3
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +12 -1
- metadata +36 -3
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "tempfile"
|
|
4
|
+
|
|
3
5
|
require_relative "../environment"
|
|
4
6
|
require_relative "../project_environment"
|
|
5
7
|
require_relative "../cache/store"
|
|
@@ -9,6 +11,7 @@ require_relative "../cache/incremental_snapshot"
|
|
|
9
11
|
require_relative "../effects/identity"
|
|
10
12
|
require_relative "../analysis/path_expansion"
|
|
11
13
|
require_relative "../protection/discovery_seed"
|
|
14
|
+
require_relative "../config_audit"
|
|
12
15
|
|
|
13
16
|
module Rigor
|
|
14
17
|
module LanguageServer
|
|
@@ -68,6 +71,24 @@ module Rigor
|
|
|
68
71
|
@project_scan = nil
|
|
69
72
|
@incremental_session = nil
|
|
70
73
|
@session_primed = false
|
|
74
|
+
@announced_config_warnings = Set.new
|
|
75
|
+
@announce_lock = Mutex.new
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Issue #2231 — the {ConfigAudit} warnings for the configuration as last loaded that the editor has not been
|
|
79
|
+
# shown yet, or an empty array. `rigor check` prints these on every run; the server is one long run, so it
|
|
80
|
+
# announces each warning once: at `initialized` for the configuration it started with, and after a later reload
|
|
81
|
+
# (every save round re-reads `.rigor.yml`) only the warnings that were not in the previous reload's set. The
|
|
82
|
+
# comparison is by message, not by order, so reordering entries announces nothing, and fixing one of two
|
|
83
|
+
# warnings does not show the other again. A warning that was fixed and then comes back is new and is shown.
|
|
84
|
+
# Called from the dispatch thread and the save round's thread alike, hence the lock.
|
|
85
|
+
def unannounced_config_warnings
|
|
86
|
+
@announce_lock.synchronize do
|
|
87
|
+
warnings = ConfigAudit.warnings(@configuration)
|
|
88
|
+
fresh = warnings.reject { |warning| @announced_config_warnings.include?(warning.message) }
|
|
89
|
+
@announced_config_warnings = warnings.to_set(&:message)
|
|
90
|
+
fresh
|
|
91
|
+
end
|
|
71
92
|
end
|
|
72
93
|
|
|
73
94
|
# Returns the cached `Rigor::Environment` for this session, building it on first access. The build
|
|
@@ -132,6 +153,22 @@ module Rigor
|
|
|
132
153
|
buffer: buffer, constant_sources: true)
|
|
133
154
|
end
|
|
134
155
|
|
|
156
|
+
# Issue #2282 — the base scope an editor query (hover, completion, signature help) probes with: the same
|
|
157
|
+
# cross-file discovery seed the diagnostic publisher hands its `Runner` ({#discovery_seed_for}), so a constant
|
|
158
|
+
# or method another file defines answers what `rigor check` would. The seed fold reads the buffer through a
|
|
159
|
+
# {Analysis::BufferBinding}, so `bytes` is staged in a tempfile for the call. An empty seed (no configured
|
|
160
|
+
# files, or nothing discovered) leaves the bare single-file scope every query used before.
|
|
161
|
+
def seeded_scope_for(path:, bytes:)
|
|
162
|
+
seed = Tempfile.create(["rigor-lsp-query-", ".rb"]) do |tmp|
|
|
163
|
+
# Staged as the publisher stages them; the seed's job is to withhold the stale on-disk declarations of
|
|
164
|
+
# this file, so an empty-bytes stage is not observable by design.
|
|
165
|
+
tmp.write(bytes)
|
|
166
|
+
tmp.flush
|
|
167
|
+
discovery_seed_for(Analysis::BufferBinding.new(logical_path: path, physical_path: tmp.path))
|
|
168
|
+
end
|
|
169
|
+
Protection::DiscoverySeed.seeded_scope(environment: environment, seed: seed)
|
|
170
|
+
end
|
|
171
|
+
|
|
135
172
|
# Builds what {#discovery_seed_for} reads once per generation, so a fork batch's children inherit it instead
|
|
136
173
|
# of each walking the project.
|
|
137
174
|
def prepare_discovery_seed
|
|
@@ -251,7 +288,8 @@ module Rigor
|
|
|
251
288
|
def reload_configuration
|
|
252
289
|
return unless @configuration_loader
|
|
253
290
|
|
|
254
|
-
|
|
291
|
+
loaded = @configuration_loader.call
|
|
292
|
+
@announce_lock.synchronize { @configuration = loaded }
|
|
255
293
|
rescue StandardError
|
|
256
294
|
nil
|
|
257
295
|
end
|
|
@@ -15,10 +15,9 @@ module Rigor
|
|
|
15
15
|
# dropped. The same claim/consume shape `DiagnosticPublisher#run_project_round` already uses for the
|
|
16
16
|
# whole-project save round (#246), generalised to an arbitrary key type and an arbitrary batch action.
|
|
17
17
|
class PublishBatcher
|
|
18
|
-
# @param on_batch —
|
|
19
|
-
#
|
|
20
|
-
#
|
|
21
|
-
# @param on_error — `(exception) -> void`, called when `on_batch` raises. A round must
|
|
18
|
+
# @param on_batch — called with the deduplicated keys pending at the start of one round. May be called
|
|
19
|
+
# more than once in a row when keys keep arriving while a round runs.
|
|
20
|
+
# @param on_error — called with the exception when `on_batch` raises. A round must
|
|
22
21
|
# never wedge the coalescing lock for every future `#enqueue` call — ownership is always released
|
|
23
22
|
# before this fires. Whatever was mid-flight when the round raised is lost; anything enqueued by a
|
|
24
23
|
# concurrent `#enqueue` call after this round's drain but before the rescue stays pending and rides
|
|
@@ -20,10 +20,8 @@ module Rigor
|
|
|
20
20
|
@project_context = project_context
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
# @param positions — LSP `
|
|
24
|
-
#
|
|
25
|
-
# @return one `SelectionRange` per
|
|
26
|
-
# position, or nil when the URI / buffer isn't resolvable.
|
|
23
|
+
# @param positions — LSP positions, each `{ line:, character: }` 0-based.
|
|
24
|
+
# @return one `SelectionRange` per position, or nil when the URI / buffer isn't resolvable.
|
|
27
25
|
def provide(uri, positions)
|
|
28
26
|
path, entry = buffer_for(uri)
|
|
29
27
|
return nil if entry.nil?
|
|
@@ -194,7 +194,12 @@ module Rigor
|
|
|
194
194
|
|
|
195
195
|
# `initialized` is a notification — no response body. Nothing is registered here yet: the server handles
|
|
196
196
|
# `workspace/didChangeWatchedFiles` when a client sends it, but does not register for it dynamically.
|
|
197
|
+
#
|
|
198
|
+
# Issue #2231 — the configuration the server started with is audited here, the first point at which the
|
|
199
|
+
# client is listening, so a `.rigor.yml` key that does nothing is named in the editor as `rigor check`
|
|
200
|
+
# names it on STDERR.
|
|
197
201
|
def handle_initialized
|
|
202
|
+
@publisher&.announce_config_warnings
|
|
198
203
|
nil
|
|
199
204
|
end
|
|
200
205
|
|
|
@@ -267,8 +272,7 @@ module Rigor
|
|
|
267
272
|
|
|
268
273
|
# textDocument/hover REQUEST. Slice 5 returns either a `Hover` payload (markdown contents wrapping type
|
|
269
274
|
# + erased-RBS info) or nil when no expression is at the queried position. Nil maps to `result: null`
|
|
270
|
-
# per LSP spec; clients suppress the popup. Returns `MethodNotFound` when no hover_provider is wired
|
|
271
|
-
# (slice 1-4 behaviour).
|
|
275
|
+
# per LSP spec; clients suppress the popup. Returns `MethodNotFound` when no hover_provider is wired.
|
|
272
276
|
def handle_hover(params)
|
|
273
277
|
return method_not_found("textDocument/hover") unless @hover_provider
|
|
274
278
|
|
|
@@ -351,7 +355,7 @@ module Rigor
|
|
|
351
355
|
)
|
|
352
356
|
end
|
|
353
357
|
|
|
354
|
-
# textDocument/documentSymbol REQUEST. Returns the
|
|
358
|
+
# textDocument/documentSymbol REQUEST. Returns the document-symbol outline for the buffer at the
|
|
355
359
|
# requested URI. Returns `MethodNotFound` when no provider is wired.
|
|
356
360
|
def handle_document_symbol(params)
|
|
357
361
|
return method_not_found("textDocument/documentSymbol") unless @document_symbol_provider
|
|
@@ -25,7 +25,7 @@ module Rigor
|
|
|
25
25
|
# parses the buffer, locates the enclosing `CallNode`, infers the receiver's type, and returns the
|
|
26
26
|
# method's first overload as a `SignatureInformation`.
|
|
27
27
|
#
|
|
28
|
-
# Slice C1
|
|
28
|
+
# Slice C1 ships:
|
|
29
29
|
# - Sentinel patching for `obj.foo(|` so Prism's parse succeeds (mirrors `CompletionProvider`'s slice B4
|
|
30
30
|
# pattern).
|
|
31
31
|
# - First-overload signature only.
|
|
@@ -111,7 +111,7 @@ module Rigor
|
|
|
111
111
|
end
|
|
112
112
|
|
|
113
113
|
def build_signature(call_node, root, path, bytes, line, character)
|
|
114
|
-
scope_index = build_scope_index(root, path)
|
|
114
|
+
scope_index = build_scope_index(root, path, bytes)
|
|
115
115
|
receiver_node = call_node.receiver
|
|
116
116
|
return nil if receiver_node.nil?
|
|
117
117
|
|
|
@@ -197,8 +197,8 @@ module Rigor
|
|
|
197
197
|
end
|
|
198
198
|
end
|
|
199
199
|
|
|
200
|
-
def build_scope_index(root,
|
|
201
|
-
scope =
|
|
200
|
+
def build_scope_index(root, path, bytes)
|
|
201
|
+
scope = @project_context.seeded_scope_for(path: path, bytes: bytes)
|
|
202
202
|
Inference::ScopeIndexer.index(root, default_scope: scope)
|
|
203
203
|
end
|
|
204
204
|
|
data/lib/rigor/lens/plugins.rb
CHANGED
|
@@ -29,7 +29,7 @@ module Rigor
|
|
|
29
29
|
return Rigor::VERSION if absolute.start_with?("#{BUNDLED_PLUGINS}/")
|
|
30
30
|
|
|
31
31
|
spec = Gem.loaded_specs.each_value.find do |gem|
|
|
32
|
-
absolute.start_with?("#{gem.full_gem_path}/")
|
|
32
|
+
absolute.start_with?("#{gem.full_gem_path}/")
|
|
33
33
|
end
|
|
34
34
|
spec&.version&.to_s
|
|
35
35
|
rescue StandardError
|
data/lib/rigor/mcp/server.rb
CHANGED
|
@@ -155,6 +155,10 @@ module Rigor
|
|
|
155
155
|
].freeze
|
|
156
156
|
Ractor.make_shareable(TOOLS)
|
|
157
157
|
|
|
158
|
+
# The tools that read a configuration — the ones taking a `config` argument — and so report its warnings.
|
|
159
|
+
CONFIG_TOOLS = TOOLS.filter_map { |tool| tool[:name] if tool.dig(:inputSchema, :properties, :config) }.freeze
|
|
160
|
+
Ractor.make_shareable(CONFIG_TOOLS)
|
|
161
|
+
|
|
158
162
|
def initialize(config_path: nil, err: $stderr)
|
|
159
163
|
@config_path = config_path
|
|
160
164
|
@err = err
|
|
@@ -165,7 +169,7 @@ module Rigor
|
|
|
165
169
|
id = request["id"]
|
|
166
170
|
method_name = request["method"]
|
|
167
171
|
|
|
168
|
-
#
|
|
172
|
+
# A notification carries no `id` and requires no response.
|
|
169
173
|
return nil if id.nil?
|
|
170
174
|
|
|
171
175
|
case method_name
|
|
@@ -181,8 +185,38 @@ module Rigor
|
|
|
181
185
|
end
|
|
182
186
|
end
|
|
183
187
|
|
|
188
|
+
# ADR-126 WD4 — `rigor_type_of` and `rigor_explain` print documents whose top-level shape changes in the next
|
|
189
|
+
# minor, and their CLI says so on stderr, which `tool_result` drops once there is a document. The notice is
|
|
190
|
+
# carried to the agent as its own `text` content block, the same text the CLI printed, so the document block
|
|
191
|
+
# stays byte-for-byte what the CLI wrote. When stdout is empty the error text already is stderr.
|
|
192
|
+
SHAPE_NOTICE_TOOLS = %w[rigor_type_of rigor_explain].freeze
|
|
193
|
+
|
|
184
194
|
private
|
|
185
195
|
|
|
196
|
+
def finish_tool_result(name, args, out, err, exit_code)
|
|
197
|
+
result = with_config_warnings(tool_result(out, err, exit_code), name, args, out)
|
|
198
|
+
result = with_shape_notice(result, name, out, err)
|
|
199
|
+
with_path_warnings(result, name, out, err)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# #2206 — `rigor coverage` writes its path warnings (a missing path skipped beside files it measures) to stderr,
|
|
203
|
+
# which a successful run drops. The tool carries the CLI's whole stderr (path warnings and any other warnings)
|
|
204
|
+
# as a second text block, unfiltered, so an agent sees what was skipped.
|
|
205
|
+
def with_path_warnings(result, name, out, err)
|
|
206
|
+
return result unless name == "rigor_coverage" && !out.empty? && !err.strip.empty?
|
|
207
|
+
|
|
208
|
+
result.merge(content: result.fetch(:content) + [{ type: "text", text: err }])
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def with_shape_notice(result, name, out, err)
|
|
212
|
+
return result unless SHAPE_NOTICE_TOOLS.include?(name) && !out.empty?
|
|
213
|
+
|
|
214
|
+
notice = err[CLI::JsonDocument::NOTICE_PATTERN]
|
|
215
|
+
return result unless notice
|
|
216
|
+
|
|
217
|
+
result.merge(content: result.fetch(:content) + [{ type: "text", text: notice }])
|
|
218
|
+
end
|
|
219
|
+
|
|
186
220
|
def handle_initialize(id)
|
|
187
221
|
require_relative "../version"
|
|
188
222
|
success(id, {
|
|
@@ -201,7 +235,7 @@ module Rigor
|
|
|
201
235
|
require_relative "../cli"
|
|
202
236
|
exit_code = with_captured_streams(out_io, err_io) { CLI.new(argv, out: out_io, err: err_io).run }
|
|
203
237
|
|
|
204
|
-
success(id,
|
|
238
|
+
success(id, finish_tool_result(name, args, out_io.string, err_io.string, exit_code))
|
|
205
239
|
rescue SystemExit => e
|
|
206
240
|
# Defence against any command that exits: it must not take the server down with it.
|
|
207
241
|
success(id, { content: [{ type: "text", text: "Command exited (status #{e.status})" }], isError: true })
|
|
@@ -221,6 +255,43 @@ module Rigor
|
|
|
221
255
|
{ content: [{ type: "text", text: out.empty? ? err : out }], isError: is_error }
|
|
222
256
|
end
|
|
223
257
|
|
|
258
|
+
# Issue #2231 — `rigor check` puts {ConfigAudit} warnings in its JSON `config_warnings`; the other tools read the
|
|
259
|
+
# same configuration and said nothing about it, so an agent saw `call.undefined-method` on a project whose
|
|
260
|
+
# `.rigor.yml` misspelt the key meant to silence it and got no hint why. Every tool that takes a `config`
|
|
261
|
+
# argument now carries them: a second `text` content block holding `{"config_warnings": [...]}`, each entry the
|
|
262
|
+
# shape check's JSON uses. The tool's own document is left byte-for-byte as its CLI printed it, and an output
|
|
263
|
+
# that already carries the key (check's) gets no second copy. Nothing is added when there is nothing to say,
|
|
264
|
+
# nor when the configuration does not load — the CLI has already reported that.
|
|
265
|
+
def with_config_warnings(result, name, args, out)
|
|
266
|
+
return result unless CONFIG_TOOLS.include?(name)
|
|
267
|
+
|
|
268
|
+
warnings = config_warnings(present_config(args["config"]) || @config_path)
|
|
269
|
+
return result if warnings.empty? || carries_config_warnings?(out)
|
|
270
|
+
|
|
271
|
+
block = { type: "text", text: JSON.generate({ "config_warnings" => warnings.map(&:to_h) }) }
|
|
272
|
+
result.merge(content: result.fetch(:content) + [block])
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
# Only a configuration that does not load is "no warnings to show": the CLI has already reported that error.
|
|
276
|
+
# A failure in ConfigAudit itself is a bug and surfaces as an internal error rather than reading as clean.
|
|
277
|
+
def config_warnings(config_path)
|
|
278
|
+
require_relative "../configuration"
|
|
279
|
+
require_relative "../config_audit"
|
|
280
|
+
begin
|
|
281
|
+
configuration = Configuration.load(config_path)
|
|
282
|
+
rescue ConfigurationError
|
|
283
|
+
return []
|
|
284
|
+
end
|
|
285
|
+
ConfigAudit.warnings(configuration)
|
|
286
|
+
end
|
|
287
|
+
|
|
288
|
+
def carries_config_warnings?(out)
|
|
289
|
+
document = JSON.parse(out)
|
|
290
|
+
document.is_a?(Hash) && document.key?("config_warnings")
|
|
291
|
+
rescue JSON::ParserError
|
|
292
|
+
false
|
|
293
|
+
end
|
|
294
|
+
|
|
224
295
|
# The JSON-RPC channel is the process's stdout, so nothing a command writes past its `out:` (an option
|
|
225
296
|
# parser's `--help`, a stray `puts`) may reach it while the command runs.
|
|
226
297
|
def with_captured_streams(out_io, err_io)
|
data/lib/rigor/mcp.rb
CHANGED
|
@@ -4,7 +4,7 @@ module Rigor
|
|
|
4
4
|
# The MCP (Model Context Protocol) server subsystem. See `docs/adr/33-mcp-server.md` for the design.
|
|
5
5
|
#
|
|
6
6
|
# Entry point: `rigor mcp --transport stdio`. The server exposes Rigor's analysis tools (check, type-of, triage,
|
|
7
|
-
# annotate, sig-gen, explain, coverage) as MCP tool calls over a newline-delimited JSON-RPC 2.0 stdio stream.
|
|
7
|
+
# lens, annotate, sig-gen, explain, coverage) as MCP tool calls over a newline-delimited JSON-RPC 2.0 stdio stream.
|
|
8
8
|
module MCP
|
|
9
9
|
end
|
|
10
10
|
end
|
|
@@ -6,8 +6,7 @@ module Rigor
|
|
|
6
6
|
# analyzer can convert the exception into a `:plugin_loader` diagnostic without crashing `rigor check`.
|
|
7
7
|
#
|
|
8
8
|
# The policy is documented in [ADR-2 § "Plugin Trust and I/O Policy"](../../../docs/adr/2-extension-api.md).
|
|
9
|
-
#
|
|
10
|
-
# may extend the set.
|
|
9
|
+
# `reason:` is one of `:read_outside_scope`, `:network_disabled`, `:url_fetch_failed` or `:url_body_too_large`.
|
|
11
10
|
class AccessDeniedError < StandardError
|
|
12
11
|
attr_reader :reason, :resource
|
|
13
12
|
|
|
@@ -127,7 +127,7 @@ module Rigor
|
|
|
127
127
|
def require_bundled_plugins
|
|
128
128
|
return {} unless File.directory?(BUNDLED_PLUGINS_ROOT)
|
|
129
129
|
|
|
130
|
-
add_bundled_lib_dirs
|
|
130
|
+
Loader.add_bundled_lib_dirs
|
|
131
131
|
Dir.children(BUNDLED_PLUGINS_ROOT).sort.each_with_object({}) do |gem_name, resolved|
|
|
132
132
|
path = Loader.bundled_plugin_path(gem_name)
|
|
133
133
|
next if path.nil?
|
|
@@ -137,17 +137,6 @@ module Rigor
|
|
|
137
137
|
end
|
|
138
138
|
end
|
|
139
139
|
|
|
140
|
-
# A bundled plugin's entry file requires its own tree by bare feature name (`require
|
|
141
|
-
# "rigor/plugin/activestorage"`), which resolves only with that plugin's `lib` on the load path — the
|
|
142
|
-
# loader gets it from the bundle, and this catalogue has to arrange it itself. Prepending the engine's
|
|
143
|
-
# own copies matches ADR-93 WD5's anti-skew rule, where the bundled copy always wins.
|
|
144
|
-
def add_bundled_lib_dirs
|
|
145
|
-
Dir.children(BUNDLED_PLUGINS_ROOT).sort.each do |gem_name|
|
|
146
|
-
dir = File.join(BUNDLED_PLUGINS_ROOT, gem_name, "lib")
|
|
147
|
-
$LOAD_PATH.unshift(dir) if File.directory?(dir) && !$LOAD_PATH.include?(dir)
|
|
148
|
-
end
|
|
149
|
-
end
|
|
150
|
-
|
|
151
140
|
# A plugin gem that fails to load is skipped rather than fatal: the catalogue exists to *advise*, and
|
|
152
141
|
# a broken bundled copy is already reported by `rigor doctor`'s plugin checks for the plugins the
|
|
153
142
|
# project actually enabled. The failure is recorded in {load_failures} so a missing entry stays
|
|
@@ -17,7 +17,7 @@ module Rigor
|
|
|
17
17
|
# scope and feed the cache descriptor; contributions built on top of out-of-scope reads will not
|
|
18
18
|
# invalidate correctly.
|
|
19
19
|
#
|
|
20
|
-
#
|
|
20
|
+
# The surface:
|
|
21
21
|
#
|
|
22
22
|
# - `#read_file(path)` — validates against the policy, returns the file's contents, and adds a
|
|
23
23
|
# digest-keyed {Cache::Descriptor::FileEntry} to the boundary's accumulated descriptor. A read that
|
data/lib/rigor/plugin/loader.rb
CHANGED
|
@@ -90,6 +90,20 @@ module Rigor
|
|
|
90
90
|
File.directory?(path) ? path : nil
|
|
91
91
|
end
|
|
92
92
|
|
|
93
|
+
# Puts every bundled plugin's `lib` on `$LOAD_PATH`, so a bundled plugin that requires another by gem
|
|
94
|
+
# name (`require "rigor-ffi"` from `rigor-ethon`, `rigor-sassc`, `rigor-ffi-rzmq` and `rigor-rbnacl`)
|
|
95
|
+
# resolves without a bundle. The engine's own copies go first, which matches ADR-93 WD5's anti-skew
|
|
96
|
+
# rule: the bundled copy always wins. Idempotent; a trimmed packaging with no `plugins/` tree adds none.
|
|
97
|
+
def self.add_bundled_lib_dirs
|
|
98
|
+
root = File.join(ENGINE_ROOT, "plugins")
|
|
99
|
+
return unless File.directory?(root)
|
|
100
|
+
|
|
101
|
+
Dir.children(root).sort.each do |gem_name|
|
|
102
|
+
dir = File.join(root, gem_name, "lib")
|
|
103
|
+
$LOAD_PATH.unshift(dir) if File.directory?(dir) && !$LOAD_PATH.include?(dir)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
93
107
|
# @param entries — the raw `plugins:` list from the configuration.
|
|
94
108
|
def load(entries)
|
|
95
109
|
plugins = []
|
|
@@ -233,7 +247,13 @@ module Rigor
|
|
|
233
247
|
# `feature_resolver` is unaffected: an absolute-path require's `$LOADED_FEATURES` entry still ends in
|
|
234
248
|
# `/<gem>.rb`, the suffix {FEATURE_RESOLVER} matches.
|
|
235
249
|
def require_gem!(entry)
|
|
236
|
-
|
|
250
|
+
path = self.class.bundled_plugin_path(entry[:gem])
|
|
251
|
+
if path
|
|
252
|
+
self.class.add_bundled_lib_dirs
|
|
253
|
+
@requirer.call(path)
|
|
254
|
+
else
|
|
255
|
+
@requirer.call(entry[:gem])
|
|
256
|
+
end
|
|
237
257
|
rescue ::LoadError => e
|
|
238
258
|
raise LoadError.new(
|
|
239
259
|
"could not load plugin gem #{entry[:gem].inspect}: #{e.message}",
|
|
@@ -272,13 +292,10 @@ module Rigor
|
|
|
272
292
|
end
|
|
273
293
|
end
|
|
274
294
|
|
|
275
|
-
# A gem that registers
|
|
276
|
-
#
|
|
277
|
-
#
|
|
278
|
-
#
|
|
279
|
-
# which activates the wrong plugin. The failure was load-order dependent (listing `rigor-ffi` first
|
|
280
|
-
# made the nested require a no-op and the entry loaded), so it never showed up in the suite, where
|
|
281
|
-
# some earlier example has always loaded `rigor-ffi`.
|
|
295
|
+
# A gem that registers several plugins is a meta-gem only when NONE of the ids is its own. The FFI family
|
|
296
|
+
# (`rigor-sassc`, `rigor-ethon`, `rigor-ffi-rzmq`, `rigor-rbnacl`) each `require "rigor-ffi"`, so listing
|
|
297
|
+
# one alone registered `ffi` too and was wrongly rejected as a meta-gem. The bug was load-order dependent
|
|
298
|
+
# and hid in the suite, where an earlier example always loaded `rigor-ffi`.
|
|
282
299
|
#
|
|
283
300
|
# `rigor-rails` stays a meta-gem under this rule: it registers eight ids and none of them is `rails`.
|
|
284
301
|
def own_id_among(gem_name, ids)
|
|
@@ -48,11 +48,11 @@ module Rigor
|
|
|
48
48
|
#
|
|
49
49
|
# Slice 2 ships at the **floor**: each emit row's `name:` is the source of truth for the synthetic
|
|
50
50
|
# method's name (a single `"\#{name}"` placeholder gets interpolated with the literal symbol argument at
|
|
51
|
-
# `symbol_arg_position`). The
|
|
52
|
-
#
|
|
53
|
-
#
|
|
54
|
-
#
|
|
55
|
-
#
|
|
51
|
+
# `symbol_arg_position`). The synthetic method's return type is the `returns_from_arg:` lookup when it
|
|
52
|
+
# resolves, else a non-empty `returns:` string (`SyntheticMethodScanner#resolve_emit_return_type`); the
|
|
53
|
+
# dispatcher turns it into a nominal type through `MethodDispatcher#promote_via_return_type` when the
|
|
54
|
+
# environment knows the class. With neither, an `"untyped"` / `"void"` placeholder, or an unknown class, the
|
|
55
|
+
# method answers `Dynamic[Top]`. Resolution through ADR-13's `Plugin::TypeNodeResolver` is not wired.
|
|
56
56
|
#
|
|
57
57
|
# Engine wiring: `Inference::SyntheticMethodScanner` (slice 2b, `synthetic_method_scanner.rb`) consumes
|
|
58
58
|
# `manifest.heredoc_templates`. Worked consumers: `plugins/rigor-dry-struct/` and
|
|
@@ -98,7 +98,7 @@ module Rigor
|
|
|
98
98
|
# `\#{name}` with the call-site literal symbol) and its declared return type. The return type can be
|
|
99
99
|
# a static String (resolved via `Environment#nominal_for_name` per ADR-16 slice 6b) or a
|
|
100
100
|
# per-call-site lookup ({ReturnsFromArg}) — see
|
|
101
|
-
# [ADR-18](
|
|
101
|
+
# [ADR-18](../../../../docs/adr/18-substrate-per-call-site-return-type.md). When both are nil, the
|
|
102
102
|
# synthesised method's return type falls back to `Dynamic[Top]`.
|
|
103
103
|
class Emit
|
|
104
104
|
attr_reader :name, :returns, :returns_from_arg
|
|
@@ -147,8 +147,7 @@ module Rigor
|
|
|
147
147
|
# lookup_via: { plugin_id: "dry-types", fact: :dry_type_aliases }
|
|
148
148
|
# }
|
|
149
149
|
#
|
|
150
|
-
#
|
|
151
|
-
# extraction + fact-store lookup land in slice 2 / 3.
|
|
150
|
+
# The scanner's `resolve_emit_return_type` does the arg-position extraction and fact-store lookup.
|
|
152
151
|
class ReturnsFromArg
|
|
153
152
|
attr_reader :position, :plugin_id, :fact
|
|
154
153
|
|
|
@@ -39,12 +39,10 @@ module Rigor
|
|
|
39
39
|
# lexical receiver class equals or inherits from this constraint.
|
|
40
40
|
# - `method_name` — Symbol naming the DSL method (e.g. `:devise`).
|
|
41
41
|
# - `symbol_arg_position` — `:rest` (all positional Symbol args are traits, slice 3a's only supported
|
|
42
|
-
# form) or a non-negative Integer (the index of a single trait symbol
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
# `macro.tier_b.unknown-trait` `:info` provenance marker per WD9 / WD13).
|
|
47
|
-
# - `always_included` — Array<String>. Fully-qualified module names that are added to every call site
|
|
42
|
+
# form) or a non-negative Integer (the index of a single trait symbol).
|
|
43
|
+
# - `modules_by_symbol` — Maps each recognised trait symbol to a fully-qualified module name. Symbols not
|
|
44
|
+
# in the table are silently skipped.
|
|
45
|
+
# - `always_included` — Fully-qualified module names that are added to every call site
|
|
48
46
|
# (even when no symbols match). Mirrors Devise's `always_include` modules.
|
|
49
47
|
#
|
|
50
48
|
# ## Floor / ceiling per ADR-16 WD13
|
|
@@ -53,7 +51,7 @@ module Rigor
|
|
|
53
51
|
# methods into the existing `SyntheticMethodIndex` (slice 2b primitive). The synthesised methods adopt
|
|
54
52
|
# the module's authored RBS return types — Tier B is NOT subject to the Tier C `Dynamic[T]` floor
|
|
55
53
|
# because the source-of-truth (the module's authored RBS) is not a manifest-declared string. Per ADR-5
|
|
56
|
-
# robustness, the substrate does not fabricate precision; it simply replays the
|
|
54
|
+
# robustness, the substrate does not fabricate precision; it simply replays the module's signatures.
|
|
57
55
|
#
|
|
58
56
|
# **Out of scope for slice 3** (deferred follow-ups):
|
|
59
57
|
# - `class_methods_module:` per-trait (Devise's `ClassMethods` extend-pattern); slice 3 covers instance
|
|
@@ -106,8 +104,7 @@ module Rigor
|
|
|
106
104
|
end
|
|
107
105
|
|
|
108
106
|
# @return fully-qualified module name for the given trait symbol, or nil when the
|
|
109
|
-
# registry doesn't know the symbol (caller
|
|
110
|
-
# through).
|
|
107
|
+
# registry doesn't know the symbol (the caller skips it).
|
|
111
108
|
def module_for(symbol)
|
|
112
109
|
modules_by_symbol[symbol.to_sym]
|
|
113
110
|
end
|
|
@@ -168,9 +168,8 @@ module Rigor
|
|
|
168
168
|
end
|
|
169
169
|
private :assign_additional_initializers
|
|
170
170
|
|
|
171
|
-
# ADR-103 WD2 / WD6 / WD10 / WD14 — the
|
|
172
|
-
#
|
|
173
|
-
# The six `effect_*` fields validate together, so {#initialize} carries one call rather than six.
|
|
171
|
+
# ADR-103 WD2 / WD6 / WD10 / WD14 — the six `effect_*` fields validate together here, so {#initialize}
|
|
172
|
+
# carries one call rather than six.
|
|
174
173
|
def validate_effect_fields!(effect_root, effect_labels, effect_attributions, effect_edges,
|
|
175
174
|
effect_entry_points, effect_ancestry)
|
|
176
175
|
validate_effect_root!(effect_root)
|
|
@@ -475,10 +474,9 @@ module Rigor
|
|
|
475
474
|
end
|
|
476
475
|
end
|
|
477
476
|
|
|
478
|
-
# ADR-13
|
|
479
|
-
#
|
|
480
|
-
#
|
|
481
|
-
# registration order; the parser integration that actually drives the chain lands in slice 3.
|
|
477
|
+
# ADR-13 — `type_node_resolvers:` declares the plugin-supplied `TypeNodeResolver` instances the parser
|
|
478
|
+
# consults when an RBS::Extended payload's named- or generic-type head misses the built-in registry. The
|
|
479
|
+
# registry exposes them in registration order, and `Environment#build_name_scope` builds the chain.
|
|
482
480
|
def validate_type_node_resolvers!(resolvers)
|
|
483
481
|
validate_array_of!("type_node_resolvers", resolvers, "Rigor::Plugin::TypeNodeResolver instances") do |r|
|
|
484
482
|
r.is_a?(TypeNodeResolver)
|
|
@@ -486,8 +484,7 @@ module Rigor
|
|
|
486
484
|
end
|
|
487
485
|
|
|
488
486
|
# ADR-16 slice 1a — `block_as_methods:` declares the Tier A substrate entries the plugin contributes.
|
|
489
|
-
#
|
|
490
|
-
# `Scope#self_type` for matching blocks arrives in a subsequent slice.
|
|
487
|
+
# The engine hook that narrows `Scope#self_type` for matching blocks is `Inference::MacroBlockSelfType`.
|
|
491
488
|
def validate_block_as_methods!(entries)
|
|
492
489
|
validate_array_of!("block_as_methods", entries, "Rigor::Plugin::Macro::BlockAsMethod instances") do |e|
|
|
493
490
|
e.is_a?(Macro::BlockAsMethod)
|
|
@@ -495,8 +492,7 @@ module Rigor
|
|
|
495
492
|
end
|
|
496
493
|
|
|
497
494
|
# ADR-16 slice 2a — `heredoc_templates:` declares the Tier C substrate entries (heredoc-template
|
|
498
|
-
# synthesis on class-level DSL calls).
|
|
499
|
-
# `SyntheticMethodIndex` that actually emit synthetic methods arrive in slice 2b.
|
|
495
|
+
# synthesis on class-level DSL calls). `Inference::SyntheticMethodScanner` emits the synthetic methods.
|
|
500
496
|
def validate_heredoc_templates!(entries)
|
|
501
497
|
validate_array_of!("heredoc_templates", entries, "Rigor::Plugin::Macro::HeredocTemplate instances") do |e|
|
|
502
498
|
e.is_a?(Macro::HeredocTemplate)
|
|
@@ -515,8 +511,8 @@ module Rigor
|
|
|
515
511
|
end
|
|
516
512
|
|
|
517
513
|
# ADR-16 slice 3a — `trait_registries:` declares the Tier B substrate entries (trait-inlining via
|
|
518
|
-
# bundled module registry).
|
|
519
|
-
#
|
|
514
|
+
# bundled module registry). `Inference::SyntheticMethodScanner#collect_trait_registries` explodes them
|
|
515
|
+
# per method through `SyntheticMethodIndex`.
|
|
520
516
|
def validate_trait_registries!(entries)
|
|
521
517
|
validate_array_of!("trait_registries", entries, "Rigor::Plugin::Macro::TraitRegistry instances") do |e|
|
|
522
518
|
e.is_a?(Macro::TraitRegistry)
|
|
@@ -214,7 +214,7 @@ module Rigor
|
|
|
214
214
|
# tie-breaker.
|
|
215
215
|
#
|
|
216
216
|
# ADR-15 Phase 3 — alongside the instantiated `plugins`, the registry carries `blueprints`: a frozen,
|
|
217
|
-
# Ractor-shareable
|
|
217
|
+
# Ractor-shareable list of {Blueprint} that records how to re-instantiate the same plugin set in a worker
|
|
218
218
|
# Ractor. The eventual Phase 4 pool ships `blueprints` across the boundary and calls {.materialize}
|
|
219
219
|
# per-Ractor; the live `plugins` carriage on the coordinator registry stays unchanged.
|
|
220
220
|
class Registry
|
|
@@ -556,8 +556,8 @@ module Rigor
|
|
|
556
556
|
# Issue #806 — {#safe_manifest} with a receipt. The construction-time aggregates degraded silently
|
|
557
557
|
# when a manifest read raised, so a plugin that contributed nothing looked identical to a plugin that
|
|
558
558
|
# was never asked; the load-error channel is where "a plugin could not be loaded" already lives, and
|
|
559
|
-
# `Analysis::Runner` turns each entry into one `
|
|
560
|
-
# plugin ref is the CLASS, not `manifest.id`: the manifest is the surface that just failed.
|
|
559
|
+
# `Analysis::Runner` turns each entry into one `load-error` row (source family `plugin_loader`) naming
|
|
560
|
+
# the plugin. The plugin ref is the CLASS, not `manifest.id`: the manifest is the surface that just failed.
|
|
561
561
|
def guarded_manifest(plugin, errors)
|
|
562
562
|
plugin.manifest
|
|
563
563
|
rescue StandardError => e
|
|
@@ -25,9 +25,8 @@ module Rigor
|
|
|
25
25
|
# documents the trust model the boundary enforces.
|
|
26
26
|
#
|
|
27
27
|
# ADR-9 slice 2 adds `fact_store`: the per-run cross-plugin `Plugin::FactStore`. Producer plugins publish
|
|
28
|
-
# their facts in `#prepare(services)
|
|
29
|
-
# `services.fact_store.read(...)`. A fresh `FactStore`
|
|
30
|
-
# supplied — the runner threads its own instance in once slice 3 wires `#prepare` invocation.
|
|
28
|
+
# their facts in `#prepare(services)`; consumer plugins read in `#diagnostics_for_file` via
|
|
29
|
+
# `services.fact_store.read(...)`. A fresh `FactStore` is constructed when none is supplied.
|
|
31
30
|
class Services
|
|
32
31
|
attr_reader :reflection, :type, :configuration, :cache_store, :trust_policy, :fact_store
|
|
33
32
|
|
|
@@ -61,6 +61,17 @@ module Rigor
|
|
|
61
61
|
def empty?
|
|
62
62
|
@entries.empty?
|
|
63
63
|
end
|
|
64
|
+
|
|
65
|
+
# The pool drain ({Rigor::Analysis::WorkerSession#drain_reporters}): the frozen entries.
|
|
66
|
+
def snapshot
|
|
67
|
+
entries
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Replays one worker's {#snapshot}, `kind:` (#824) and `line:` (#1075) included; the dedup above
|
|
71
|
+
# collapses every worker's copy. Nil records nothing.
|
|
72
|
+
def absorb(snapshot)
|
|
73
|
+
Array(snapshot).each { |entry| record(**entry.to_h) }
|
|
74
|
+
end
|
|
64
75
|
end
|
|
65
76
|
end
|
|
66
77
|
end
|
|
@@ -19,8 +19,8 @@ module Rigor
|
|
|
19
19
|
# - `trusted_gems`: gem names the user has authorised. Derived from the `plugins:` section of `.rigor.yml`
|
|
20
20
|
# plus any gems they reach transitively. Used today for documentation and future trust diagnostics.
|
|
21
21
|
# - `allowed_read_roots`: absolute paths plugin code may read from through the {IoBoundary}. The default
|
|
22
|
-
# set covers the project root, the project's `signature_paths`,
|
|
23
|
-
#
|
|
22
|
+
# set covers the project root, the project's `signature_paths`, and each trusted
|
|
23
|
+
# gem's `Gem::Specification#full_gem_path`. The user extends this with `.rigor.yml`'s
|
|
24
24
|
# `plugins_io.allowed_paths:`.
|
|
25
25
|
# - `network_policy`: one of {VALID_NETWORK_POLICIES}. `:disabled` (default) makes
|
|
26
26
|
# {IoBoundary#open_url} always raise. `:allowlist` (v0.1.2) consults `allowed_url_hosts` on every fetch —
|
|
@@ -28,10 +28,8 @@ 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 — the parser-emitted node the chain is
|
|
32
|
-
#
|
|
33
|
-
# @param scope — companion value object (slice 3); slice 2 invocations MAY pass
|
|
34
|
-
# `nil` because the chain doesn't exist yet.
|
|
31
|
+
# @param node — the parser-emitted node the chain is asking about.
|
|
32
|
+
# @param scope — the scope the chain was invoked with.
|
|
35
33
|
# @return resolved type, or `nil` to fall through.
|
|
36
34
|
def resolve(node, scope) # rubocop:disable Lint/UnusedMethodArgument
|
|
37
35
|
nil
|