rigortype 0.3.9 → 0.4.1
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 +5 -4
- data/data/core_overlay/enumerable.rbs +51 -0
- data/data/core_overlay/enumerator.rbs +84 -0
- data/data/core_overlay/hash_rbs3.rbs +41 -0
- data/data/core_overlay/process.rbs +40 -0
- data/data/core_overlay/string_io.rbs +33 -0
- data/data/effects/core.yml +3 -3
- data/data/gem_overlay/activesupport/core_ext.rbs +127 -4
- data/docs/handbook/03-narrowing.md +95 -10
- data/docs/handbook/04-tuples-and-shapes.md +8 -6
- data/docs/handbook/07-rbs-and-extended.md +16 -11
- data/docs/handbook/08-understanding-errors.md +3 -2
- data/docs/handbook/10-sorbet.md +9 -10
- data/docs/handbook/11-sig-gen.md +454 -13
- data/docs/install.md +87 -15
- data/docs/manual/02-cli-reference.md +65 -4
- data/docs/manual/03-configuration.md +12 -0
- data/docs/manual/04-diagnostics.md +4 -0
- data/docs/manual/07-plugins.md +1 -1
- data/docs/manual/08-skills.md +4 -3
- data/docs/manual/10-mcp-server.md +3 -2
- data/docs/manual/12-caching.md +43 -4
- data/docs/manual/14-rails-quickstart.md +19 -11
- data/docs/manual/16-rbs-extended-annotations.md +40 -7
- data/docs/manual/19-effect-labels.md +10 -0
- data/docs/manual/plugins/README.md +7 -0
- data/docs/manual/plugins/rigor-actioncable.md +8 -1
- data/docs/manual/plugins/rigor-actionmailer.md +7 -0
- data/docs/manual/plugins/rigor-actionpack.md +243 -0
- data/docs/manual/plugins/rigor-active-model-serializers.md +153 -0
- data/docs/manual/plugins/rigor-activejob.md +7 -0
- data/docs/manual/plugins/rigor-activerecord.md +110 -4
- data/docs/manual/plugins/rigor-activestorage.md +8 -1
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +11 -5
- data/docs/manual/plugins/rigor-grape.md +106 -0
- data/docs/manual/plugins/rigor-graphql.md +23 -2
- data/docs/manual/plugins/rigor-pundit.md +8 -1
- data/docs/manual/plugins/rigor-rails-i18n.md +10 -5
- data/docs/manual/plugins/rigor-rails-routes.md +7 -0
- data/docs/manual/plugins/rigor-rbs-inline.md +212 -25
- data/docs/manual/plugins/rigor-sidekiq.md +8 -1
- data/docs/manual/plugins/rigor-sorbet.md +21 -5
- data/exe/rigor +19 -4
- data/lib/rigor/analysis/baseline.rb +1 -1
- data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +167 -2
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +189 -0
- data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -3
- data/lib/rigor/analysis/check_rules/rule_ids.rb +20 -5
- data/lib/rigor/analysis/check_rules/rule_walk.rb +2 -1
- data/lib/rigor/analysis/check_rules/source_arity.rb +427 -0
- data/lib/rigor/analysis/check_rules/special_global_setters.rb +146 -0
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +36 -2
- data/lib/rigor/analysis/check_rules.rb +638 -136
- data/lib/rigor/analysis/dependency_recorder.rb +222 -17
- data/lib/rigor/analysis/fact_store.rb +9 -0
- data/lib/rigor/analysis/incremental.rb +26 -0
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +266 -0
- data/lib/rigor/analysis/incremental_run_slot.rb +171 -0
- data/lib/rigor/analysis/incremental_session.rb +309 -53
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +24 -13
- data/lib/rigor/analysis/project_scan.rb +11 -1
- data/lib/rigor/analysis/reachability/plugin_roots.rb +0 -1
- data/lib/rigor/analysis/reachability/scan_cache.rb +3 -1
- data/lib/rigor/analysis/rule_catalog.rb +127 -0
- data/lib/rigor/analysis/run_cache_key.rb +46 -18
- data/lib/rigor/analysis/run_stats.rb +2 -2
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +168 -12
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +9 -4
- data/lib/rigor/analysis/runner/pool_coordinator.rb +109 -17
- data/lib/rigor/analysis/runner/project_pre_passes.rb +44 -17
- data/lib/rigor/analysis/runner.rb +603 -54
- data/lib/rigor/analysis/shadow_harness.rb +27 -0
- data/lib/rigor/analysis/source_rbs_gate.rb +157 -0
- data/lib/rigor/analysis/template_unit_collector.rb +303 -0
- data/lib/rigor/analysis/template_unit_paths.rb +93 -0
- data/lib/rigor/analysis/template_unit_positions.rb +293 -0
- data/lib/rigor/analysis/template_units.rb +409 -0
- data/lib/rigor/analysis/worker_session.rb +57 -13
- data/lib/rigor/bleeding_edge.rb +0 -2
- data/lib/rigor/builtins/hkt_builtins.rb +1 -0
- data/lib/rigor/builtins/imported_refinements.rb +4 -0
- data/lib/rigor/builtins/regex_refinement.rb +17 -9
- data/lib/rigor/builtins/static_return_refinements.rb +2 -0
- data/lib/rigor/cache/descriptor.rb +45 -1
- data/lib/rigor/cache/engine_source.rb +29 -1
- data/lib/rigor/cache/file_digest.rb +28 -0
- data/lib/rigor/cache/incremental_snapshot.rb +102 -8
- data/lib/rigor/cache/rbs_cache_producer.rb +4 -0
- data/lib/rigor/cache/rbs_descriptor.rb +37 -7
- data/lib/rigor/cache/store.rb +63 -4
- data/lib/rigor/cache/value_digest.rb +325 -0
- data/lib/rigor/ci_detector.rb +1 -0
- data/lib/rigor/cli/check_command.rb +83 -13
- data/lib/rigor/cli/doc_links.rb +1 -1
- data/lib/rigor/cli/docs_command.rb +4 -4
- data/lib/rigor/cli/doctor_command.rb +1 -1
- data/lib/rigor/cli/effects_report.rb +2 -2
- data/lib/rigor/cli/plugin_command.rb +3 -3
- data/lib/rigor/cli/prism_colorizer.rb +0 -1
- data/lib/rigor/cli/sig_gen_command.rb +210 -29
- data/lib/rigor/cli/skill_command.rb +1 -1
- data/lib/rigor/cli/skill_describe.rb +0 -1
- data/lib/rigor/cli/type_of_command.rb +19 -5
- data/lib/rigor/cli/type_of_renderer.rb +20 -9
- data/lib/rigor/cli/type_of_template_probe.rb +189 -0
- data/lib/rigor/cli/unused_command.rb +3 -3
- data/lib/rigor/cli.rb +21 -3
- data/lib/rigor/config_audit.rb +3 -2
- data/lib/rigor/config_path.rb +29 -0
- data/lib/rigor/configuration/severity_profile.rb +24 -3
- data/lib/rigor/configuration.rb +71 -7
- data/lib/rigor/effects/ancestry_recorder.rb +191 -0
- data/lib/rigor/effects/attribution.rb +11 -2
- data/lib/rigor/effects/callee_rule.rb +368 -0
- data/lib/rigor/effects/catalog.rb +7 -4
- data/lib/rigor/effects/collector.rb +11 -5
- data/lib/rigor/effects/config_envelopes.rb +9 -2
- data/lib/rigor/effects/definition_context.rb +179 -0
- data/lib/rigor/effects/effect_table.rb +11 -3
- data/lib/rigor/effects/envelope_check.rb +1 -1
- data/lib/rigor/effects/envelope_index.rb +15 -0
- data/lib/rigor/effects/file_collection.rb +59 -5
- data/lib/rigor/effects/framework_units.rb +1 -1
- data/lib/rigor/effects/identity.rb +16 -0
- data/lib/rigor/effects/local_ownership.rb +38 -12
- data/lib/rigor/effects/method_key.rb +21 -0
- data/lib/rigor/effects/mutation_classifier.rb +23 -12
- data/lib/rigor/effects/plugin_facts.rb +49 -37
- data/lib/rigor/effects/propagator.rb +295 -16
- data/lib/rigor/effects/registry.rb +1 -1
- data/lib/rigor/effects/scanner.rb +121 -72
- data/lib/rigor/effects/signature_sources.rb +1 -1
- data/lib/rigor/effects/snapshot.rb +2 -1
- data/lib/rigor/effects/summary.rb +27 -4
- data/lib/rigor/effects/unit_scan.rb +385 -30
- data/lib/rigor/effects/visibility.rb +101 -0
- data/lib/rigor/environment/bundle_sig_discovery.rb +5 -3
- data/lib/rigor/environment/lockfile_resolver.rb +27 -1
- data/lib/rigor/environment/member_consistency/comparator.rb +708 -0
- data/lib/rigor/environment/member_consistency.rb +298 -0
- data/lib/rigor/environment/rbs_collection_discovery.rb +10 -1
- data/lib/rigor/environment/rbs_loader.rb +400 -134
- data/lib/rigor/environment/source_rbs_synthesis.rb +147 -0
- data/lib/rigor/environment.rb +104 -96
- data/lib/rigor/hashing/xxh3.rb +264 -0
- data/lib/rigor/inference/acceptance.rb +139 -9
- data/lib/rigor/inference/block_auto_splat.rb +216 -0
- data/lib/rigor/inference/block_call_timing.rb +338 -0
- data/lib/rigor/inference/block_parameter_binder.rb +88 -26
- data/lib/rigor/inference/block_repetition.rb +71 -0
- data/lib/rigor/inference/body_fixpoint.rb +2 -1
- data/lib/rigor/inference/budget_trace.rb +2 -1
- data/lib/rigor/inference/builtins/method_catalog.rb +2 -1
- data/lib/rigor/inference/builtins/string_catalog.rb +1 -1
- data/lib/rigor/inference/captured_locals.rb +393 -15
- data/lib/rigor/inference/closure_escape_analyzer.rb +157 -13
- data/lib/rigor/inference/content_join.rb +200 -27
- data/lib/rigor/inference/declaration_walk/context.rb +195 -0
- data/lib/rigor/inference/declaration_walk/errors.rb +18 -0
- data/lib/rigor/inference/declaration_walk/shadow.rb +147 -0
- data/lib/rigor/inference/declaration_walk/traversal.rb +441 -0
- data/lib/rigor/inference/declaration_walk.rb +7 -0
- data/lib/rigor/inference/def_node_resolver.rb +3 -2
- data/lib/rigor/inference/def_return_typer.rb +11 -7
- data/lib/rigor/inference/define_method_block_self.rb +64 -0
- data/lib/rigor/inference/definer_resolution.rb +266 -0
- data/lib/rigor/inference/element_read_widening.rb +22 -10
- data/lib/rigor/inference/error_info.rb +196 -0
- data/lib/rigor/inference/expression_typer.rb +1665 -538
- data/lib/rigor/inference/external_ancestor_resolution.rb +264 -0
- data/lib/rigor/inference/fresh_frame_blocks.rb +127 -0
- data/lib/rigor/inference/global_write_census.rb +239 -0
- data/lib/rigor/inference/guard_rebinding.rb +488 -0
- data/lib/rigor/inference/hash_lookup_mutation.rb +88 -0
- data/lib/rigor/inference/index_write_widening.rb +16 -3
- data/lib/rigor/inference/indexed_narrowing.rb +61 -9
- data/lib/rigor/inference/jump_targets.rb +82 -0
- data/lib/rigor/inference/last_line/implicit_self.rb +210 -0
- data/lib/rigor/inference/last_line/self_evidence.rb +329 -0
- data/lib/rigor/inference/last_line.rb +340 -0
- data/lib/rigor/inference/last_status.rb +144 -0
- data/lib/rigor/inference/macro_block_self_type.rb +179 -17
- data/lib/rigor/inference/match_rebinding/calls.rb +281 -0
- data/lib/rigor/inference/match_rebinding/frame.rb +148 -0
- data/lib/rigor/inference/match_rebinding/operands.rb +236 -0
- data/lib/rigor/inference/match_rebinding/self_calls.rb +83 -0
- data/lib/rigor/inference/match_rebinding.rb +392 -0
- data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
- data/lib/rigor/inference/method_dispatcher/block_folding.rb +85 -24
- data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +147 -0
- data/lib/rigor/inference/method_dispatcher/hash_transform_keys_folding.rb +191 -0
- data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +4 -0
- data/lib/rigor/inference/method_dispatcher/match_data_folding.rb +159 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +113 -73
- data/lib/rigor/inference/method_dispatcher/process_folding.rb +56 -0
- data/lib/rigor/inference/method_dispatcher/proven_overload.rb +72 -0
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +901 -38
- data/lib/rigor/inference/method_dispatcher/self_substitute.rb +142 -0
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +128 -53
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +1 -1
- data/lib/rigor/inference/method_dispatcher.rb +214 -13
- data/lib/rigor/inference/method_parameter_binder.rb +8 -3
- data/lib/rigor/inference/module_function_state.rb +213 -0
- data/lib/rigor/inference/multi_target_binder.rb +345 -52
- data/lib/rigor/inference/mutation_rejoin.rb +4 -1
- data/lib/rigor/inference/mutation_widening.rb +70 -48
- data/lib/rigor/inference/narrowing.rb +674 -124
- data/lib/rigor/inference/operand_effects.rb +167 -0
- data/lib/rigor/inference/operand_walk.rb +88 -0
- data/lib/rigor/inference/optimistic_origin.rb +152 -9
- data/lib/rigor/inference/parameter_inference_collector.rb +13 -11
- data/lib/rigor/inference/project_method_ownership.rb +136 -0
- data/lib/rigor/inference/project_patched_methods.rb +7 -2
- data/lib/rigor/inference/project_patched_scanner.rb +7 -3
- data/lib/rigor/inference/receiver_alias.rb +90 -1
- data/lib/rigor/inference/receiver_blind_block.rb +219 -0
- data/lib/rigor/inference/refinement_mutation.rb +15 -11
- data/lib/rigor/inference/repeated_or_writes.rb +463 -0
- data/lib/rigor/inference/return_barrier.rb +54 -0
- data/lib/rigor/inference/rewrite_mutation.rb +120 -0
- data/lib/rigor/inference/scope_indexer/certainty.rb +176 -0
- data/lib/rigor/inference/scope_indexer/class_cvars_collector.rb +35 -0
- data/lib/rigor/inference/scope_indexer/def_nestings_collector.rb +47 -0
- data/lib/rigor/inference/scope_indexer/layered_def_nestings.rb +36 -0
- data/lib/rigor/inference/scope_indexer/member_layouts_collector.rb +61 -0
- data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +130 -0
- data/lib/rigor/inference/scope_indexer/superclasses_collector.rb +64 -0
- data/lib/rigor/inference/scope_indexer.rb +5913 -701
- data/lib/rigor/inference/singleton_hook_decline.rb +253 -0
- data/lib/rigor/inference/statement_evaluator.rb +3249 -497
- data/lib/rigor/inference/stored_block_call.rb +54 -0
- data/lib/rigor/inference/string_mutation.rb +44 -7
- data/lib/rigor/inference/unknown_store_widening.rb +200 -0
- data/lib/rigor/inference/unthreaded_rebinds.rb +282 -0
- data/lib/rigor/language_server/debouncer.rb +0 -1
- data/lib/rigor/language_server/diagnostic_publisher.rb +3 -2
- data/lib/rigor/language_server/hover_renderer.rb +3 -3
- data/lib/rigor/language_server/project_context.rb +5 -3
- data/lib/rigor/mcp/server.rb +2 -1
- data/lib/rigor/plugin/base.rb +196 -13
- data/lib/rigor/plugin/box_probe.rb +91 -0
- data/lib/rigor/plugin/bundled_catalog.rb +1 -1
- data/lib/rigor/plugin/effect_attribution.rb +58 -4
- data/lib/rigor/plugin/io_boundary.rb +82 -4
- data/lib/rigor/plugin/isolation.rb +1 -1
- data/lib/rigor/plugin/loader.rb +2 -1
- data/lib/rigor/plugin/macro/block_as_method.rb +45 -6
- data/lib/rigor/plugin/manifest.rb +71 -10
- data/lib/rigor/plugin/registry.rb +35 -1
- data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +9 -3
- data/lib/rigor/plugin/template_unit.rb +196 -0
- data/lib/rigor/plugin/trust_policy.rb +3 -3
- data/lib/rigor/plugin.rb +1 -0
- data/lib/rigor/protection/discovery_seed.rb +20 -9
- data/lib/rigor/protection/kill_signature.rb +0 -1
- data/lib/rigor/protection/mutation_cache.rb +4 -5
- data/lib/rigor/rbs_extended.rb +27 -0
- data/lib/rigor/reflection/constant_ancestors.rb +146 -0
- data/lib/rigor/reflection/constant_path.rb +41 -13
- data/lib/rigor/reflection.rb +73 -107
- data/lib/rigor/scope/discovery_index.rb +351 -2
- data/lib/rigor/scope/resolution_chain/relevance.rb +165 -0
- data/lib/rigor/scope/resolution_chain.rb +884 -0
- data/lib/rigor/scope.rb +981 -198
- data/lib/rigor/sig_gen/alias_index.rb +289 -0
- data/lib/rigor/sig_gen/classification.rb +22 -5
- data/lib/rigor/sig_gen/declaration_equivalence.rb +127 -0
- data/lib/rigor/sig_gen/effect_annotation.rb +202 -0
- data/lib/rigor/sig_gen/generator.rb +580 -47
- data/lib/rigor/sig_gen/inline_declarations.rb +184 -0
- data/lib/rigor/sig_gen/method_candidate.rb +47 -3
- data/lib/rigor/sig_gen/observation_collector.rb +1 -1
- data/lib/rigor/sig_gen/renderer.rb +124 -9
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +44 -1
- data/lib/rigor/sig_gen/write_result.rb +19 -3
- data/lib/rigor/sig_gen/writer.rb +166 -35
- data/lib/rigor/sig_gen.rb +3 -0
- data/lib/rigor/signature_path_audit.rb +1 -1
- data/lib/rigor/source/node_walker.rb +0 -3
- data/lib/rigor/source/parameter_envelope.rb +72 -0
- data/lib/rigor/source.rb +1 -0
- data/lib/rigor/type/combinator.rb +112 -12
- data/lib/rigor/type/difference.rb +1 -0
- data/lib/rigor/type/hash_shape.rb +1 -1
- data/lib/rigor/type/refined.rb +1 -0
- data/lib/rigor/version.rb +1 -1
- data/lib/rigor.rb +1 -0
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/effects.rb +1 -0
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +9 -5
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +13 -4
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +0 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +96 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/effects.rb +65 -8
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +270 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/render_locals.rb +402 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +370 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_units.rb +132 -0
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +239 -4
- data/plugins/rigor-actionpack/sig/action_view.rbs +43 -0
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +220 -0
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_index.rb +56 -0
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +263 -0
- data/plugins/rigor-active-model-serializers/lib/rigor-active-model-serializers.rb +7 -0
- data/plugins/rigor-active-model-serializers/sig/active_model_serializers.rbs +45 -0
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -0
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +9 -5
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +35 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +72 -12
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +380 -3
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +14 -6
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +281 -35
- data/plugins/rigor-activerecord/sig/active_record/relation.rbs +206 -31
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +25 -14
- data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +9 -2
- data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +171 -5
- data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +2 -0
- data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +1 -0
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/types.rb +1 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +10 -0
- data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +141 -0
- data/plugins/rigor-grape/lib/rigor-grape.rb +6 -0
- data/plugins/rigor-grape/sig/grape.rbs +263 -0
- data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +72 -2
- data/plugins/rigor-graphql/sig/graphql.rbs +485 -0
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +2 -0
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +11 -7
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +35 -37
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +0 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +1 -0
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +9 -17
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline/same_line_annotations.rb +149 -0
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +217 -36
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +1 -0
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +9 -5
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +0 -1
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +46 -1
- data/plugins/rigor-sorbet/sig/sorbet.rbs +277 -0
- data/sig/rigor/analysis/baseline.rbs +69 -7
- data/sig/rigor/analysis/fact_store.rbs +1 -1
- data/sig/rigor/analysis/project_scan.rbs +74 -0
- data/sig/rigor/effects/config_envelopes.rbs +100 -0
- data/sig/rigor/effects/effect_table.rbs +60 -0
- data/sig/rigor/effects/envelope.rbs +97 -0
- data/sig/rigor/effects/envelope_index.rbs +33 -0
- data/sig/rigor/effects/file_collection.rbs +81 -0
- data/sig/rigor/effects/label.rbs +26 -0
- data/sig/rigor/effects/label_set.rbs +46 -0
- data/sig/rigor/effects/method_key.rbs +24 -0
- data/sig/rigor/effects/origin.rbs +50 -0
- data/sig/rigor/effects/plugin_facts.rbs +141 -0
- data/sig/rigor/effects/registry.rbs +68 -0
- data/sig/rigor/effects/summary.rbs +50 -0
- data/sig/rigor/effects/taint_cause.rbs +12 -0
- data/sig/rigor/environment.rbs +14 -10
- data/sig/rigor/inference/optimistic_origin.rbs +10 -0
- data/sig/rigor/inference.rbs +6 -4
- data/sig/rigor/plugin/additional_initializer.rbs +36 -0
- data/sig/rigor/plugin/base.rbs +30 -7
- data/sig/rigor/plugin/effect_ancestry.rbs +27 -0
- data/sig/rigor/plugin/effect_attribution.rbs +66 -0
- data/sig/rigor/plugin/effect_edge.rbs +33 -0
- data/sig/rigor/plugin/effect_entry_points.rbs +25 -0
- data/sig/rigor/plugin/io_boundary.rbs +2 -1
- data/sig/rigor/plugin/loader.rbs +3 -3
- data/sig/rigor/plugin/manifest.rbs +50 -11
- data/sig/rigor/plugin/protocol_contract.rbs +68 -0
- data/sig/rigor/plugin/registry.rbs +62 -1
- data/sig/rigor/plugin.rbs +1 -1
- data/sig/rigor/rbs_extended.rbs +1 -1
- data/sig/rigor/reflection.rbs +7 -6
- data/sig/rigor/scope.rbs +177 -20
- data/sig/rigor/sig_gen/skip_reason_catalog.rbs +14 -7
- data/sig/rigor/source.rbs +4 -4
- data/sig/rigor/testing.rbs +10 -4
- data/sig/rigor/type.rbs +6 -0
- data/sig/rigor.rbs +38 -20
- data/skills/rigor-ask/SKILL.md +8 -5
- data/skills/rigor-baseline-reduce/SKILL.md +4 -6
- data/skills/rigor-ci-setup/SKILL.md +17 -21
- data/skills/rigor-doctor/SKILL.md +24 -22
- data/skills/rigor-doctor/references/01-checks.md +97 -33
- data/skills/rigor-editor-setup/SKILL.md +6 -4
- data/skills/rigor-mcp-setup/SKILL.md +5 -4
- data/skills/rigor-monkeypatch-resolve/SKILL.md +5 -3
- data/skills/rigor-next-steps/SKILL.md +4 -2
- data/skills/rigor-plugin-author/SKILL.md +19 -23
- data/skills/rigor-plugin-author/references/01-plan-and-scaffold.md +9 -8
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +12 -25
- data/skills/rigor-plugin-author/references/03-test-and-ship.md +6 -8
- data/skills/rigor-plugin-review/SKILL.md +6 -4
- data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +2 -1
- data/skills/rigor-plugin-tune/SKILL.md +4 -2
- data/skills/rigor-project-init/SKILL.md +46 -18
- data/skills/rigor-project-init/references/01-detect.md +13 -9
- data/skills/rigor-project-init/references/02-configure.md +40 -10
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +14 -14
- data/skills/rigor-project-init/references/04-sig-uplift.md +27 -14
- data/skills/rigor-protection-uplift/SKILL.md +4 -6
- data/skills/rigor-rbs-setup/SKILL.md +4 -2
- data/skills/rigor-type-oracle/SKILL.md +4 -6
- data/skills/rigor-type-oracle/references/01-oracle-commands.md +12 -8
- data/skills/rigor-type-oracle/references/03-gap-protocol.md +0 -3
- data/skills/rigor-unused-adjudicate/SKILL.md +4 -2
- data/skills/rigor-upgrade/SKILL.md +13 -8
- metadata +132 -1
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
module Cache
|
|
7
|
+
# Issue #1574 — a digest of a Ruby VALUE, not of its object graph. The same value digests to the same hex
|
|
8
|
+
# string whether a producer computed it in this process or a cache entry served it from disk, whatever
|
|
9
|
+
# Strings, Arrays or Hashes happen to share one object.
|
|
10
|
+
#
|
|
11
|
+
# `Marshal.dump` is not that function. It writes an object reached twice once and back-references it after,
|
|
12
|
+
# and a Marshal round trip does not keep that sharing: `Marshal.load` rebuilds a String unfrozen, and
|
|
13
|
+
# `Hash#[]=` then stores a frozen copy of it as the key rather than the String itself. A frozen String used
|
|
14
|
+
# both as a row's field and as the key of an index Hash (rigor-sidekiq's `worker_index`, rigor-rails-i18n's
|
|
15
|
+
# `locale_index`) therefore dumps to one set of bytes when computed and another when served, and a digest of
|
|
16
|
+
# those bytes flipped the ADR-88 fact-surface fingerprint on every run that switched between the two.
|
|
17
|
+
#
|
|
18
|
+
# The encoding below walks the value as a tree. Each node is written as a type tag plus its content, and
|
|
19
|
+
# sizes are length-prefixed, so the encoding is unambiguous and two values encode alike only when they are
|
|
20
|
+
# alike. Nothing in it reads identity or frozenness. An object (a Data, a Struct, or any other non-core
|
|
21
|
+
# object) is always written as its class name and the SHA-256 of its own encoding. That digest is taken once
|
|
22
|
+
# per object and reused wherever the object is reached again, so a row shared between an index's list and
|
|
23
|
+
# its by-name Hash is walked once, and the encoding is the same whether an object is shared or not.
|
|
24
|
+
#
|
|
25
|
+
# What it keeps, so a changed value still changes the digest:
|
|
26
|
+
#
|
|
27
|
+
# - every class distinction Marshal keeps: Integer vs Float, String vs Symbol, a subclass of String / Array /
|
|
28
|
+
# Hash / Set vs the base class, and the class name of every Data, Struct and plain object;
|
|
29
|
+
# - Hash and Set iteration ORDER. Two Hashes equal under `==` but built in another order digest differently.
|
|
30
|
+
# A consumer may iterate a fact in order (the first matching row wins), and Marshal keeps the order, so the
|
|
31
|
+
# computed and served forms agree on it; digesting the order is the direction that can only over-invalidate;
|
|
32
|
+
# - a Hash's `default` value, the `compare_by_identity` flag of a Hash or Set, a Range's `exclude_end?`, a
|
|
33
|
+
# Regexp's options, and a non-ASCII String's encoding. ASCII-only Strings digest alike in every encoding, as
|
|
34
|
+
# `==` has it.
|
|
35
|
+
#
|
|
36
|
+
# What it leaves out, although Marshal keeps it:
|
|
37
|
+
#
|
|
38
|
+
# - instance variables set on a String, Array, Hash or Set, which `==` ignores too;
|
|
39
|
+
# - modules an object was `extend`ed with (Marshal refuses singleton methods, and this does not look);
|
|
40
|
+
# - the hidden fields a core exception subclass keeps beside its message, backtrace and cause
|
|
41
|
+
# (`NameError#name`, `SystemCallError#errno`), which reach the digest only through the message, and the
|
|
42
|
+
# hidden fields any other C-implemented class keeps outside `instance_variables`.
|
|
43
|
+
#
|
|
44
|
+
# A plain object digests as its instance variables, sorted by name; a Data or Struct as its members and its
|
|
45
|
+
# instance variables; an Exception as its message, backtrace, cause and instance variables. Each is read
|
|
46
|
+
# through the core class's own method, so a subclass that overrides `to_h`, `each_pair`, `message` or
|
|
47
|
+
# `backtrace` cannot hide a field. An object that defines `marshal_dump` or `_dump` digests as what that
|
|
48
|
+
# returns, which is what Marshal keeps of it.
|
|
49
|
+
#
|
|
50
|
+
# A value that cannot be digested this way raises {Uncanonicalisable}: an object whose state lives outside
|
|
51
|
+
# its instance variables (a Proc, an IO, a Mutex: what `Marshal.dump` refuses), a Hash with a default proc, an
|
|
52
|
+
# anonymous class, or a structure nested more than {MAX_DEPTH} deep, which is what a cycle becomes. A caller
|
|
53
|
+
# must treat that as "unknown", never as "unchanged".
|
|
54
|
+
module ValueDigest
|
|
55
|
+
# The value has no canonical encoding. Fail closed: treat the value as moved.
|
|
56
|
+
class Uncanonicalisable < StandardError; end
|
|
57
|
+
|
|
58
|
+
# Containers nested deeper than this are refused. No fact is this deep; a cyclic structure is infinitely
|
|
59
|
+
# deep, so this is also the cycle check, and it fires well before the Ruby stack would.
|
|
60
|
+
MAX_DEPTH = 256
|
|
61
|
+
|
|
62
|
+
module_function
|
|
63
|
+
|
|
64
|
+
# The hex SHA-256 of `value`'s canonical encoding.
|
|
65
|
+
def hexdigest(value)
|
|
66
|
+
Encoder.new.hexdigest(value)
|
|
67
|
+
rescue SystemStackError
|
|
68
|
+
raise Uncanonicalisable, "#{value.class} is nested too deeply to digest"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# One walk of one value. The canonical bytes are fed to the SHA-256 in chunks, so a multi-megabyte fact
|
|
72
|
+
# never has its whole encoding in memory at once.
|
|
73
|
+
class Encoder
|
|
74
|
+
FLUSH_AT = 64 * 1024
|
|
75
|
+
private_constant :FLUSH_AT
|
|
76
|
+
|
|
77
|
+
def initialize
|
|
78
|
+
@sha = Digest::SHA256.new
|
|
79
|
+
@out = String.new(capacity: FLUSH_AT * 2, encoding: Encoding::BINARY)
|
|
80
|
+
@depth = 0
|
|
81
|
+
@objects = {}.compare_by_identity # object => the SHA-256 of its encoding
|
|
82
|
+
@symbols = {} # Symbol => its encoding
|
|
83
|
+
@plain = {}.compare_by_identity # class => whether Marshal writes its instances as their ivars
|
|
84
|
+
# The core readers, taken unbound so an override on the value's class cannot change what is read. Held
|
|
85
|
+
# per walk rather than in constants, which must stay Ractor-shareable (#1064).
|
|
86
|
+
@data_to_h = Data.instance_method(:to_h)
|
|
87
|
+
@struct_each_pair = Struct.instance_method(:each_pair)
|
|
88
|
+
@exception_readers = %i[to_s backtrace cause].map { |name| Exception.instance_method(name) }
|
|
89
|
+
@instance_variables = Kernel.instance_method(:instance_variables)
|
|
90
|
+
@instance_variable_get = Kernel.instance_method(:instance_variable_get)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def hexdigest(value)
|
|
94
|
+
write(value)
|
|
95
|
+
@sha.update(@out)
|
|
96
|
+
@sha.hexdigest
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
private
|
|
100
|
+
|
|
101
|
+
def write(value)
|
|
102
|
+
case value
|
|
103
|
+
when String then write_string(value)
|
|
104
|
+
when Symbol then @out.append_as_bytes(@symbols[value] ||= encoded("y", value.name))
|
|
105
|
+
when Hash then enter { write_hash(value) }
|
|
106
|
+
when Array then enter { write_array(value) }
|
|
107
|
+
when Set then enter { write_set(value) }
|
|
108
|
+
when Integer then @out.append_as_bytes("i", value.to_s, ";")
|
|
109
|
+
when nil then @out.append_as_bytes("n")
|
|
110
|
+
when true then @out.append_as_bytes("t")
|
|
111
|
+
when false then @out.append_as_bytes("f")
|
|
112
|
+
else write_other(value)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def write_other(value)
|
|
117
|
+
case value
|
|
118
|
+
when Float then @out.append_as_bytes("d", value.to_s, ";")
|
|
119
|
+
when Range then enter { write_range(value) }
|
|
120
|
+
when Regexp then write_regexp(value)
|
|
121
|
+
when Rational then @out.append_as_bytes("q", value.numerator.to_s, "/", value.denominator.to_s, ";")
|
|
122
|
+
when Complex then enter { write_pair("c", value.real, value.imaginary) }
|
|
123
|
+
when Module then write_bytes(value.is_a?(Class) ? "k" : "m", class_name(value))
|
|
124
|
+
else write_object(value)
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# The common case, an ASCII-only plain String, is written inline: it is most of the nodes of a large fact.
|
|
129
|
+
def write_string(value)
|
|
130
|
+
if value.ascii_only? && value.instance_of?(String)
|
|
131
|
+
@out.append_as_bytes("s:", value.bytesize.to_s, ":", value)
|
|
132
|
+
else
|
|
133
|
+
write_subclass(value, String)
|
|
134
|
+
write_bytes("s", value)
|
|
135
|
+
end
|
|
136
|
+
flush if @out.bytesize >= FLUSH_AT
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# A tag and a String's bytes, length-prefixed. The encoding is written only for a String with non-ASCII
|
|
140
|
+
# bytes: ASCII-only Strings compare equal whatever their encoding, and one built from a Symbol is
|
|
141
|
+
# US-ASCII.
|
|
142
|
+
def write_bytes(tag, string)
|
|
143
|
+
@out.append_as_bytes(encoded(tag, string))
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def encoded(tag, string)
|
|
147
|
+
encoding = string.ascii_only? ? "" : string.encoding.name
|
|
148
|
+
"#{tag}#{encoding}:#{string.bytesize}:".b.append_as_bytes(string)
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def write_hash(value)
|
|
152
|
+
raise Uncanonicalisable, "a Hash with a default proc" if value.default_proc
|
|
153
|
+
|
|
154
|
+
write_subclass(value, Hash)
|
|
155
|
+
default = value.default
|
|
156
|
+
@out.append_as_bytes("h", value.compare_by_identity? ? "I" : "", default.nil? ? "" : "D",
|
|
157
|
+
value.size.to_s, ":")
|
|
158
|
+
write(default) unless default.nil?
|
|
159
|
+
value.each_pair do |key, entry|
|
|
160
|
+
write(key)
|
|
161
|
+
write(entry)
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def write_array(value)
|
|
166
|
+
write_subclass(value, Array)
|
|
167
|
+
@out.append_as_bytes("a", value.size.to_s, ":")
|
|
168
|
+
value.each { |element| write(element) }
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def write_set(value)
|
|
172
|
+
write_subclass(value, Set)
|
|
173
|
+
@out.append_as_bytes("e", value.compare_by_identity? ? "I" : "", value.size.to_s, ":")
|
|
174
|
+
value.each { |element| write(element) }
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def write_range(value)
|
|
178
|
+
write_pair(value.exclude_end? ? "rx" : "ri", value.begin, value.end)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def write_pair(tag, first, second)
|
|
182
|
+
@out.append_as_bytes(tag)
|
|
183
|
+
write(first)
|
|
184
|
+
write(second)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def write_regexp(value)
|
|
188
|
+
@out.append_as_bytes("g", value.options.to_s, ";")
|
|
189
|
+
write_bytes("s", value.source)
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Every object that is not a core value: its class name and the digest of its content, taken once per
|
|
193
|
+
# object however often the value reaches it.
|
|
194
|
+
def write_object(value)
|
|
195
|
+
digest = @objects[value] ||= enter { object_digest(value) }
|
|
196
|
+
write_bytes("o", class_name(value.class))
|
|
197
|
+
@out.append_as_bytes(digest)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
# The object's content, walked into a SHA-256 of its own. A `marshal_dump` / `_dump` hook decides what
|
|
201
|
+
# Marshal keeps of an object, so it decides the digest too; otherwise the object is its members (a Data or
|
|
202
|
+
# Struct) or its message, backtrace and cause (an Exception), then its instance variables.
|
|
203
|
+
def object_digest(value)
|
|
204
|
+
outer_sha = @sha
|
|
205
|
+
outer_out = @out
|
|
206
|
+
@sha = Digest::SHA256.new
|
|
207
|
+
@out = +"".b
|
|
208
|
+
write_content(value)
|
|
209
|
+
@sha.update(@out)
|
|
210
|
+
@sha.digest
|
|
211
|
+
ensure
|
|
212
|
+
@sha = outer_sha
|
|
213
|
+
@out = outer_out
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def write_content(value)
|
|
217
|
+
if value.respond_to?(:marshal_dump, true)
|
|
218
|
+
@out.append_as_bytes("U")
|
|
219
|
+
write(value.send(:marshal_dump))
|
|
220
|
+
elsif value.is_a?(Time)
|
|
221
|
+
# `Time#_dump` keeps the offset and zone as instance variables on the String it returns, which the
|
|
222
|
+
# String encoding leaves out.
|
|
223
|
+
@out.append_as_bytes("z")
|
|
224
|
+
[value.to_r, value.utc_offset, value.zone].each { |part| write(part) }
|
|
225
|
+
elsif value.respond_to?(:_dump, true)
|
|
226
|
+
@out.append_as_bytes("u")
|
|
227
|
+
write(value.send(:_dump, -1))
|
|
228
|
+
else
|
|
229
|
+
write_members(value)
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def write_members(value)
|
|
234
|
+
case value
|
|
235
|
+
when Data then write_fields("D", @data_to_h.bind_call(value))
|
|
236
|
+
when Struct then write_fields("T", struct_members(value))
|
|
237
|
+
when Exception then write_exception(value)
|
|
238
|
+
else
|
|
239
|
+
assert_plain(value)
|
|
240
|
+
@out.append_as_bytes("V")
|
|
241
|
+
end
|
|
242
|
+
write_fields("@", instance_variables(value))
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def struct_members(value)
|
|
246
|
+
members = []
|
|
247
|
+
@struct_each_pair.bind_call(value) { |name, field| members << [name, field] }
|
|
248
|
+
members
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# Marshal keeps an exception's message, backtrace and cause in hidden instance variables, which
|
|
252
|
+
# `instance_variables` does not list: two exceptions that differ only in their message would otherwise
|
|
253
|
+
# digest alike. A plugin that records parse errors as exceptions depends on the message moving the digest.
|
|
254
|
+
def write_exception(value)
|
|
255
|
+
@out.append_as_bytes("X")
|
|
256
|
+
@exception_readers.each { |reader| write(reader.bind_call(value)) }
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def instance_variables(value)
|
|
260
|
+
@instance_variables.bind_call(value).sort.map { |name| [name, @instance_variable_get.bind_call(value, name)] }
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def write_fields(tag, pairs)
|
|
264
|
+
fields = pairs.to_a
|
|
265
|
+
@out.append_as_bytes(tag, fields.size.to_s, ":")
|
|
266
|
+
fields.each do |name, field|
|
|
267
|
+
write(name.to_sym)
|
|
268
|
+
write(field)
|
|
269
|
+
end
|
|
270
|
+
end
|
|
271
|
+
|
|
272
|
+
# An object's instance variables are all of its state only when Marshal writes it that way. `Marshal.dump`
|
|
273
|
+
# refuses a Proc, an IO, a Mutex or a Thread (C-level state and no `_dump_data`), and so does this. The
|
|
274
|
+
# depth limit of 1 makes the check cost one object: Marshal decides whether it can write the object
|
|
275
|
+
# before it writes any instance variable, and raises `ArgumentError` on reaching the first one. It is
|
|
276
|
+
# asked once per class, since whether a class keeps state outside Ruby does not vary by instance.
|
|
277
|
+
#
|
|
278
|
+
# The `ArgumentError` cannot tell a listed instance variable from a hidden one that a C-implemented class
|
|
279
|
+
# keeps, so an Exception, whose message and backtrace are hidden, is read explicitly above instead.
|
|
280
|
+
def assert_plain(value)
|
|
281
|
+
klass = value.class
|
|
282
|
+
plain = @plain.fetch(klass) do
|
|
283
|
+
@plain[klass] = begin
|
|
284
|
+
Marshal.dump(value, 1)
|
|
285
|
+
true
|
|
286
|
+
rescue ArgumentError # "exceed depth limit": the object itself was written
|
|
287
|
+
true
|
|
288
|
+
rescue TypeError
|
|
289
|
+
false
|
|
290
|
+
end
|
|
291
|
+
end
|
|
292
|
+
raise Uncanonicalisable, "#{klass} keeps its state outside its instance variables" unless plain
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
# A subclass of a core value keeps its class through Marshal, so the class is part of the value.
|
|
296
|
+
def write_subclass(value, base)
|
|
297
|
+
klass = value.class
|
|
298
|
+
write_bytes("C", class_name(klass)) unless klass.equal?(base)
|
|
299
|
+
end
|
|
300
|
+
|
|
301
|
+
def class_name(klass)
|
|
302
|
+
klass.name || raise(Uncanonicalisable, "an anonymous #{klass.class.name.downcase} has no name to digest")
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def enter
|
|
306
|
+
raise Uncanonicalisable, "a value nested deeper than #{MAX_DEPTH} (a cycle?)" if @depth >= MAX_DEPTH
|
|
307
|
+
|
|
308
|
+
@depth += 1
|
|
309
|
+
begin
|
|
310
|
+
result = yield
|
|
311
|
+
ensure
|
|
312
|
+
@depth -= 1
|
|
313
|
+
end
|
|
314
|
+
flush if @out.bytesize >= FLUSH_AT
|
|
315
|
+
result
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
def flush
|
|
319
|
+
@sha.update(@out)
|
|
320
|
+
@out.clear
|
|
321
|
+
end
|
|
322
|
+
end
|
|
323
|
+
end
|
|
324
|
+
end
|
|
325
|
+
end
|
data/lib/rigor/ci_detector.rb
CHANGED
|
@@ -59,8 +59,8 @@ module Rigor
|
|
|
59
59
|
# ADR-87 WD4 — try to serve the whole run from the ADR-45 cache before booting the engine. A hit boots
|
|
60
60
|
# only CLI + config + cache + digest code (no `rigor/inference`), skipping the plugin prepass + env
|
|
61
61
|
# build entirely.
|
|
62
|
-
|
|
63
|
-
return
|
|
62
|
+
served = serve_from_run_cache(configuration, options, buffer, cache_root, config_warnings)
|
|
63
|
+
return served unless served.nil?
|
|
64
64
|
|
|
65
65
|
load_check_dependencies
|
|
66
66
|
special = dispatch_special_check_mode(configuration, options, cache_root, buffer)
|
|
@@ -113,6 +113,16 @@ module Rigor
|
|
|
113
113
|
false
|
|
114
114
|
end
|
|
115
115
|
|
|
116
|
+
# The exit code of a run served whole from a run-result slot, engine-free, or nil to run the engine: the
|
|
117
|
+
# plain slot for an ordinary check (ADR-87 WD4), the incremental session's for `--incremental` (ADR-45 WD2).
|
|
118
|
+
def serve_from_run_cache(configuration, options, buffer, cache_root, config_warnings)
|
|
119
|
+
probed = try_run_cache_hit(configuration, options, buffer, cache_root)
|
|
120
|
+
return finalize_cache_hit(probed, configuration, options, config_warnings) unless probed.nil?
|
|
121
|
+
|
|
122
|
+
hit = try_incremental_run_hit(configuration, options, buffer, cache_root)
|
|
123
|
+
hit && finalize_incremental_hit(hit, configuration, options)
|
|
124
|
+
end
|
|
125
|
+
|
|
116
126
|
# ADR-87 WD4 — attempt the boot-slimming run-cache hit. Returns the cached {Analysis::Result} (severity
|
|
117
127
|
# profile applied, no stats — matching a cache-served `Runner#run`) on a hit, or nil to fall through to
|
|
118
128
|
# the full engine path. Only an ordinary sequential check whose result IS cache-served the same way is
|
|
@@ -141,8 +151,9 @@ module Rigor
|
|
|
141
151
|
|
|
142
152
|
# Any signal that per-file analysis would run in a worker pool (CLI `--workers`, the env override, or the
|
|
143
153
|
# config default). Deliberately over-declines (a false positive only forgoes the fast lane); mirrors
|
|
144
|
-
# {CheckRunnerFactory.resolve_workers} without requiring it (that pulls in the engine).
|
|
145
|
-
|
|
154
|
+
# {CheckRunnerFactory.resolve_workers} without requiring it (that pulls in the engine). `unreadable` is the
|
|
155
|
+
# answer for a count that does not parse, which the full path's resolver raises on.
|
|
156
|
+
def pool_workers_configured?(options, configuration, unreadable: false)
|
|
146
157
|
cli = options[:workers]
|
|
147
158
|
return Integer(cli).positive? if cli
|
|
148
159
|
|
|
@@ -151,7 +162,41 @@ module Rigor
|
|
|
151
162
|
|
|
152
163
|
configuration.parallel_workers.positive?
|
|
153
164
|
rescue ArgumentError
|
|
154
|
-
|
|
165
|
+
unreadable
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# ADR-45 WD2 (#1507) — `rigor check --incremental`'s engine-free null run: serve the run-result slot the
|
|
169
|
+
# incremental session wrote after its last run, when every row of it still validates. Returns an
|
|
170
|
+
# {Analysis::IncrementalRunSlot::Hit}, or nil to run the full incremental path.
|
|
171
|
+
def try_incremental_run_hit(configuration, options, buffer, cache_root)
|
|
172
|
+
return nil unless incremental_run_hit_eligible?(configuration, options, buffer)
|
|
173
|
+
|
|
174
|
+
require_relative "../analysis/incremental_run_slot"
|
|
175
|
+
Analysis::IncrementalRunSlot.serve(
|
|
176
|
+
configuration: configuration, cache_root: cache_root, paths: @argv.empty? ? configuration.paths : @argv
|
|
177
|
+
)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# A plain `--incremental` run the session would answer as a warm recheck and write a slot for. Every other
|
|
181
|
+
# shape declines, and the full path handles it exactly as before: an editor buffer (never saved), `--no-cache`
|
|
182
|
+
# (no store to hold a slot), `--verify-incremental`, a worker pool (the session writes no slot for one: a
|
|
183
|
+
# worker's plugin reads never reach the process that records them), and `--explain`, which the session
|
|
184
|
+
# ignores today (#1533) — the slot is keyed without it, so the probe declines rather than answer for a flag
|
|
185
|
+
# its writer never saw. The rest mirror the plain probe: `--coverage`, `--cache-stats` and the
|
|
186
|
+
# `RIGOR_*_TRACE` probes each want the full path's own output.
|
|
187
|
+
def incremental_run_hit_eligible?(configuration, options, buffer)
|
|
188
|
+
options.fetch(:incremental) && buffer.nil? &&
|
|
189
|
+
!options.fetch(:no_cache) && !options.fetch(:explain) && !options.fetch(:coverage) &&
|
|
190
|
+
!options.fetch(:cache_stats) && !options.fetch(:verify_incremental) &&
|
|
191
|
+
!pool_workers_configured?(options, configuration, unreadable: true) &&
|
|
192
|
+
ENV["RIGOR_BUDGET_TRACE"].to_s.empty? && ENV["RIGOR_HEAP_TRACE"].to_s.empty?
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# The tail {#run_incremental_check} gives a warm run, from the slot's answer: the same banner, baseline
|
|
196
|
+
# filter, output and exit code. No fact-surface note, since a warm run never has one.
|
|
197
|
+
def finalize_incremental_hit(hit, configuration, options)
|
|
198
|
+
report_incremental_run(true, hit.file_count)
|
|
199
|
+
write_incremental_result(hit.result.diagnostics, configuration, options)
|
|
155
200
|
end
|
|
156
201
|
|
|
157
202
|
# ADR-87 WD4 — the engine-free tail for a cache-served hit: baseline filter + output + exit code,
|
|
@@ -237,9 +282,15 @@ module Rigor
|
|
|
237
282
|
def run_incremental_check(configuration, options, cache_root, buffer = nil)
|
|
238
283
|
require_relative "check_runner_factory"
|
|
239
284
|
paths = @argv.empty? ? nil : @argv
|
|
240
|
-
fingerprint
|
|
241
|
-
|
|
242
|
-
)
|
|
285
|
+
# `--no-cache` is a cold run that touches no persistent state: a nil fingerprint is the session's "uncomputable
|
|
286
|
+
# inputs" signal, so it neither loads the snapshot, nor saves it, nor writes the run-result slot.
|
|
287
|
+
fingerprint = if options.fetch(:no_cache)
|
|
288
|
+
nil
|
|
289
|
+
else
|
|
290
|
+
Cache::IncrementalSnapshot.fingerprint(
|
|
291
|
+
configuration: configuration, roots: paths || configuration.paths
|
|
292
|
+
)
|
|
293
|
+
end
|
|
243
294
|
snapshot = Cache::IncrementalSnapshot.new(root: cache_root)
|
|
244
295
|
store = incremental_cache_store(configuration, options, cache_root)
|
|
245
296
|
session = Analysis::IncrementalSession.new(
|
|
@@ -258,12 +309,23 @@ module Rigor
|
|
|
258
309
|
# The banner's file count comes from the session's analyzed set (cold analyses all; a warm recheck's
|
|
259
310
|
# `@analyzed` advances to the current file set), so a dedicated probe Runner + `Dir.glob` is no longer
|
|
260
311
|
# built just to size the banner (recon §3 — the analysis tree was expanded three times per recheck).
|
|
261
|
-
|
|
262
|
-
"(#{session.analyzed_files.size} files)")
|
|
312
|
+
report_incremental_run(warm, session.analyzed_files.size)
|
|
263
313
|
emit_incremental_fact_surface_notes(session)
|
|
264
314
|
write_incremental_cache_stats(session, cache_root, store, options.fetch(:format)) if
|
|
265
315
|
options.fetch(:cache_stats)
|
|
266
316
|
|
|
317
|
+
write_incremental_result(diagnostics, configuration, options)
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
def report_incremental_run(warm, file_count)
|
|
321
|
+
@err.puts("rigor: --incremental #{warm ? 'warm — reused cached diagnostics' : 'cold — full analysis'} " \
|
|
322
|
+
"(#{file_count} files)")
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
# The incremental tail, shared by the full path and the ADR-45 WD2 slot hit so a served run prints exactly
|
|
326
|
+
# what the full path would. It is not the ordinary check's tail — no `config_warnings` block in the JSON,
|
|
327
|
+
# no CI-detected annotations, no `--baseline-strict` verdict — which is why the hit cannot reuse that one.
|
|
328
|
+
def write_incremental_result(diagnostics, configuration, options)
|
|
267
329
|
result = apply_baseline_filter(Analysis::Result.new(diagnostics: diagnostics, stats: nil), configuration,
|
|
268
330
|
options)
|
|
269
331
|
write_result(result, options.fetch(:format), fail_on: options.fetch(:fail_on))
|
|
@@ -282,8 +344,13 @@ module Rigor
|
|
|
282
344
|
def run_editor_mode_option_b(session, snapshot, fingerprint, configuration, options)
|
|
283
345
|
result = session.run_buffer_recheck(snapshot: snapshot, fingerprint: fingerprint)
|
|
284
346
|
if result.nil?
|
|
285
|
-
|
|
286
|
-
|
|
347
|
+
if options.fetch(:no_cache)
|
|
348
|
+
@err.puts("rigor: --no-cache skips the incremental snapshot, so the buffer is analysed on its own " \
|
|
349
|
+
"(no whole-project editor mode).")
|
|
350
|
+
else
|
|
351
|
+
@err.puts("rigor: --incremental has no reusable snapshot for this project; analysing the buffer " \
|
|
352
|
+
"alone (run `rigor check --incremental` once to enable whole-project editor mode).")
|
|
353
|
+
end
|
|
287
354
|
return nil
|
|
288
355
|
end
|
|
289
356
|
|
|
@@ -523,7 +590,10 @@ module Rigor
|
|
|
523
590
|
options[:coverage] = true
|
|
524
591
|
end
|
|
525
592
|
opts.on("--clear-cache", "Remove the .rigor/cache directory before running") { options[:clear_cache] = true }
|
|
526
|
-
opts.on("--no-cache",
|
|
593
|
+
opts.on("--no-cache",
|
|
594
|
+
"Disable the persistent cache for this run (with --incremental: no snapshot is read or written)") do
|
|
595
|
+
options[:no_cache] = true
|
|
596
|
+
end
|
|
527
597
|
opts.on("--[no-]stats",
|
|
528
598
|
"Print run summary (files, classes, memory, wall time) to stderr (default: on)") do |value|
|
|
529
599
|
options[:stats] = value
|
data/lib/rigor/cli/doc_links.rb
CHANGED
|
@@ -31,7 +31,7 @@ module Rigor
|
|
|
31
31
|
private_constant :MARKDOWN_LINK
|
|
32
32
|
|
|
33
33
|
# Where the gem's `docs/` tree sits, so a rendered path can be made repository-relative.
|
|
34
|
-
GEM_ROOT = File.expand_path("../../..", __dir__)
|
|
34
|
+
GEM_ROOT = File.expand_path("../../..", __dir__).freeze
|
|
35
35
|
private_constant :GEM_ROOT
|
|
36
36
|
|
|
37
37
|
module_function
|
|
@@ -47,10 +47,10 @@ module Rigor
|
|
|
47
47
|
USAGE
|
|
48
48
|
|
|
49
49
|
# The bundled docs live at `<gem_root>/docs/`. From `lib/rigor/cli/docs_command.rb` that is three directories up.
|
|
50
|
-
DOCS_ROOT = File.expand_path("../../../docs", __dir__)
|
|
51
|
-
MANUAL_ROOT = File.join(DOCS_ROOT, "manual")
|
|
52
|
-
HANDBOOK_ROOT = File.join(DOCS_ROOT, "handbook")
|
|
53
|
-
LLMS_INDEX = File.join(DOCS_ROOT, "llms.txt")
|
|
50
|
+
DOCS_ROOT = File.expand_path("../../../docs", __dir__).freeze
|
|
51
|
+
MANUAL_ROOT = File.join(DOCS_ROOT, "manual").freeze
|
|
52
|
+
HANDBOOK_ROOT = File.join(DOCS_ROOT, "handbook").freeze
|
|
53
|
+
LLMS_INDEX = File.join(DOCS_ROOT, "llms.txt").freeze
|
|
54
54
|
|
|
55
55
|
# @return CLI exit status.
|
|
56
56
|
def run
|
|
@@ -246,7 +246,7 @@ module Rigor
|
|
|
246
246
|
def real_or_expanded(path)
|
|
247
247
|
File.realpath(path)
|
|
248
248
|
rescue StandardError
|
|
249
|
-
File.
|
|
249
|
+
File.absolute_path(path)
|
|
250
250
|
end
|
|
251
251
|
|
|
252
252
|
# ADR-25 carry-over — the plain `bundle install` layout. {Environment::BundleSigDiscovery} detects a
|
|
@@ -121,7 +121,7 @@ module Rigor
|
|
|
121
121
|
# syntax: this is the argument a shell just tab-completed, and `rigor effects app/models` meaning
|
|
122
122
|
# "that directory" is the only reading a reader would guess.
|
|
123
123
|
def self.normalize_scope(scope)
|
|
124
|
-
Array(scope).map { |path| File.
|
|
124
|
+
Array(scope).map { |path| File.absolute_path(path.to_s.chomp("/")) }.freeze
|
|
125
125
|
end
|
|
126
126
|
private_class_method :normalize_scope
|
|
127
127
|
|
|
@@ -130,7 +130,7 @@ module Rigor
|
|
|
130
130
|
return false if paths.nil? || paths.empty?
|
|
131
131
|
|
|
132
132
|
paths.any? do |path|
|
|
133
|
-
absolute = File.
|
|
133
|
+
absolute = File.absolute_path(path)
|
|
134
134
|
roots.any? { |root| absolute == root || absolute.start_with?("#{root}/") }
|
|
135
135
|
end
|
|
136
136
|
end
|
|
@@ -60,9 +60,9 @@ module Rigor
|
|
|
60
60
|
|
|
61
61
|
# The bundled plugins/examples/source live at `<gem_root>/...`. From `lib/rigor/cli/plugin_command.rb` the gem
|
|
62
62
|
# root is three directories up (matching SkillCommand::SKILLS_ROOT).
|
|
63
|
-
GEM_ROOT = File.expand_path("../../..", __dir__)
|
|
64
|
-
PLUGINS_ROOT = File.join(GEM_ROOT, "plugins")
|
|
65
|
-
EXAMPLES_ROOT = File.join(GEM_ROOT, "examples")
|
|
63
|
+
GEM_ROOT = File.expand_path("../../..", __dir__).freeze
|
|
64
|
+
PLUGINS_ROOT = File.join(GEM_ROOT, "plugins").freeze
|
|
65
|
+
EXAMPLES_ROOT = File.join(GEM_ROOT, "examples").freeze
|
|
66
66
|
|
|
67
67
|
# @return CLI exit status.
|
|
68
68
|
def run
|
|
@@ -35,7 +35,6 @@ module Rigor
|
|
|
35
35
|
|
|
36
36
|
VARIABLE_TOKENS = %i[INSTANCE_VARIABLE CLASS_VARIABLE GLOBAL_VARIABLE].freeze
|
|
37
37
|
|
|
38
|
-
# @param source — Ruby source.
|
|
39
38
|
# @return the source with ANSI colour escapes, or
|
|
40
39
|
# the input unchanged when lexing surfaces an error.
|
|
41
40
|
def colorize(source)
|