rigortype 0.4.6 → 0.4.8
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 +4 -2
- data/data/core_overlay/bigdecimal.rbs +33 -0
- data/data/core_overlay/resolv.rbs +30 -0
- data/data/gem_overlay/activesupport/core_ext.rbs +45 -13
- data/data/gem_overlay/addressable/addressable.rbs +40 -0
- 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 +6 -3
- data/docs/handbook/11-sig-gen.md +85 -3
- data/docs/handbook/appendix-phpstan.md +4 -4
- data/docs/install.md +20 -0
- data/docs/manual/01-installation.md +67 -26
- data/docs/manual/02-cli-reference.md +455 -113
- data/docs/manual/03-configuration.md +76 -5
- data/docs/manual/04-diagnostics.md +5 -4
- data/docs/manual/06-baseline.md +6 -4
- data/docs/manual/08-skills.md +11 -0
- data/docs/manual/09-editor-integration.md +21 -2
- data/docs/manual/10-mcp-server.md +38 -2
- data/docs/manual/12-caching.md +4 -3
- data/docs/manual/14-rails-quickstart.md +1 -1
- data/docs/manual/19-effect-labels.md +3 -4
- data/docs/manual/plugins/rigor-actionpack.md +32 -15
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +23 -1
- data/docs/manual/plugins/rigor-sorbet.md +17 -3
- data/lib/rigor/analysis/baseline.rb +24 -27
- data/lib/rigor/analysis/check_rules/always_raises_shield.rb +147 -0
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +5 -6
- data/lib/rigor/analysis/check_rules/call_facts.rb +58 -0
- data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +2 -2
- data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +12 -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 +16 -4
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +6 -1
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -8
- data/lib/rigor/analysis/check_rules/plugin_api_surface.rb +195 -0
- data/lib/rigor/analysis/check_rules/published_constant_guard.rb +41 -12
- data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +27 -1
- data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +3 -3
- data/lib/rigor/analysis/check_rules/replaced_singleton_defs.rb +96 -0
- data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
- data/lib/rigor/analysis/check_rules/rule_ids.rb +9 -2
- 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 +2 -2
- data/lib/rigor/analysis/check_rules.rb +720 -141
- data/lib/rigor/analysis/crash_signature.rb +45 -3
- data/lib/rigor/analysis/dependency_recorder.rb +237 -80
- 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.rb +11 -1
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +4 -3
- data/lib/rigor/analysis/incremental_run_slot.rb +2 -2
- data/lib/rigor/analysis/incremental_session.rb +306 -53
- data/lib/rigor/analysis/reachability/plugin_roots.rb +6 -3
- data/lib/rigor/analysis/rule_catalog.rb +39 -3
- data/lib/rigor/analysis/run_cache_key.rb +27 -3
- data/lib/rigor/analysis/run_disclosure_credit.rb +26 -6
- data/lib/rigor/analysis/run_stats.rb +2 -2
- data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -2
- data/lib/rigor/analysis/runner/declaration_position.rb +8 -8
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +264 -112
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +4 -9
- data/lib/rigor/analysis/runner/pool_coordinator.rb +173 -328
- data/lib/rigor/analysis/runner/project_pre_passes.rb +4 -37
- 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 +298 -109
- data/lib/rigor/analysis/self_call_resolution_recorder.rb +62 -23
- 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 +65 -71
- data/lib/rigor/bleeding_edge.rb +22 -10
- 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/file_digest.rb +39 -31
- data/lib/rigor/cache/incremental_snapshot.rb +95 -17
- 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 +90 -4
- data/lib/rigor/cache/rbs_environment.rb +8 -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 +160 -57
- data/lib/rigor/cli/catalogue.rb +256 -0
- data/lib/rigor/cli/check_command.rb +112 -104
- 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 +26 -18
- 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 +29 -10
- 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 +92 -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 +2 -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 +214 -56
- data/lib/rigor/config_audit.rb +33 -19
- data/lib/rigor/configuration/severity_profile.rb +14 -5
- data/lib/rigor/configuration.rb +104 -35
- data/lib/rigor/effects/collector.rb +53 -21
- 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 +64 -33
- data/lib/rigor/environment/class_registry.rb +3 -3
- data/lib/rigor/environment/constant_type_cache_holder.rb +5 -0
- data/lib/rigor/environment/core_root.rb +77 -0
- data/lib/rigor/environment/default_libraries.rb +18 -1
- data/lib/rigor/environment/deferred_signature_plan.rb +381 -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/hkt_registry_holder.rb +6 -0
- 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_collection_discovery.rb +123 -8
- data/lib/rigor/environment/rbs_coverage_report.rb +12 -11
- data/lib/rigor/environment/rbs_hierarchy.rb +12 -15
- data/lib/rigor/environment/rbs_loader.rb +772 -110
- 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 +169 -42
- data/lib/rigor/flow_contribution/merger.rb +4 -4
- data/lib/rigor/inference/acceptance.rb +136 -40
- data/lib/rigor/inference/alias_mutation_floor/foreign_locals.rb +117 -0
- data/lib/rigor/inference/alias_mutation_floor/memos.rb +85 -0
- data/lib/rigor/inference/alias_mutation_floor/reach.rb +119 -0
- data/lib/rigor/inference/alias_mutation_floor/site_collector.rb +252 -0
- data/lib/rigor/inference/alias_mutation_floor.rb +339 -0
- data/lib/rigor/inference/anonymous_meta_class.rb +18 -0
- data/lib/rigor/inference/block_auto_splat.rb +1 -1
- data/lib/rigor/inference/block_parameter_binder.rb +2 -3
- data/lib/rigor/inference/block_run_count.rb +185 -0
- data/lib/rigor/inference/budget_trace.rb +8 -2
- data/lib/rigor/inference/builtins/array_catalog.rb +2 -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/captured_locals.rb +2 -0
- data/lib/rigor/inference/census_foreign_block.rb +354 -0
- data/lib/rigor/inference/clause_widening.rb +1 -1
- data/lib/rigor/inference/closure_escape_analyzer.rb +11 -4
- data/lib/rigor/inference/construction_escape.rb +258 -0
- data/lib/rigor/inference/declaration_walk/traversal.rb +1 -1
- data/lib/rigor/inference/declaration_walk.rb +4 -0
- 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 +33 -15
- data/lib/rigor/inference/def_return_typer.rb +47 -5
- data/lib/rigor/inference/definer_resolution.rb +19 -9
- data/lib/rigor/inference/definers_index.rb +162 -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/error_info.rb +16 -0
- data/lib/rigor/inference/expression_typer.rb +1559 -320
- data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
- data/lib/rigor/inference/flow_tracer.rb +31 -14
- data/lib/rigor/inference/fork_map.rb +3 -0
- data/lib/rigor/inference/fresh_frame_blocks.rb +52 -1
- data/lib/rigor/inference/global_write_census.rb +81 -5
- 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/in_effect_refinements.rb +7 -0
- data/lib/rigor/inference/indexed_narrowing.rb +61 -15
- data/lib/rigor/inference/intervening_ivar_reset.rb +26 -4
- data/lib/rigor/inference/ivar_writer_census.rb +154 -8
- data/lib/rigor/inference/key_presence_guard.rb +51 -24
- data/lib/rigor/inference/last_line/self_evidence.rb +30 -0
- data/lib/rigor/inference/last_line.rb +5 -2
- data/lib/rigor/inference/loop_value_record.rb +66 -14
- data/lib/rigor/inference/match_rebinding/frame.rb +57 -0
- data/lib/rigor/inference/match_rebinding/yielding_match.rb +101 -0
- data/lib/rigor/inference/match_rebinding.rb +28 -5
- 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 +44 -30
- data/lib/rigor/inference/method_dispatcher/class_keyed_block_params.rb +65 -0
- 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/core_enumerable_receiver.rb +44 -0
- data/lib/rigor/inference/method_dispatcher/declared_constructor.rb +151 -0
- data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/imprecise_argument.rb +2 -2
- data/lib/rigor/inference/method_dispatcher/interface_shape.rb +181 -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 +51 -27
- data/lib/rigor/inference/method_dispatcher/random_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +606 -78
- 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 +3 -3
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +9 -7
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +467 -84
- 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/singleton_type_params.rb +104 -0
- 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 +123 -25
- data/lib/rigor/inference/method_parameter_binder.rb +136 -56
- data/lib/rigor/inference/module_function_state/spellings.rb +2 -2
- data/lib/rigor/inference/module_function_state.rb +24 -3
- data/lib/rigor/inference/multi_target_binder.rb +2 -2
- data/lib/rigor/inference/mutation_rejoin.rb +240 -4
- data/lib/rigor/inference/mutation_widening.rb +199 -29
- data/lib/rigor/inference/narrowing.rb +238 -50
- data/lib/rigor/inference/operand_effects.rb +43 -2
- data/lib/rigor/inference/optimistic_origin.rb +134 -9
- data/lib/rigor/inference/origin_lookup.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/precision_scanner.rb +1 -1
- data/lib/rigor/inference/project_method_ownership.rb +30 -9
- data/lib/rigor/inference/project_patched_methods.rb +65 -13
- data/lib/rigor/inference/project_patched_scanner.rb +120 -25
- data/lib/rigor/inference/project_patched_visibility.rb +39 -3
- data/lib/rigor/inference/project_yielder.rb +11 -17
- data/lib/rigor/inference/rbs_type_translator.rb +68 -15
- data/lib/rigor/inference/receiver_alias.rb +20 -0
- data/lib/rigor/inference/receiver_writer_call.rb +67 -0
- data/lib/rigor/inference/recursion_rebinding.rb +157 -0
- data/lib/rigor/inference/refined_dispatch/constructed_class.rb +57 -17
- data/lib/rigor/inference/refined_dispatch/own_method.rb +51 -16
- data/lib/rigor/inference/refined_dispatch.rb +13 -0
- data/lib/rigor/inference/reflective_ivar_write.rb +19 -11
- data/lib/rigor/inference/remover_holder_scan.rb +287 -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/class_cvars_collector.rb +13 -6
- data/lib/rigor/inference/scope_indexer/constant_ivar_value.rb +93 -0
- data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +70 -26
- 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 +295 -0
- data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +136 -9
- data/lib/rigor/inference/scope_indexer.rb +2213 -226
- data/lib/rigor/inference/self_call_ivar_writes.rb +103 -12
- data/lib/rigor/inference/self_preserving_block.rb +87 -0
- data/lib/rigor/inference/set_mutation.rb +189 -0
- data/lib/rigor/inference/singleton_hook_decline.rb +42 -8
- data/lib/rigor/inference/singleton_object_constant.rb +32 -2
- data/lib/rigor/inference/statement_evaluator.rb +3268 -358
- data/lib/rigor/inference/string_mutation.rb +14 -0
- data/lib/rigor/inference/struct_fold_safety.rb +4 -6
- data/lib/rigor/inference/synthetic_method_scanner.rb +40 -17
- data/lib/rigor/inference/toplevel_def_census.rb +67 -0
- data/lib/rigor/inference/unknown_store_widening.rb +103 -4
- data/lib/rigor/inference/unthreaded_rebinds.rb +2 -3
- data/lib/rigor/inference/untyped_rejoin.rb +1 -1
- data/lib/rigor/inference/void_tail_summary.rb +29 -9
- 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/claims/comparison.rb +78 -0
- data/lib/rigor/lens/claims/request.rb +131 -0
- data/lib/rigor/lens/claims/type_reader.rb +216 -0
- data/lib/rigor/lens/claims.rb +208 -0
- data/lib/rigor/lens/contract_reader.rb +5 -0
- data/lib/rigor/lens/document.rb +1 -1
- data/lib/rigor/lens/file_reader.rb +21 -8
- data/lib/rigor/lens/plugins.rb +1 -1
- data/lib/rigor/lens/run.rb +8 -0
- data/lib/rigor/lens.rb +1 -1
- data/lib/rigor/library_probe.rb +68 -0
- data/lib/rigor/mcp/server.rb +73 -2
- data/lib/rigor/mcp.rb +1 -1
- data/lib/rigor/memo_audit.rb +282 -0
- data/lib/rigor/plugin/access_denied_error.rb +1 -2
- data/lib/rigor/plugin/base.rb +15 -2
- data/lib/rigor/plugin/bundled_catalog.rb +1 -12
- data/lib/rigor/plugin/io_boundary.rb +26 -4
- 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 +16 -16
- data/lib/rigor/plugin/registry.rb +16 -5
- 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 +48 -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/mutation_cache.rb +4 -1
- 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 +81 -22
- data/lib/rigor/reflection/declaration_collectors.rb +55 -0
- data/lib/rigor/reflection/declarations.rb +58 -0
- data/lib/rigor/reflection/project_method.rb +177 -0
- data/lib/rigor/reflection.rb +2 -0
- data/lib/rigor/rooted_glob.rb +32 -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 +168 -8
- data/lib/rigor/scope/resolution_chain/relevance.rb +14 -1
- data/lib/rigor/scope/resolution_chain.rb +901 -54
- data/lib/rigor/scope.rb +644 -37
- data/lib/rigor/sig_gen/bot_proposal_guard.rb +306 -0
- data/lib/rigor/sig_gen/classification.rb +18 -1
- data/lib/rigor/sig_gen/effect_annotation.rb +3 -3
- data/lib/rigor/sig_gen/generator.rb +588 -66
- 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 +37 -4
- data/lib/rigor/sig_gen/observation_collector.rb +14 -7
- 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/platform_guarded_files.rb +184 -0
- data/lib/rigor/sig_gen/renderer.rb +15 -9
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +62 -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 +238 -37
- data/lib/rigor/sig_gen.rb +1 -0
- data/lib/rigor/source/forwardable_delegators.rb +122 -0
- data/lib/rigor/source/node_children.rb +13 -0
- 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 +173 -21
- data/lib/rigor/type/constant.rb +4 -5
- data/lib/rigor/type/hash_shape.rb +21 -40
- data/lib/rigor/type/interface.rb +60 -0
- data/lib/rigor/type/union.rb +1 -1
- data/lib/rigor/type.rb +1 -0
- 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/type_of_memo/serving.rb +99 -0
- data/lib/rigor/type_of_memo/stats.rb +43 -0
- data/lib/rigor/type_of_memo.rb +337 -0
- data/lib/rigor/typing_state.rb +1094 -0
- 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 +3 -3
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +115 -36
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +3 -6
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +25 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +16 -6
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +4 -2
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +4 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +2 -1
- 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 +132 -33
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +20 -6
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +7 -4
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +54 -17
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +19 -5
- 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 +11 -14
- 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 +30 -9
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +55 -13
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_collector.rb +15 -21
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +1 -6
- 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 +8 -7
- 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-graphql/lib/rigor/plugin/graphql.rb +12 -2
- 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 +8 -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 +200 -4
- 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 +4 -3
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +2 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +5 -5
- 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 +210 -3
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +146 -7
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +2 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +52 -34
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +97 -24
- data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +2 -3
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_collector.rb +16 -16
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +4 -8
- 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/check_rules/rule_walk.rbs +6 -0
- data/sig/rigor/analysis/fact_store.rbs +2 -2
- data/sig/rigor/analysis/reachability/scan_cache.rbs +1 -1
- data/sig/rigor/cache/descriptor.rbs +14 -0
- 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/environment.rbs +4 -1
- data/sig/rigor/inference.rbs +4 -1
- data/sig/rigor/plugin/additional_initializer.rbs +0 -5
- data/sig/rigor/plugin/base.rbs +53 -18
- 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/interfaces.rbs +51 -0
- data/sig/rigor/plugin/io_boundary.rbs +3 -3
- data/sig/rigor/plugin/manifest.rbs +9 -9
- data/sig/rigor/plugin/node_context.rbs +21 -0
- data/sig/rigor/plugin/protocol_contract.rbs +3 -7
- data/sig/rigor/reflection.rbs +37 -0
- data/sig/rigor/scope.rbs +43 -4
- data/sig/rigor/trinary.rbs +1 -0
- data/sig/rigor/type.rbs +9 -3
- data/sig/rigor.rbs +3 -2
- data/skills/rigor-ask/SKILL.md +1 -0
- 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/01-detect.md +1 -0
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +17 -2
- data/skills/rigor-project-init/references/06-agent-contract.md +4 -4
- data/skills/rigor-rbs-contract/SKILL.md +147 -0
- data/skills/rigor-rbs-contract/references/01-finding-candidates.md +212 -0
- data/skills/rigor-rbs-contract/references/02-contract-rules.md +157 -0
- data/skills/rigor-rbs-contract/references/03-target-own-code.md +90 -0
- data/skills/rigor-rbs-contract/references/04-target-gem-rbs-collection.md +210 -0
- data/skills/rigor-rbs-contract/references/05-declaration-mutants.md +182 -0
- data/skills/rigor-rbs-setup/SKILL.md +3 -1
- data/skills/rigor-type-oracle/SKILL.md +26 -1
- data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +4 -3
- metadata +82 -3
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
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "typing_state"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
# ADR-127 WD3 — the memo audit. A memo hit is correct only when the key holds every input the value
|
|
7
|
+
# was computed from, and a key that misses one answers a caller with another caller's value (#2516, #2524). With the
|
|
8
|
+
# audit on, every hit in the callee floor's memos (`resolved_census`, the unrecorded hand-off memo, the successor
|
|
9
|
+
# store and `def_census`) recomputes the value and raises {Mismatch} when it differs from the stored one, so a key
|
|
10
|
+
# defect fails on the first hit instead of waiting for an order that exposes it.
|
|
11
|
+
#
|
|
12
|
+
# Like {BudgetTrace}, a switch for specs and CI (`RIGOR_MEMO_AUDIT=1` in the environment, read once at load, or
|
|
13
|
+
# {enable!}): off by default, a single boolean check when off. A recomputation
|
|
14
|
+
# runs with the audit suspended on its thread and the memos bypassed ({suspended?}), so it reads nothing stored.
|
|
15
|
+
#
|
|
16
|
+
# A mismatch raises, and is also kept on this thread's list until {drain} takes it (ADR-129 WD5, #2785): the
|
|
17
|
+
# typing path rescues `StandardError` in many places, and a raise one of them swallows would turn the defect into
|
|
18
|
+
# an ordinary fallback answer. The runner drains the list after each file and reports what it holds the way it
|
|
19
|
+
# reports a mismatch no rescue caught, and {raise_undrained!} fails the run on what a file never drained.
|
|
20
|
+
#
|
|
21
|
+
# **The key audit** (ADR-129 WD1) checks the other half at every miss: a Memo, or a Table a computation fills (the
|
|
22
|
+
# ADR-84 return memo), whose miss computation reads a Frame must key on the state id or on that Frame's value.
|
|
23
|
+
# {key_audit} runs a miss computation with its Frame reads noted ({TypingState::KeyAudit}) and compares them with
|
|
24
|
+
# the Frame terms the key read through {TypingState::State#key_frame} when {key} built it. A read Frame the key
|
|
25
|
+
# does not carry, and that neither the computation installed itself nor the site declared ({key_declaration}), is a
|
|
26
|
+
# {KeyMismatch}: kept on this thread's list like a raised mismatch, and not raised, since the computation's answer
|
|
27
|
+
# is correct for this caller.
|
|
28
|
+
module MemoAudit
|
|
29
|
+
# A memo hit whose stored value differs from a fresh computation.
|
|
30
|
+
class Mismatch < StandardError; end
|
|
31
|
+
|
|
32
|
+
# A memo miss whose computation read a Frame its key does not carry (ADR-129 WD1's key audit).
|
|
33
|
+
class KeyMismatch < Mismatch; end
|
|
34
|
+
|
|
35
|
+
# What a miss site declares beside its key terms (ADR-129 WD1), by Frame slot name:
|
|
36
|
+
# - `forced`: Frames the memo installs at a constant (or a value derived from its key) before the key is built,
|
|
37
|
+
# so the computation reads that form rather than the caller's;
|
|
38
|
+
# - `gaps`: `slot name => reason` naming the issue of a known key gap, read without a key term and reported as a
|
|
39
|
+
# named exception rather than a mismatch;
|
|
40
|
+
# - `carried`: `slot name => reason` for Frames another key term carries (a run-level flag, fixed while the run
|
|
41
|
+
# generation the memo's bucket keys on is), counted as key terms.
|
|
42
|
+
KeyDeclaration = Data.define(:forced, :gaps, :carried)
|
|
43
|
+
|
|
44
|
+
# ADR-129 WD1 — a Frame, installed around a recomputation. `@enabled` is a process switch, outside the typing
|
|
45
|
+
# state (WD5). G1′ witness: a recomputation types a callee body, and the memo hits inside it read the flag.
|
|
46
|
+
SUSPENDED = TypingState.frame(
|
|
47
|
+
:memo_audit_suspended, reason: "set around an audit recomputation, which bypasses every memo",
|
|
48
|
+
witness: "spec/fixtures/typing_state_witnesses/memo_audit_suspended.rb"
|
|
49
|
+
)
|
|
50
|
+
private_constant :SUSPENDED
|
|
51
|
+
|
|
52
|
+
# ADR-129 WD1 — a Resource: the mismatches raised on this thread and not drained yet. Typing never reads it; an
|
|
53
|
+
# append refuses every open capture, which only an audited run reaches.
|
|
54
|
+
MISMATCHES = TypingState.resource(
|
|
55
|
+
:memo_audit_mismatches,
|
|
56
|
+
run_scoped: true,
|
|
57
|
+
reason: "the audit's mismatches on this thread, appended where raised, drained per file, never read by typing"
|
|
58
|
+
)
|
|
59
|
+
private_constant :MISMATCHES
|
|
60
|
+
|
|
61
|
+
EMPTY = [].freeze
|
|
62
|
+
private_constant :EMPTY
|
|
63
|
+
|
|
64
|
+
# ADR-129 WD1 — the Frames every key audit names as exceptions rather than mismatches, with their reasons: the
|
|
65
|
+
# audit's own switch and the process switches whose installed recorders a computation may file into.
|
|
66
|
+
KEY_AUDIT_SWITCHES = {
|
|
67
|
+
memo_audit_suspended: "the audit's own switch: a nested hit's check reads it",
|
|
68
|
+
flow_tracer: "a process switch (WD5): the tracer is an output, and the type_of memo is shut under one (WD4)",
|
|
69
|
+
effects_collector: "a process switch (WD5): recording into its contents touches a Resource",
|
|
70
|
+
dependency_recorder: "a process switch (WD5): the recorder files ADR-46 dependency edges, which no type reads",
|
|
71
|
+
self_call_resolution_recorder: "a process switch (WD5): its records are a journaled Table, never read back"
|
|
72
|
+
}.freeze
|
|
73
|
+
|
|
74
|
+
# ADR-129 WD1 — a Resource: the named exceptions key audits on this thread met, `[label, slot name] => reason`.
|
|
75
|
+
# Typing never reads it.
|
|
76
|
+
KEY_EXCEPTIONS = TypingState.resource(
|
|
77
|
+
:memo_audit_key_exceptions,
|
|
78
|
+
run_scoped: true,
|
|
79
|
+
reason: "the key audit's named exceptions met on this thread, for specs and reports; never read by typing"
|
|
80
|
+
)
|
|
81
|
+
private_constant :KEY_EXCEPTIONS
|
|
82
|
+
|
|
83
|
+
NO_DECLARATION = KeyDeclaration.new(forced: EMPTY, gaps: {}.freeze, carried: {}.freeze)
|
|
84
|
+
private_constant :NO_DECLARATION
|
|
85
|
+
|
|
86
|
+
@enabled = %w[1 true].include?(ENV["RIGOR_MEMO_AUDIT"].to_s.downcase)
|
|
87
|
+
|
|
88
|
+
module_function
|
|
89
|
+
|
|
90
|
+
def enabled? = @enabled
|
|
91
|
+
|
|
92
|
+
# True while an audit recomputation runs on this thread. The callee floor's memos are then bypassed (read and
|
|
93
|
+
# write), so the recomputation reads no stored entry of another memo: a defect shared across a table (a stale
|
|
94
|
+
# `resolved_census(:edges)` entry) would otherwise make the recomputed value match the stored one.
|
|
95
|
+
def suspended? = TypingState.current[SUSPENDED]
|
|
96
|
+
|
|
97
|
+
def enable! = (@enabled = true)
|
|
98
|
+
|
|
99
|
+
def disable! = (@enabled = false)
|
|
100
|
+
|
|
101
|
+
# `stored`, after checking it against `yield` when the audit is on. `label` and the key parts (`key`, `key2`) name
|
|
102
|
+
# the memo entry in the failure; they are passed apart so an audit that is off allocates nothing.
|
|
103
|
+
def check(label, stored, key, key2 = nil, &)
|
|
104
|
+
return stored unless @enabled && !TypingState.current[SUSPENDED]
|
|
105
|
+
|
|
106
|
+
fresh = suspended(&)
|
|
107
|
+
return stored if fresh == stored
|
|
108
|
+
|
|
109
|
+
mismatch = Mismatch.new(
|
|
110
|
+
"#{label} memo hit differs from a fresh computation for #{[key, key2].inspect[0, 120]}: " \
|
|
111
|
+
"stored #{stored.inspect[0, 200]}, fresh #{fresh.inspect[0, 200]}"
|
|
112
|
+
)
|
|
113
|
+
state = TypingState.current
|
|
114
|
+
state.append(MISMATCHES, mismatch)
|
|
115
|
+
raise mismatch
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# ADR-129 G1′ — a `type_of` begins on this thread, for the matching {leave_type_of}; the caller guards on
|
|
119
|
+
# {enabled?}, so an audit that is off costs one boolean check. Fails (as a {Mismatch} on the list, not raised) on an
|
|
120
|
+
# installed `typing_unreachable` Frame ({TypingState::FrameAudit}).
|
|
121
|
+
def enter_type_of
|
|
122
|
+
state = TypingState.current
|
|
123
|
+
state.extend(TypingState::FrameAudit) unless state.is_a?(TypingState::FrameAudit)
|
|
124
|
+
state.enter_type_of
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# ADR-129 G1′ — the `type_of` memo's shadow reached its table on `state`; the caller guards on {enabled?}. Fails (as
|
|
128
|
+
# a {Mismatch} on the list, not raised) on an installed `memo_shut_while_installed` Frame.
|
|
129
|
+
def note_memo_lookup(state)
|
|
130
|
+
state.extend(TypingState::FrameAudit) unless state.is_a?(TypingState::FrameAudit)
|
|
131
|
+
state.note_memo_lookup
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# ADR-129 G1′ — the `type_of` {enter_type_of} tracked ends.
|
|
135
|
+
def leave_type_of
|
|
136
|
+
TypingState.current.leave_type_of
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Keeps one {Mismatch} per message on the list until it is drained: a Frame declaration that does not hold is
|
|
140
|
+
# reported at every `type_of` it meets.
|
|
141
|
+
def report_frame_declaration(state, message)
|
|
142
|
+
pending = state.read(MISMATCHES)
|
|
143
|
+
return if pending&.any? { |mismatch| mismatch.message == message }
|
|
144
|
+
|
|
145
|
+
mismatch = Mismatch.new(message)
|
|
146
|
+
mismatch.set_backtrace(caller.drop_while { |frame| frame.include?("/typing_state.rb:") })
|
|
147
|
+
state.append(MISMATCHES, mismatch)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# The mismatches raised on this thread since the last drain, oldest first, and an empty list for the next file.
|
|
151
|
+
# Each has been raised, so it carries the backtrace a crash row is built from.
|
|
152
|
+
def drain
|
|
153
|
+
state = TypingState.current
|
|
154
|
+
mismatches = state.read(MISMATCHES)
|
|
155
|
+
return EMPTY if mismatches.nil? || mismatches.empty?
|
|
156
|
+
|
|
157
|
+
state.write(MISMATCHES, nil)
|
|
158
|
+
mismatches
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# ADR-129 WD1 — checks a `carried:` claim at the install of a run-level flag (`flag`, ADR-130's erasure or
|
|
162
|
+
# dispatch-off frame). The return memo counts the flag as carried by its bucket's run-generation key, which holds
|
|
163
|
+
# only while the flag is installed around a whole run: with the audit on, an install that finds the return memo
|
|
164
|
+
# holding entries (a run in progress, or one that was not released) raises instead of silently mixing answers
|
|
165
|
+
# computed under both values.
|
|
166
|
+
def check_run_level_install(flag)
|
|
167
|
+
return unless @enabled
|
|
168
|
+
|
|
169
|
+
held = TypingState.current[TypingState.slot(:user_method_return_memo).index]
|
|
170
|
+
return if held.nil? || held[1].empty?
|
|
171
|
+
|
|
172
|
+
raise Mismatch, "#{flag} installed while the return memo holds entries: the memo's key carries it only as a " \
|
|
173
|
+
"flag installed around a whole run (ADR-129 WD1 key audit)"
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Raises the first mismatch no file drained (one raised outside every file's analysis), so a run that ends with
|
|
177
|
+
# one fails instead of releasing it with the run's other slots.
|
|
178
|
+
def raise_undrained!
|
|
179
|
+
return unless @enabled
|
|
180
|
+
|
|
181
|
+
mismatches = drain
|
|
182
|
+
raise mismatches.first unless mismatches.empty?
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def suspended(&)
|
|
186
|
+
TypingState.current.with(SUSPENDED, true, &)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# A frozen {KeyDeclaration} for a miss site, built once into a constant so an audit that is off allocates nothing.
|
|
190
|
+
def key_declaration(forced: EMPTY, gaps: {}, carried: {})
|
|
191
|
+
KeyDeclaration.new(forced: forced.map(&:to_sym).freeze, gaps: gaps.transform_keys(&:to_sym).freeze,
|
|
192
|
+
carried: carried.transform_keys(&:to_sym).freeze)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# The key the block builds. When the audit is on, the Frames the block reads through
|
|
196
|
+
# {TypingState::State#key_frame} are remembered as the key's terms for the {key_audit} of the miss that follows.
|
|
197
|
+
def key(&)
|
|
198
|
+
return yield unless @enabled
|
|
199
|
+
|
|
200
|
+
auditing_state.collect_key_terms(&)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# The block's value: a memo's miss computation, run under ADR-129 WD1's Memo rule
|
|
204
|
+
# ({TypingState::State#memo_miss}: a miss that writes a Table while a capture is open touches), keyed by `key`
|
|
205
|
+
# ({key}, or nil for a key that reads no Frame) and declared by `declaration` ({key_declaration}). When the audit
|
|
206
|
+
# is on, a Frame the computation reads that the key does not carry is a {KeyMismatch} on this thread's list, named
|
|
207
|
+
# by `label` (the slot's `audit:` or `computed:` label), unless it is internal, forced, carried, a declared gap or
|
|
208
|
+
# a {KEY_AUDIT_SWITCHES} switch.
|
|
209
|
+
#
|
|
210
|
+
# A computation that raises or throws is not judged, and its audit is closed on the way out. A computation that
|
|
211
|
+
# returns is judged whether or not its memo then stores it (the return memo's WD3 bracket may refuse it), which
|
|
212
|
+
# only reports more. An audit recomputation ({suspended?}) is not judged either, but it still opens the site's
|
|
213
|
+
# declaration, so a gap it declares reaches the audits around it as it does on a miss: the verdict of an enclosing
|
|
214
|
+
# miss does not depend on whether this memo hit.
|
|
215
|
+
def key_audit(label, declaration = nil, key = nil, &)
|
|
216
|
+
return TypingState.current.memo_miss(&) unless @enabled
|
|
217
|
+
|
|
218
|
+
state = auditing_state
|
|
219
|
+
declaration ||= NO_DECLARATION
|
|
220
|
+
judged = !state[SUSPENDED]
|
|
221
|
+
terms = state.key_terms_for(key) if judged
|
|
222
|
+
state.open_key_audit(label, declaration.gaps)
|
|
223
|
+
begin
|
|
224
|
+
value = state.memo_miss(&)
|
|
225
|
+
ensure
|
|
226
|
+
reads = state.close_key_audit
|
|
227
|
+
end
|
|
228
|
+
judge_key_audit(state, label, declaration, terms, reads) if judged
|
|
229
|
+
value
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# The named exceptions key audits on this thread have met since the run began, `[label, slot name] => reason`.
|
|
233
|
+
def key_exceptions
|
|
234
|
+
TypingState.current.read(KEY_EXCEPTIONS) || {}
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def auditing_state
|
|
238
|
+
state = TypingState.current
|
|
239
|
+
state.extend(TypingState::KeyAudit) unless state.is_a?(TypingState::KeyAudit)
|
|
240
|
+
state
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def judge_key_audit(state, label, declaration, terms, reads)
|
|
244
|
+
return if reads.empty? || terms.include?(TypingState::KeyAudit::STATE_ID)
|
|
245
|
+
|
|
246
|
+
reads.each do |slot, via|
|
|
247
|
+
name = TypingState.slots[slot].name
|
|
248
|
+
next note_key_exception(state, label, name, "through the #{via[0]} miss: #{via[1]}") unless via == true
|
|
249
|
+
next if terms.include?(slot) || declaration.forced.include?(name) || declaration.carried.key?(name)
|
|
250
|
+
|
|
251
|
+
reason = key_exception_reason(declaration, name)
|
|
252
|
+
reason ? note_key_exception(state, label, name, reason) : report_key_mismatch(state, label, name)
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# Why a miss may read the Frame `name` without a key term, or nil: a gap its site declares, or a switch.
|
|
257
|
+
def key_exception_reason(declaration, name) = declaration.gaps[name] || KEY_AUDIT_SWITCHES[name]
|
|
258
|
+
|
|
259
|
+
def note_key_exception(state, label, name, reason)
|
|
260
|
+
seen = state.acquire(KEY_EXCEPTIONS) { {} }
|
|
261
|
+
seen[[label, name]] ||= reason
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
# Keeps one {KeyMismatch} per message on the list until it is drained: a key defect repeats at every miss.
|
|
265
|
+
def report_key_mismatch(state, label, name)
|
|
266
|
+
message = "key audit: the #{label} miss read the Frame #{name}, which its key does not carry"
|
|
267
|
+
pending = state.read(MISMATCHES)
|
|
268
|
+
return if pending&.any? { |mismatch| mismatch.message == message }
|
|
269
|
+
|
|
270
|
+
mismatch = KeyMismatch.new(message)
|
|
271
|
+
mismatch.set_backtrace(caller.drop_while { |frame| frame.include?("/memo_audit.rb:") })
|
|
272
|
+
state.append(MISMATCHES, mismatch)
|
|
273
|
+
end
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# The engine reaches the audit as `Inference::MemoAudit`; it lives at `lib/rigor/memo_audit.rb` so the value layer
|
|
277
|
+
# (`Reflection`, `Scope`) can load it without loading an `inference/` file (ADR-104's warm effects probe budget), as
|
|
278
|
+
# `TypingState` does (#2796).
|
|
279
|
+
module Inference
|
|
280
|
+
MemoAudit = Rigor::MemoAudit
|
|
281
|
+
end
|
|
282
|
+
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
|
|
data/lib/rigor/plugin/base.rb
CHANGED
|
@@ -945,6 +945,14 @@ module Rigor
|
|
|
945
945
|
@io_boundary ||= services.io_boundary_for(manifest.id)
|
|
946
946
|
end
|
|
947
947
|
|
|
948
|
+
# The boundary {#io_boundary} has built, or nil when this plugin never asked for one, without building it: a
|
|
949
|
+
# plugin that never built one read nothing through it. Engine-internal ({Analysis::RunDisclosureCredit} sends it,
|
|
950
|
+
# being outside the class), so it is private and part of no plugin-facing contract.
|
|
951
|
+
#
|
|
952
|
+
# @api private
|
|
953
|
+
def built_io_boundary = @io_boundary
|
|
954
|
+
private :built_io_boundary
|
|
955
|
+
|
|
948
956
|
# ADR-7 § "Slice 6-A" / ADR-60 WD3 — returns a callable that performs a `Cache::Store#fetch_or_validate`
|
|
949
957
|
# round-trip for the named producer (the ADR-45 record-and-validate path). The entry is KEYED on the
|
|
950
958
|
# stable identity inputs — the plugin's `PluginEntry` template (id, version, config_hash) composed with
|
|
@@ -1064,14 +1072,19 @@ module Rigor
|
|
|
1064
1072
|
# The gate for one `dynamic_return` rule. Method-name gate first — a Symbol-array probe vs the receiver
|
|
1065
1073
|
# ancestry resolution below (ADR-52 WD1); both are pure predicates, so order only affects cost. A
|
|
1066
1074
|
# receiver-less rule (ADR-52 WD2) skips the ancestry check entirely and fires on the method name alone.
|
|
1075
|
+
# The engine also offers the nameless nodes of #2717 (the `&:sym` BlockArgumentNode, the compound
|
|
1076
|
+
# write nodes) to a plugin `build_name_gates` left ungated; only `CallNode` answers `name`, so a
|
|
1077
|
+
# name-gated rule declines them (`include?(nil)` is false) rather than raising past the later rules
|
|
1078
|
+
# (#2764).
|
|
1067
1079
|
def dynamic_return_rule_applies?(rule, call_node, receiver_type, environment, scope)
|
|
1068
|
-
|
|
1080
|
+
name = call_node.respond_to?(:name) ? call_node.name : nil
|
|
1081
|
+
return false if rule[:methods] && !resolved_dynamic_return_methods(rule).include?(name)
|
|
1069
1082
|
|
|
1070
1083
|
if rule[:file_methods]
|
|
1071
1084
|
# The path is read here, not in `dynamic_return_type`, so a spec-double scope without
|
|
1072
1085
|
# `source_path` only affects `file_methods:` rules (other gate forms never touch it).
|
|
1073
1086
|
path = scope.respond_to?(:source_path) ? scope.source_path : nil
|
|
1074
|
-
return false unless resolved_dynamic_return_file_methods(rule, path).include?(
|
|
1087
|
+
return false unless resolved_dynamic_return_file_methods(rule, path).include?(name)
|
|
1075
1088
|
end
|
|
1076
1089
|
|
|
1077
1090
|
entries = resolved_dynamic_return_receiver_entries(rule)
|
|
@@ -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
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "digest"
|
|
4
|
+
require_relative "../typing_state"
|
|
4
5
|
|
|
5
6
|
require_relative "access_denied_error"
|
|
6
7
|
require_relative "../rooted_glob"
|
|
@@ -17,7 +18,7 @@ module Rigor
|
|
|
17
18
|
# scope and feed the cache descriptor; contributions built on top of out-of-scope reads will not
|
|
18
19
|
# invalidate correctly.
|
|
19
20
|
#
|
|
20
|
-
#
|
|
21
|
+
# The surface:
|
|
21
22
|
#
|
|
22
23
|
# - `#read_file(path)` — validates against the policy, returns the file's contents, and adds a
|
|
23
24
|
# digest-keyed {Cache::Descriptor::FileEntry} to the boundary's accumulated descriptor. A read that
|
|
@@ -52,11 +53,20 @@ module Rigor
|
|
|
52
53
|
# descriptor and any enclosing producer's descriptor then carry the served value's inputs.
|
|
53
54
|
class IoBoundary
|
|
54
55
|
URL_TIMEOUT_SECONDS = 10
|
|
55
|
-
URL_MAX_BYTES =
|
|
56
|
+
URL_MAX_BYTES = 10_485_760 # 10 MiB
|
|
56
57
|
|
|
57
58
|
ATTRIBUTION_KEY = :rigor_io_boundary_attribution
|
|
58
59
|
private_constant :ATTRIBUTION_KEY
|
|
59
60
|
|
|
61
|
+
# ADR-129 WD3 / #2814 — the attribution sink is a Resource: a `type_of` memo hit that served an answer without
|
|
62
|
+
# re-running the plugin read would not append what the miss appended, so every {.attribute} refuses each open
|
|
63
|
+
# capture. Its storage stays the thread variable above (ADR-129 WD5 shape 2); this slot declares its kind.
|
|
64
|
+
SINK = TypingState.resource(
|
|
65
|
+
:io_boundary_attribution,
|
|
66
|
+
reason: "every plugin boundary read appends to the attributing sink, which a memo hit would not replay"
|
|
67
|
+
)
|
|
68
|
+
private_constant :SINK
|
|
69
|
+
|
|
60
70
|
# ADR-45 WD2 (#1507) — runs the block with `sink` (an Array) receiving every file and listing row ANY
|
|
61
71
|
# boundary records on this thread meanwhile, in addition to the boundary's own table, and returns the
|
|
62
72
|
# block's value. `Analysis::Runner` wraps each file's analysis in one when it records dependencies, so the
|
|
@@ -67,16 +77,28 @@ module Rigor
|
|
|
67
77
|
# lands in the sink. The boundary's own table is unchanged by it, including its first-existence-row-stands
|
|
68
78
|
# rule: the sink records what each read observed. Only reads are credited: a value a plugin memoised reaches
|
|
69
79
|
# later files without one, so the first file to trigger the read is the only one credited with it.
|
|
80
|
+
#
|
|
81
|
+
# The installing fiber's typing state rides with the sink, so a read from inside an `Enumerator` refuses the
|
|
82
|
+
# captures open where the analysis runs, not only those of the `Enumerator`'s own fiber ({.attribute}).
|
|
70
83
|
def self.attributing(sink)
|
|
71
84
|
previous = Thread.current.thread_variable_get(ATTRIBUTION_KEY)
|
|
72
|
-
Thread.current.thread_variable_set(ATTRIBUTION_KEY, sink)
|
|
85
|
+
Thread.current.thread_variable_set(ATTRIBUTION_KEY, [sink, TypingState.current].freeze)
|
|
73
86
|
yield
|
|
74
87
|
ensure
|
|
75
88
|
Thread.current.thread_variable_set(ATTRIBUTION_KEY, previous)
|
|
76
89
|
end
|
|
77
90
|
|
|
91
|
+
# Records `entry` in the attributing sink, if one is installed, and touches {SINK} on the calling fiber's typing
|
|
92
|
+
# state and on the installing fiber's (ADR-129 WD3, #2814).
|
|
78
93
|
def self.attribute(entry)
|
|
79
|
-
|
|
94
|
+
state = TypingState.current
|
|
95
|
+
state.touch(SINK)
|
|
96
|
+
sink, owner = Thread.current.thread_variable_get(ATTRIBUTION_KEY)
|
|
97
|
+
return nil if sink.nil?
|
|
98
|
+
|
|
99
|
+
owner.touch(SINK)
|
|
100
|
+
sink.push(entry)
|
|
101
|
+
nil
|
|
80
102
|
end
|
|
81
103
|
|
|
82
104
|
attr_reader :policy, :plugin_id
|
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
|
|