rigortype 0.2.9 → 0.3.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 +2 -2
- data/data/builtins/ruby_core/array.yml +416 -392
- data/data/builtins/ruby_core/file.yml +42 -42
- data/data/builtins/ruby_core/hash.yml +302 -302
- data/data/builtins/ruby_core/io.yml +191 -191
- data/data/builtins/ruby_core/numeric.yml +321 -366
- data/data/builtins/ruby_core/proc.yml +124 -124
- data/data/builtins/ruby_core/range.yml +21 -21
- data/data/builtins/ruby_core/rational.yml +39 -39
- data/data/builtins/ruby_core/re.yml +65 -65
- data/data/builtins/ruby_core/set.yml +106 -106
- data/data/builtins/ruby_core/struct.yml +14 -14
- data/data/core_overlay/string_scanner.rbs +6 -5
- data/docs/handbook/01-getting-started.md +22 -34
- data/docs/handbook/03-narrowing.md +2 -1
- data/docs/handbook/04-tuples-and-shapes.md +7 -3
- data/docs/handbook/06-classes.md +19 -10
- data/docs/handbook/07-rbs-and-extended.md +76 -101
- data/docs/handbook/08-understanding-errors.md +114 -246
- data/docs/handbook/09-plugins.md +54 -144
- data/docs/handbook/11-sig-gen.md +11 -1
- data/docs/handbook/README.md +5 -3
- data/docs/handbook/appendix-liskov.md +4 -2
- data/docs/handbook/appendix-mypy.md +4 -3
- data/docs/handbook/appendix-phpstan.md +14 -7
- data/docs/handbook/appendix-steep.md +4 -2
- data/docs/handbook/appendix-type-theory.md +3 -1
- data/docs/install.md +14 -6
- data/docs/manual/01-installation.md +98 -4
- data/docs/manual/02-cli-reference.md +81 -12
- data/docs/manual/03-configuration.md +34 -2
- data/docs/manual/04-diagnostics.md +59 -4
- data/docs/manual/06-baseline.md +35 -1
- data/docs/manual/07-plugins.md +4 -4
- data/docs/manual/08-skills.md +6 -1
- data/docs/manual/09-editor-integration.md +7 -6
- data/docs/manual/11-ci.md +51 -0
- data/docs/manual/12-caching.md +65 -0
- data/docs/manual/plugins/rigor-actioncable.md +32 -0
- data/docs/manual/plugins/rigor-devise.md +4 -2
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +9 -1
- data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +5 -3
- data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +128 -0
- data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +60 -0
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +3 -2
- data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +117 -0
- data/lib/rigor/analysis/check_rules/rule_ids.rb +125 -0
- data/lib/rigor/analysis/check_rules/rule_walk.rb +2 -1
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +2 -1
- data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +283 -0
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +10 -1
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +112 -0
- data/lib/rigor/analysis/check_rules.rb +581 -98
- data/lib/rigor/analysis/dependency_recorder.rb +93 -9
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +2 -1
- data/lib/rigor/analysis/incremental_session.rb +456 -51
- data/lib/rigor/analysis/path_expansion.rb +42 -0
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +188 -0
- data/lib/rigor/analysis/rule_catalog.rb +226 -1
- data/lib/rigor/analysis/run_cache_key.rb +78 -0
- data/lib/rigor/analysis/run_cache_probe.rb +72 -0
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +181 -28
- data/lib/rigor/analysis/runner/pool_coordinator.rb +77 -15
- data/lib/rigor/analysis/runner/project_pre_passes.rb +95 -49
- data/lib/rigor/analysis/runner/run_snapshots.rb +9 -2
- data/lib/rigor/analysis/runner.rb +344 -68
- data/lib/rigor/analysis/severity_stamp.rb +43 -0
- data/lib/rigor/analysis/worker_session.rb +30 -2
- data/lib/rigor/bleeding_edge.rb +37 -8
- data/lib/rigor/builtins/regex_refinement.rb +36 -1
- data/lib/rigor/cache/descriptor.rb +88 -28
- data/lib/rigor/cache/file_digest.rb +158 -0
- data/lib/rigor/cache/incremental_snapshot.rb +41 -5
- data/lib/rigor/cache/rbs_cache_producer.rb +11 -1
- data/lib/rigor/cache/rbs_descriptor.rb +56 -10
- data/lib/rigor/cache/rbs_environment_marshal_patch.rb +38 -0
- data/lib/rigor/cache/store.rb +132 -27
- data/lib/rigor/ci_detector.rb +90 -0
- data/lib/rigor/cli/annotate_command.rb +15 -9
- data/lib/rigor/cli/baseline_command.rb +4 -1
- data/lib/rigor/cli/check_command.rb +223 -32
- data/lib/rigor/cli/check_invocation.rb +84 -0
- data/lib/rigor/cli/coverage_command.rb +6 -1
- data/lib/rigor/cli/docs_command.rb +0 -30
- data/lib/rigor/cli/doctor_command.rb +169 -31
- data/lib/rigor/cli/lsp_command.rb +5 -0
- data/lib/rigor/cli/mcp_command.rb +5 -0
- data/lib/rigor/cli/plugins_command.rb +36 -6
- data/lib/rigor/cli/plugins_renderer.rb +44 -12
- data/lib/rigor/cli/probe_environment.rb +85 -0
- data/lib/rigor/cli/sig_gen_command.rb +36 -9
- data/lib/rigor/cli/skill_command.rb +22 -33
- data/lib/rigor/cli/skill_deep_probe.rb +172 -0
- data/lib/rigor/cli/skill_describe.rb +75 -9
- data/lib/rigor/cli/trace_command.rb +5 -6
- data/lib/rigor/cli/type_of_command.rb +9 -8
- data/lib/rigor/cli/type_scan_command.rb +8 -8
- data/lib/rigor/cli.rb +15 -4
- data/lib/rigor/config_audit.rb +48 -2
- data/lib/rigor/configuration/severity_profile.rb +31 -0
- data/lib/rigor/configuration.rb +156 -8
- data/lib/rigor/environment/default_libraries.rb +36 -0
- data/lib/rigor/environment/rbs_loader.rb +164 -5
- data/lib/rigor/environment.rb +29 -28
- data/lib/rigor/inference/budget_trace.rb +77 -2
- data/lib/rigor/inference/closure_escape_analyzer.rb +14 -1
- data/lib/rigor/inference/def_handle.rb +23 -0
- data/lib/rigor/inference/def_node_resolver.rb +90 -0
- data/lib/rigor/inference/def_return_typer.rb +2 -1
- data/lib/rigor/inference/expression_typer.rb +361 -113
- data/lib/rigor/inference/method_dispatcher/data_folding.rb +30 -5
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +200 -0
- data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +22 -16
- data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +33 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +17 -1
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +42 -9
- data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +5 -0
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +28 -35
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +40 -8
- data/lib/rigor/inference/method_dispatcher.rb +57 -1
- data/lib/rigor/inference/mutation_widening.rb +42 -8
- data/lib/rigor/inference/narrowing.rb +127 -17
- data/lib/rigor/inference/parameter_inference_collector.rb +15 -0
- data/lib/rigor/inference/project_patched_scanner.rb +2 -1
- data/lib/rigor/inference/rbs_type_translator.rb +8 -1
- data/lib/rigor/inference/scope_indexer.rb +451 -36
- data/lib/rigor/inference/statement_evaluator.rb +64 -15
- data/lib/rigor/inference/struct_fold_safety.rb +48 -11
- data/lib/rigor/inference/synthetic_method_scanner.rb +15 -14
- data/lib/rigor/inference/void_origin.rb +25 -0
- data/lib/rigor/inference/void_tail_summary.rb +220 -0
- data/lib/rigor/language_server/buffer_resolution.rb +6 -3
- data/lib/rigor/language_server/buffer_table.rb +46 -6
- data/lib/rigor/language_server/completion_provider.rb +2 -1
- data/lib/rigor/language_server/diagnostic_publisher.rb +4 -0
- data/lib/rigor/language_server/document_symbol_provider.rb +2 -1
- data/lib/rigor/language_server/folding_range_provider.rb +2 -1
- data/lib/rigor/language_server/incremental_sync.rb +159 -0
- data/lib/rigor/language_server/selection_range_provider.rb +2 -1
- data/lib/rigor/language_server/server.rb +19 -9
- data/lib/rigor/language_server/signature_help_provider.rb +2 -1
- data/lib/rigor/language_server.rb +1 -0
- data/lib/rigor/plugin/base.rb +38 -24
- data/lib/rigor/plugin/inflector.rb +12 -3
- data/lib/rigor/plugin/io_boundary.rb +5 -1
- data/lib/rigor/plugin/isolation.rb +81 -11
- data/lib/rigor/plugin/load_error.rb +10 -1
- data/lib/rigor/plugin/loader.rb +96 -14
- data/lib/rigor/plugin/node_rule_walk.rb +5 -3
- data/lib/rigor/plugin/registry.rb +32 -23
- data/lib/rigor/plugin.rb +26 -0
- data/lib/rigor/protection/mutator.rb +3 -2
- data/lib/rigor/reflection.rb +64 -0
- data/lib/rigor/runtime/jit.rb +128 -0
- data/lib/rigor/scope/discovery_index.rb +12 -2
- data/lib/rigor/scope.rb +122 -28
- data/lib/rigor/sig_gen/classification.rb +5 -1
- data/lib/rigor/sig_gen/generator.rb +38 -6
- data/lib/rigor/sig_gen/observation_collector.rb +4 -3
- data/lib/rigor/sig_gen/rbs_validity.rb +44 -0
- data/lib/rigor/sig_gen/renderer.rb +10 -0
- data/lib/rigor/sig_gen/write_result.rb +9 -4
- data/lib/rigor/sig_gen/writer.rb +207 -49
- data/lib/rigor/source/node_children.rb +116 -0
- data/lib/rigor/source/node_locator.rb +3 -1
- data/lib/rigor/source/node_walker.rb +4 -2
- data/lib/rigor/source.rb +1 -0
- data/lib/rigor/type/difference.rb +28 -24
- data/lib/rigor/type/hash_shape.rb +34 -10
- data/lib/rigor/version.rb +1 -1
- data/lib/rigortype.rb +24 -0
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +4 -2
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +51 -1
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +6 -4
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +5 -3
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +4 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +3 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +4 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +4 -2
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +3 -1
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +4 -2
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +38 -6
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +3 -1
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +3 -1
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +10 -0
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +11 -0
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +3 -1
- data/plugins/rigor-rails/lib/rigor-rails.rb +11 -12
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +3 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +3 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +7 -5
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +83 -9
- data/plugins/rigor-rbs-inline/lib/rigor-rbs-inline.rb +9 -5
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +3 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/scope_walker.rb +3 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +11 -0
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/http_status_codes.rb +4 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +4 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +3 -1
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +74 -14
- data/sig/prism_node_children.rbs +9 -0
- data/sig/rigor/cache.rbs +6 -0
- data/sig/rigor/inference/void_origin.rbs +18 -0
- data/sig/rigor/inference.rbs +7 -0
- data/sig/rigor/plugin/base.rbs +6 -6
- data/sig/rigor/reflection.rbs +1 -0
- data/sig/rigor/scope.rbs +12 -1
- data/skills/rigor-ci-setup/SKILL.md +10 -0
- data/skills/rigor-editor-setup/SKILL.md +11 -0
- data/skills/rigor-next-steps/SKILL.md +23 -2
- data/skills/rigor-plugin-author/SKILL.md +3 -3
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +2 -2
- data/skills/rigor-plugin-review/SKILL.md +2 -2
- data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +5 -4
- data/skills/rigor-project-init/SKILL.md +29 -3
- data/skills/rigor-project-init/references/01-detect.md +1 -1
- data/skills/rigor-project-init/references/02-configure.md +39 -5
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +20 -0
- data/skills/rigor-project-init/references/05-jit-performance.md +125 -0
- metadata +48 -26
- data/lib/rigor/cli/ci_detector.rb +0 -89
data/sig/rigor/inference.rbs
CHANGED
|
@@ -3,8 +3,10 @@ module Rigor
|
|
|
3
3
|
type closure_classification = :non_escaping | :escaping | :unknown
|
|
4
4
|
|
|
5
5
|
class ExpressionTyper
|
|
6
|
+
def self.harvest_return_memo: () -> Hash[untyped, Array[untyped]]
|
|
6
7
|
def initialize: (scope: Scope, ?tracer: FallbackTracer?) -> void
|
|
7
8
|
def type_of: (untyped node) -> Type::t
|
|
9
|
+
def return_type_for: (untyped def_node, Type::t receiver, Array[Type::t] arg_types) -> Type::t?
|
|
8
10
|
def scope: () -> Scope
|
|
9
11
|
def tracer: () -> FallbackTracer?
|
|
10
12
|
def dynamic_top: () -> Type::Dynamic
|
|
@@ -27,6 +29,7 @@ module Rigor
|
|
|
27
29
|
|
|
28
30
|
def initialize: (scope: Scope, ?tracer: FallbackTracer?, ?on_enter: (^(untyped, Scope) -> void)?, ?class_context: Array[ClassFrame]) -> void
|
|
29
31
|
def evaluate: (untyped node) -> [Type::t, Scope]
|
|
32
|
+
def content_mutated_parameter_positions: (untyped def_node) -> Array[Integer]
|
|
30
33
|
def scope: () -> Scope
|
|
31
34
|
def tracer: () -> FallbackTracer?
|
|
32
35
|
def sub_eval: (untyped node, Scope with_scope, ?class_context: Array[ClassFrame]) -> [Type::t, Scope]
|
|
@@ -163,6 +166,10 @@ module Rigor
|
|
|
163
166
|
def self?.record_declarations: (untyped node, Array[String] qualified_prefix, Hash[untyped, Type::t] identity_table, Hash[String, Type::t] discovered) -> void
|
|
164
167
|
def self?.discovered_classes_for_paths: (Array[String] paths, ?buffer: untyped) -> Hash[String, Type::t]
|
|
165
168
|
def self?.discovered_def_index_for_paths: (Array[String] paths, ?buffer: untyped) -> Hash[Symbol, untyped]
|
|
169
|
+
def self?.scan_summary_for_paths: (Array[String] paths, ?buffer: untyped) -> Hash[Symbol, untyped]
|
|
170
|
+
def self?.code_fingerprint: (String source, untyped comments) -> String
|
|
171
|
+
def self?.discovered_project_index_for_paths: (Array[String] paths, ?buffer: untyped) -> Hash[Symbol, untyped]
|
|
172
|
+
def self?.discovered_project_index_incremental: (Array[String] paths, seed_bundles: Hash[String, untyped], ?buffer: untyped) -> Hash[Symbol, untyped]
|
|
166
173
|
def self?.collect_class_decls: (untyped node, Array[String] qualified_prefix, Hash[String, Type::t] accumulator) -> void
|
|
167
174
|
def self?.qualified_name_for: (untyped constant_path_node) -> String?
|
|
168
175
|
def self?.render_constant_path: (untyped node) -> String
|
data/sig/rigor/plugin/base.rbs
CHANGED
|
@@ -14,7 +14,7 @@ class Rigor::Plugin::Base
|
|
|
14
14
|
# the no-arg `manifest` reads the cached value back.
|
|
15
15
|
def self.manifest: (**untyped fields) -> Rigor::Plugin::Manifest
|
|
16
16
|
|
|
17
|
-
def self.producer: (untyped id, ?watch: untyped, ?serialize: untyped, ?deserialize: untyped) { (untyped params) -> untyped } -> Symbol
|
|
17
|
+
def self.producer: (untyped id, ?watch: untyped, ?serialize: untyped, ?deserialize: untyped, ?generation_cap: untyped) { (untyped params) -> untyped } -> Symbol
|
|
18
18
|
def self.producers: () -> Hash[Symbol, untyped]
|
|
19
19
|
|
|
20
20
|
def self.node_rule: (untyped node_type) { (*untyped) -> untyped } -> untyped
|
|
@@ -23,13 +23,13 @@ class Rigor::Plugin::Base
|
|
|
23
23
|
def self.node_file_context: () { (untyped root, untyped scope) -> untyped } -> untyped
|
|
24
24
|
def self.node_file_context_block: () -> untyped
|
|
25
25
|
|
|
26
|
-
def self.dynamic_return: (?receivers: (Array[String] | ^() -> Array[String])?, ?methods: (Array[untyped] | ^() -> Array[untyped])?) { (untyped call_node, untyped scope) -> untyped } -> nil
|
|
26
|
+
def self.dynamic_return: (?receivers: (Array[String] | ^() -> Array[String])?, ?methods: (Array[untyped] | ^() -> Array[untyped])?, ?file_methods: (^(untyped path) -> Array[untyped])?) { (untyped call_node, untyped scope) -> untyped } -> nil
|
|
27
27
|
def self.dynamic_returns: () -> Array[untyped]
|
|
28
28
|
|
|
29
29
|
def self.narrowing_facts: (methods: Array[untyped]) { (untyped call_node, untyped scope) -> untyped } -> nil
|
|
30
|
-
#
|
|
31
|
-
|
|
32
|
-
def self.
|
|
30
|
+
# The engine-facing reader for the declared `narrowing_facts` rules. Named
|
|
31
|
+
# `type_specifiers` before ADR-80; that spelling was removed in 0.3.0.
|
|
32
|
+
def self.narrowing_facts_rules: () -> Array[untyped]
|
|
33
33
|
|
|
34
34
|
def self.suggest: (untyped name, untyped candidates) -> String?
|
|
35
35
|
|
|
@@ -48,7 +48,7 @@ class Rigor::Plugin::Base
|
|
|
48
48
|
# Engine-executed dispatch over the declared DSLs.
|
|
49
49
|
def node_rule_diagnostics: (path: untyped, scope: untyped, root: untyped) -> Array[untyped]
|
|
50
50
|
def dynamic_return_type: (call_node: untyped, scope: untyped, receiver_type: untyped) -> untyped
|
|
51
|
-
def
|
|
51
|
+
def narrowing_facts_for: (call_node: untyped, scope: untyped) -> Array[untyped]
|
|
52
52
|
|
|
53
53
|
# Authoring helpers.
|
|
54
54
|
def diagnostic: (untyped node, path: untyped, message: untyped, ?severity: untyped, ?rule: untyped, ?location: untyped) -> untyped
|
data/sig/rigor/reflection.rbs
CHANGED
|
@@ -6,6 +6,7 @@ module Rigor
|
|
|
6
6
|
def self?.nominal_for_name: (String | Symbol class_name, ?scope: Scope) -> Type::Nominal?
|
|
7
7
|
def self?.singleton_for_name: (String | Symbol class_name, ?scope: Scope) -> Type::Singleton?
|
|
8
8
|
def self?.constant_type_for: (String | Symbol constant_name, ?scope: Scope) -> Type::t?
|
|
9
|
+
def self?.resolve_constant_type: (String | Symbol name, ?scope: Scope) -> Type::t?
|
|
9
10
|
def self?.instance_method_definition: (String | Symbol class_name, String | Symbol method_name, ?scope: Scope) -> untyped
|
|
10
11
|
def self?.singleton_method_definition: (String | Symbol class_name, String | Symbol method_name, ?scope: Scope) -> untyped
|
|
11
12
|
def self?.instance_definition: (String | Symbol class_name, ?scope: Scope) -> untyped
|
data/sig/rigor/scope.rbs
CHANGED
|
@@ -11,6 +11,7 @@ module Rigor
|
|
|
11
11
|
attr_reader dynamic_origins: Hash[untyped, Symbol]
|
|
12
12
|
attr_reader local_origins: Hash[Symbol, Symbol]
|
|
13
13
|
attr_reader ivar_origins: Hash[Symbol, Symbol]
|
|
14
|
+
attr_reader void_origins: Hash[untyped, Inference::VoidOrigin]
|
|
14
15
|
attr_reader indexed_narrowings: Hash[IndexedKey, Type::t]
|
|
15
16
|
attr_reader method_chain_narrowings: Hash[ChainKey, Type::t]
|
|
16
17
|
attr_reader source_path: String?
|
|
@@ -28,6 +29,7 @@ module Rigor
|
|
|
28
29
|
def discovered_def_nodes: () -> Hash[String, Hash[Symbol, untyped]]
|
|
29
30
|
def discovered_singleton_def_nodes: () -> Hash[String, Hash[Symbol, untyped]]
|
|
30
31
|
def discovered_def_sources: () -> Hash[String, Hash[Symbol, String]]
|
|
32
|
+
def discovered_singleton_def_sources: () -> Hash[String, Hash[Symbol, String]]
|
|
31
33
|
def discovered_method_visibilities: () -> Hash[String, Hash[Symbol, Symbol]]
|
|
32
34
|
def discovered_superclasses: () -> Hash[String, String]
|
|
33
35
|
def discovered_includes: () -> Hash[String, Array[String]]
|
|
@@ -35,8 +37,11 @@ module Rigor
|
|
|
35
37
|
def data_member_layouts: () -> Hash[String, Array[Symbol]]
|
|
36
38
|
def struct_member_layouts: () -> Hash[String, { members: Array[Symbol], keyword_init: bool }]
|
|
37
39
|
def param_inferred_types: () -> Hash[[String, Symbol, Symbol], Hash[Symbol, Type::t]]
|
|
40
|
+
def run_generation: () -> Object?
|
|
38
41
|
def record_dynamic_origin: (untyped node, Symbol cause) -> Scope
|
|
39
42
|
|
|
43
|
+
def record_void_origin: (untyped node, Inference::VoidOrigin origin) -> Scope
|
|
44
|
+
|
|
40
45
|
class DiscoveryIndex
|
|
41
46
|
attr_reader declared_types: Hash[untyped, Type::t]
|
|
42
47
|
attr_reader class_ivars: Hash[String, Hash[Symbol, Type::t]]
|
|
@@ -48,6 +53,7 @@ module Rigor
|
|
|
48
53
|
attr_reader discovered_def_nodes: Hash[String, Hash[Symbol, untyped]]
|
|
49
54
|
attr_reader discovered_singleton_def_nodes: Hash[String, Hash[Symbol, untyped]]
|
|
50
55
|
attr_reader discovered_def_sources: Hash[String, Hash[Symbol, String]]
|
|
56
|
+
attr_reader discovered_singleton_def_sources: Hash[String, Hash[Symbol, String]]
|
|
51
57
|
attr_reader discovered_method_visibilities: Hash[String, Hash[Symbol, Symbol]]
|
|
52
58
|
attr_reader discovered_superclasses: Hash[String, String]
|
|
53
59
|
attr_reader discovered_includes: Hash[String, Array[String]]
|
|
@@ -55,10 +61,11 @@ module Rigor
|
|
|
55
61
|
attr_reader data_member_layouts: Hash[String, Array[Symbol]]
|
|
56
62
|
attr_reader struct_member_layouts: Hash[String, { members: Array[Symbol], keyword_init: bool }]
|
|
57
63
|
attr_reader param_inferred_types: Hash[[String, Symbol, Symbol], Hash[Symbol, Type::t]]
|
|
64
|
+
attr_reader run_generation: Object?
|
|
58
65
|
|
|
59
66
|
EMPTY: DiscoveryIndex
|
|
60
67
|
|
|
61
|
-
def with: (?declared_types: Hash[untyped, Type::t], ?class_ivars: Hash[String, Hash[Symbol, Type::t]], ?class_cvars: Hash[String, Hash[Symbol, Type::t]], ?program_globals: Hash[Symbol, Type::t], ?discovered_classes: Hash[String, Type::Singleton], ?in_source_constants: Hash[String, Type::t], ?discovered_methods: Hash[String, Hash[Symbol, Symbol]], ?discovered_def_nodes: Hash[String, Hash[Symbol, untyped]], ?discovered_singleton_def_nodes: Hash[String, Hash[Symbol, untyped]], ?discovered_def_sources: Hash[String, Hash[Symbol, String]], ?discovered_method_visibilities: Hash[String, Hash[Symbol, Symbol]], ?discovered_superclasses: Hash[String, String], ?discovered_includes: Hash[String, Array[String]], ?discovered_class_sources: Hash[String, Set[String]], ?data_member_layouts: Hash[String, Array[Symbol]], ?struct_member_layouts: Hash[String, { members: Array[Symbol], keyword_init: bool }], ?param_inferred_types: Hash[[String, Symbol, Symbol], Hash[Symbol, Type::t]]) -> DiscoveryIndex
|
|
68
|
+
def with: (?declared_types: Hash[untyped, Type::t], ?class_ivars: Hash[String, Hash[Symbol, Type::t]], ?class_cvars: Hash[String, Hash[Symbol, Type::t]], ?program_globals: Hash[Symbol, Type::t], ?discovered_classes: Hash[String, Type::Singleton], ?in_source_constants: Hash[String, Type::t], ?discovered_methods: Hash[String, Hash[Symbol, Symbol]], ?discovered_def_nodes: Hash[String, Hash[Symbol, untyped]], ?discovered_singleton_def_nodes: Hash[String, Hash[Symbol, untyped]], ?discovered_def_sources: Hash[String, Hash[Symbol, String]], ?discovered_singleton_def_sources: Hash[String, Hash[Symbol, String]], ?discovered_method_visibilities: Hash[String, Hash[Symbol, Symbol]], ?discovered_superclasses: Hash[String, String], ?discovered_includes: Hash[String, Array[String]], ?discovered_class_sources: Hash[String, Set[String]], ?data_member_layouts: Hash[String, Array[Symbol]], ?struct_member_layouts: Hash[String, { members: Array[Symbol], keyword_init: bool }], ?param_inferred_types: Hash[[String, Symbol, Symbol], Hash[Symbol, Type::t]], ?run_generation: Object?) -> DiscoveryIndex
|
|
62
69
|
end
|
|
63
70
|
|
|
64
71
|
class IndexedKey
|
|
@@ -97,6 +104,9 @@ module Rigor
|
|
|
97
104
|
def with_declaration_sourced_local: (String | Symbol name, Type::t type) -> Scope
|
|
98
105
|
def with_local_declaration_mark: (String | Symbol name) -> Scope
|
|
99
106
|
def declaration_sourced?: (Symbol kind, String | Symbol name) -> bool
|
|
107
|
+
def with_inferred_param_mark: (String | Symbol name) -> Scope
|
|
108
|
+
def without_inferred_param_mark: (String | Symbol name) -> Scope
|
|
109
|
+
def inferred_param?: (String | Symbol name) -> bool
|
|
100
110
|
def forget_match_globals: () -> Scope
|
|
101
111
|
def class_ivars_for: (String | Symbol? class_name) -> Hash[Symbol, Type::t]
|
|
102
112
|
def class_cvars_for: (String | Symbol? class_name) -> Hash[Symbol, Type::t]
|
|
@@ -125,6 +135,7 @@ module Rigor
|
|
|
125
135
|
def local_facts: (String | Symbol name, ?bucket: Symbol?) -> Array[Analysis::FactStore::Fact]
|
|
126
136
|
def type_of: (untyped node, ?tracer: Inference::FallbackTracer?) -> Type::t
|
|
127
137
|
def evaluate: (untyped node, ?tracer: Inference::FallbackTracer?) -> [Type::t, Scope]
|
|
138
|
+
def user_method_return: (untyped def_node, Type::t receiver, Array[Type::t] arg_types) -> Type::t?
|
|
128
139
|
def join: (Scope other) -> Scope
|
|
129
140
|
def ==: (untyped other) -> bool
|
|
130
141
|
alias eql? ==
|
|
@@ -141,6 +141,16 @@ The exact recipe (the `BUNDLE_GEMFILE` wiring, the Dependabot entry) is in
|
|
|
141
141
|
role.
|
|
142
142
|
- **Determinism.** Add `--no-cache` in CI if you want each run independent
|
|
143
143
|
of any persisted `.rigor/cache`.
|
|
144
|
+
- **Cache persistence (opposite trade).** When the Rigor job's runtime
|
|
145
|
+
matters, persist `.rigor/cache` with the CI's cache facility. Current
|
|
146
|
+
Rigor detects CI and validates the restored cache by content hash
|
|
147
|
+
automatically (`cache.validation: auto`); on versions before the
|
|
148
|
+
`auto` default, **also set `RIGOR_STRICT_VALIDATION=1` on the job** —
|
|
149
|
+
without it a fresh checkout's regenerated stat tuples make the plugin
|
|
150
|
+
watch-glob cache slots read as stale on every run. A self-hosted
|
|
151
|
+
runner that reuses its workspace opts back into the faster stat check
|
|
152
|
+
with `cache.validation: stat`. Snippets: `rigor docs ci`
|
|
153
|
+
§ "Persisting the analysis cache across runs".
|
|
144
154
|
|
|
145
155
|
## Verify
|
|
146
156
|
|
|
@@ -89,6 +89,17 @@ The manual chapter covers:
|
|
|
89
89
|
- **Helix** (`languages.toml`)
|
|
90
90
|
- **Emacs** (Eglot and lsp-mode)
|
|
91
91
|
|
|
92
|
+
**The editors are not equally cheap to wire.** Neovim / Helix / Emacs
|
|
93
|
+
are plain config snippets. **VS Code has no first-party Rigor
|
|
94
|
+
extension yet** — its path means authoring and side-loading a minimal
|
|
95
|
+
extension (a `LanguageClient` wrapper), which is a development task,
|
|
96
|
+
not a `settings.json` edit. Say so *before* starting: the developer
|
|
97
|
+
may prefer to wait for a first-class extension, and an autonomous
|
|
98
|
+
agent should treat the VS Code path as a **user handoff** (report the
|
|
99
|
+
manual's recipe; do not scaffold an unrequested extension). Phase 4's
|
|
100
|
+
verification (live diagnostics, hover) also requires a human in front
|
|
101
|
+
of the editor — an agent stops there and reports, for every editor.
|
|
102
|
+
|
|
92
103
|
### Phase 3 — apply the matching config
|
|
93
104
|
|
|
94
105
|
Open the manual's **Editor wiring** section and apply the snippet for
|
|
@@ -21,6 +21,25 @@ frozen in this file (ADR-73).
|
|
|
21
21
|
Work top to bottom. Stop at the first step that is not yet satisfied,
|
|
22
22
|
complete it, then continue.
|
|
23
23
|
|
|
24
|
+
## Autonomous / non-interactive runs
|
|
25
|
+
|
|
26
|
+
When the user has delegated the journey up front ("set this project up
|
|
27
|
+
and get it healthy; don't wait for me"), the skills below still contain
|
|
28
|
+
ask-the-user points. Handle them like this:
|
|
29
|
+
|
|
30
|
+
- A choice the user **already made in their instruction** (e.g. the
|
|
31
|
+
adoption mode) is settled — record it and proceed; do not re-ask.
|
|
32
|
+
- A low-risk, reversible, in-repo action a skill says to confirm
|
|
33
|
+
(installing an RBS collection, a `.gitignore` addition) is covered by
|
|
34
|
+
the standing delegation — do it, and note the self-answer in your
|
|
35
|
+
report.
|
|
36
|
+
- **Committing, pushing, or publishing is never covered** by a general
|
|
37
|
+
delegation — leave the files uncommitted and list them for the user.
|
|
38
|
+
- A step only a human can complete or verify (watching a CI run
|
|
39
|
+
execute, confirming live diagnostics inside an editor) is a
|
|
40
|
+
**handoff, not a stall**: do the file-level part if it is safe,
|
|
41
|
+
then report what remains for the user instead of waiting.
|
|
42
|
+
|
|
24
43
|
## Step 1 — Resolve the `rigor` command
|
|
25
44
|
|
|
26
45
|
```sh
|
|
@@ -53,9 +72,11 @@ Check the project root for `.rigor.yml` or `.rigor.dist.yml`. If
|
|
|
53
72
|
first:
|
|
54
73
|
|
|
55
74
|
```sh
|
|
56
|
-
rigor skill rigor-project-init
|
|
75
|
+
rigor skill --full rigor-project-init
|
|
57
76
|
```
|
|
58
77
|
|
|
78
|
+
(`rigor skill <name>` prints the skill body alone; `--full` inlines its
|
|
79
|
+
`references/` files too, so one call fetches the complete procedure.)
|
|
59
80
|
Follow that skill's body top to bottom (it detects the stack, picks
|
|
60
81
|
plugins, writes `.rigor.dist.yml`, and snapshots a baseline if needed),
|
|
61
82
|
then come back to Step 4.
|
|
@@ -87,7 +108,7 @@ the `describe` output, then:
|
|
|
87
108
|
- Then load the chosen skill and follow it:
|
|
88
109
|
|
|
89
110
|
```sh
|
|
90
|
-
rigor skill <chosen-skill>
|
|
111
|
+
rigor skill --full <chosen-skill>
|
|
91
112
|
```
|
|
92
113
|
|
|
93
114
|
Re-run `rigor skill describe` any time you want the next step again — it
|
|
@@ -115,8 +115,8 @@ AST walk per file — hands every matching node to the block along with a
|
|
|
115
115
|
Optionally the plugin also declares `dynamic_return(receivers:)` /
|
|
116
116
|
`narrowing_facts(methods:)` to *supply* a return type or narrowing facts
|
|
117
117
|
for call sites the core analyzer types as `Dynamic`. (`narrowing_facts`
|
|
118
|
-
was renamed from `type_specifier` in ADR-80;
|
|
119
|
-
|
|
118
|
+
was renamed from `type_specifier` in ADR-80; the old verb was removed in
|
|
119
|
+
0.3.0, so `narrowing_facts` is the only spelling.)
|
|
120
120
|
`#diagnostics_for_file`
|
|
121
121
|
is the file-rule surface for whole-file diagnostics a per-node walk can't
|
|
122
122
|
express. (`flow_contribution_for` was removed pre-1.0 in ADR-52 WD3 —
|
|
@@ -136,5 +136,5 @@ defining it now raises `ArgumentError`; use `dynamic_return` /
|
|
|
136
136
|
| Module | Read | Covers |
|
|
137
137
|
| --- | --- | --- |
|
|
138
138
|
| 1 | [`references/01-plan-and-scaffold.md`](references/01-plan-and-scaffold.md) | **Phase 1.** The gem vs project-private packaging split, directory trees for both, gemspec template, project-private path-gem / `RUBYLIB` activation, the `Rigor::Plugin::Base` skeleton, `.rigor.yml` `plugins:` wiring. |
|
|
139
|
-
| 2 | [`references/02-walker-and-types.md`](references/02-walker-and-types.md) | **Phase 2.** The `node_rule` engine-owned AST walk over Prism nodes, the `Base#diagnostic` helper, asking the analyzer for inferred types via `scope.type_of`, two-pass / lexical context (`node_file_context` / `NodeContext`), the optional `dynamic_return` / `narrowing_facts` return-type hooks (`narrowing_facts` was renamed from `type_specifier` in ADR-80, alias removed in 0.3.0; `flow_contribution_for` was removed pre-1.0 in ADR-52 WD3), calling the target library's pure methods directly rather than reimplementing them (ADR-39: `Plugin::Inflector` over the real `ActiveSupport::Inflector`; `Base.suggest` for did-you-mean), and shipping `sig/*.rbs` so the DSL's types are visible. |
|
|
139
|
+
| 2 | [`references/02-walker-and-types.md`](references/02-walker-and-types.md) | **Phase 2.** The `node_rule` engine-owned AST walk over Prism nodes, the `Base#diagnostic` helper, asking the analyzer for inferred types via `scope.type_of`, two-pass / lexical context (`node_file_context` / `NodeContext`), the optional `dynamic_return` / `narrowing_facts` return-type hooks (`narrowing_facts` was renamed from `type_specifier` in ADR-80, the alias removed in 0.3.0; `flow_contribution_for` was removed pre-1.0 in ADR-52 WD3), calling the target library's pure methods directly rather than reimplementing them (ADR-39: `Plugin::Inflector` over the real `ActiveSupport::Inflector`; `Base.suggest` for did-you-mean), and shipping `sig/*.rbs` so the DSL's types are visible. |
|
|
140
140
|
| 3 | [`references/03-test-and-ship.md`](references/03-test-and-ship.md) | **Phase 3.** Testing a plugin from outside the monorepo — fixture projects driven through `rigor check --format json`, plus pure unit tests of dispatch tables — with RSpec or Minitest. Version pinning against the pre-1.0 contract. README. Publishing to RubyGems or keeping the plugin private. |
|
|
@@ -203,8 +203,8 @@ end
|
|
|
203
203
|
```
|
|
204
204
|
|
|
205
205
|
> **`narrowing_facts` was renamed from `type_specifier` in ADR-80.**
|
|
206
|
-
>
|
|
207
|
-
>
|
|
206
|
+
> The old verb was removed in 0.3.0 — `narrowing_facts` is the only
|
|
207
|
+
> spelling a plugin can declare.
|
|
208
208
|
|
|
209
209
|
Build return types with `Rigor::Type::Combinator`:
|
|
210
210
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: rigor-plugin-review
|
|
3
3
|
description: |
|
|
4
|
-
Review an existing Rigor plugin's source against the current authoring contract and produce a prioritized upgrade path — the modernization counterpart to rigor-plugin-author. Audits config-default declaration (ADR-40), the AST-walk model (node_rule vs a hand-rolled traversal), return-type / narrowing hooks (dynamic_return / narrowing_facts, not the removed flow_contribution_for or
|
|
4
|
+
Review an existing Rigor plugin's source against the current authoring contract and produce a prioritized upgrade path — the modernization counterpart to rigor-plugin-author. Audits config-default declaration (ADR-40), the AST-walk model (node_rule vs a hand-rolled traversal), return-type / narrowing hooks (dynamic_return / narrowing_facts, not the removed flow_contribution_for or type_specifier), the ADR-60 WD4 authoring helpers (diagnostic / diagnostics_for / suggest / producer_value / read_fact), engine-collaboration vs reimplementation, cache-producer soundness, manifest-field hygiene, and doc freshness. Triggers: "review this Rigor plugin", "does my plugin follow best practices", "upgrade our rigor-prefixed plugin to the latest contract", "modernize this plugin", "is this plugin using the current API". NOT for authoring a new plugin (use rigor-plugin-author), enabling bundled plugins on a project (use rigor-plugin-tune), or tuning plugin config.
|
|
5
5
|
license: MPL-2.0
|
|
6
6
|
metadata:
|
|
7
7
|
version: 0.1.0
|
|
@@ -30,7 +30,7 @@ appears:
|
|
|
30
30
|
3. **A hand-rolled AST walk** where the engine-owned `node_rule` now
|
|
31
31
|
fits (ADR-37 / ADR-52).
|
|
32
32
|
4. **Removed / renamed hooks** still named — `flow_contribution_for`
|
|
33
|
-
(deleted, ADR-52 WD3) or `type_specifier` (
|
|
33
|
+
(deleted, ADR-52 WD3) or `type_specifier` (removed in 0.3.0; renamed to
|
|
34
34
|
`narrowing_facts`, ADR-80).
|
|
35
35
|
5. **Stale docs** — archaeology about deleted hooks, pinned version
|
|
36
36
|
references that no longer mean anything.
|
|
@@ -78,12 +78,13 @@ can.
|
|
|
78
78
|
## 3. Return-type & narrowing hooks (ADR-37 / ADR-52 / ADR-80) — `M`/`D`
|
|
79
79
|
|
|
80
80
|
**Smell:** `flow_contribution_for` (deleted in ADR-52 WD3 — *defining it
|
|
81
|
-
now raises `ArgumentError`*), or `type_specifier` (the pre-ADR-80 name
|
|
81
|
+
now raises `ArgumentError`*), or `type_specifier` (the pre-ADR-80 name,
|
|
82
|
+
removed in 0.3.0 — it now raises `NoMethodError`).
|
|
82
83
|
|
|
83
84
|
**Modern:** `dynamic_return(receivers:/methods:/file_methods:)` to
|
|
84
85
|
*supply* a return type, `narrowing_facts(methods:)` to supply
|
|
85
|
-
post-return narrowing facts. `type_specifier`
|
|
86
|
-
|
|
86
|
+
post-return narrowing facts. Rename any surviving `type_specifier` to
|
|
87
|
+
`narrowing_facts`.
|
|
87
88
|
|
|
88
89
|
The gate resolves after `#init` when passed a callable
|
|
89
90
|
(`methods: -> { [@method_name] }`), so config-derived method names work.
|
|
@@ -207,7 +208,7 @@ and put that in a CHANGELOG / migration note, not the class docstring.
|
|
|
207
208
|
misuse. MUST be clean; fix the cause, never disable the rule.
|
|
208
209
|
- `rigor plugins --strict` — the plugin still activates.
|
|
209
210
|
- `rigor plugins --capabilities` — `node_rule_types` /
|
|
210
|
-
`dynamic_return_receivers` / `
|
|
211
|
+
`dynamic_return_receivers` / `narrowing_facts_methods` reflect the
|
|
211
212
|
declarations.
|
|
212
213
|
- The integration / unit spec — green, and byte-identical across every
|
|
213
214
|
mechanical step.
|
|
@@ -17,9 +17,10 @@ and — if the project chooses it — a `.rigor-baseline.yml` snapshot.
|
|
|
17
17
|
This skill is for **users adopting Rigor on their own project**. It
|
|
18
18
|
uses the published `rigor` executable, installed standalone — Rigor
|
|
19
19
|
is a tool, not a library, so it does **not** go in the project's
|
|
20
|
-
`Gemfile`.
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
`Gemfile`. For the install channels (`mise` recommended) see the
|
|
21
|
+
manual's Installing Rigor chapter — `rigor docs manual/01-installation`
|
|
22
|
+
once any `rigor` is on `PATH`, or (pre-install)
|
|
23
|
+
<https://github.com/rigortype/rigor/blob/master/docs/manual/01-installation.md>. This skill
|
|
23
24
|
references only public CLI flags and config keys — the same surface
|
|
24
25
|
`rigor --help` documents.
|
|
25
26
|
|
|
@@ -98,6 +99,29 @@ If the project's first `rigor check` reports more than ~100 errors,
|
|
|
98
99
|
recommend acknowledge mode as the default and let the user override.
|
|
99
100
|
Below that, either mode is reasonable — ask.
|
|
100
101
|
|
|
102
|
+
Note the ordering: the error count that feeds this recommendation (and
|
|
103
|
+
the `severity_profile` choice in Phase 4) is only *measured* in
|
|
104
|
+
Phase 6's triage run. Any profile written in Phase 4 is therefore
|
|
105
|
+
**provisional** — Phase 6 revisits it against the measured count (see
|
|
106
|
+
[`references/02-configure.md`](references/02-configure.md)
|
|
107
|
+
§ "Severity profile").
|
|
108
|
+
|
|
109
|
+
## Non-interactive / agent-driven runs
|
|
110
|
+
|
|
111
|
+
This skill has several ask-the-user points (the mode choice, the RBS
|
|
112
|
+
collection install, the final gitignore-and-commit confirmation). When
|
|
113
|
+
the user delegated the onboarding up front — including the adoption
|
|
114
|
+
mode — those points resolve without blocking:
|
|
115
|
+
|
|
116
|
+
- The **mode** the user pre-declared is settled; record it and skip the
|
|
117
|
+
Phase 2 presentation.
|
|
118
|
+
- Low-risk, reversible, in-repo confirmations (`rbs collection
|
|
119
|
+
install`, the `.gitignore` addition) are covered by the delegation —
|
|
120
|
+
act, and note each self-answer in your report.
|
|
121
|
+
- **Never commit or push under a general delegation.** Run the Final
|
|
122
|
+
step's file inventory as a *report* (what was created, what to
|
|
123
|
+
commit) instead of a question, and leave the commits to the user.
|
|
124
|
+
|
|
101
125
|
## Phase outline
|
|
102
126
|
|
|
103
127
|
| Phase | What | Reference |
|
|
@@ -126,6 +150,7 @@ committed `sig/` directory.
|
|
|
126
150
|
| 2 | [`references/02-configure.md`](references/02-configure.md) | **Phase 4.** Severity-profile choice tied to the mode. The `.rigor.dist.yml` template and every key it uses. The `.rigor.dist.yml` vs `.rigor.yml` convention. |
|
|
127
151
|
| 3 | [`references/04-sig-uplift.md`](references/04-sig-uplift.md) | **Phase 5.** `rigor sig-gen --write` baseline. `rigor sig-gen --params=observed --write` attr_reader precision uplift. Handling residual `untyped` methods. Committing `sig/`. |
|
|
128
152
|
| 4 | [`references/03-baseline-and-bugs.md`](references/03-baseline-and-bugs.md) | **Phases 6–8.** `rigor triage` as the diagnosis layer. Phase 6a pre-baseline cleanup loop (`pre_eval:` for monkey-patch hints, `rbs collection install`). `rigor baseline generate` + wiring `baseline:`. Surfacing likely real bugs; sig quality FP recognition (Struct `call.wrong-arity`, `-> bot` return-type-mismatch, regex-capture `$1` FPs). The two escalation paths — write a project plugin, or open a Rigor issue. |
|
|
153
|
+
| — (optional) | [`references/05-jit-performance.md`](references/05-jit-performance.md) | **Operational, not a phase.** Run speed via a Ruby JIT: Rigor auto-enables YJIT for long runs (~5 s break-even), how to detect JIT support in your install, the override env vars, and why YJIT beats ZJIT for Rigor on Ruby 4.0. Read only when a large project's `rigor check` wall time matters. |
|
|
129
154
|
|
|
130
155
|
## Escalation paths (Phase 7 preview)
|
|
131
156
|
|
|
@@ -201,6 +226,7 @@ only in acknowledge mode). For each, give the commit recommendation:
|
|
|
201
226
|
| `sig/` | RBS skeletons from `rigor sig-gen` (Phase 5), if that phase ran. A first-class project artefact — it sharpens inference for everyone. | **Yes** — commit it (see [`references/04-sig-uplift.md`](references/04-sig-uplift.md) § "Commit the sig/ directory"). |
|
|
202
227
|
| `.rigor/` (contains `cache/`) | The per-file analysis cache `rigor check` writes to speed up re-runs. Regenerable and machine-local. | **No** — add `.rigor/` to `.gitignore`. |
|
|
203
228
|
| `.rigor.yml` | Optional per-developer local override (not written by this skill). Takes precedence over `.rigor.dist.yml`; used to opt out locally (e.g. run without the baseline). | **No** — gitignore it if a developer creates one. |
|
|
229
|
+
| `rbs_collection.lock.yaml` | Not a Rigor artefact — but if Phase 1 ran `rbs collection install`, the install may have regenerated this pre-existing project file (e.g. the stdlib gem list for the resolved Ruby). | **Yes**, but flag it separately: it is a change to an existing project file, not part of the Rigor file set. |
|
|
204
230
|
|
|
205
231
|
Recommend the two concrete actions and **ask before doing them**
|
|
206
232
|
(both touch the user's repo): (1) add `.rigor/` — and `.rigor.yml`
|
|
@@ -102,7 +102,7 @@ user as a list they can trim — do not silently enable everything.
|
|
|
102
102
|
| Rails | `rigor-actionpack`, `rigor-activerecord`, `rigor-actionmailer`, `rigor-rails-routes`, `rigor-rails-i18n`, plus `rigor-activesupport-core-ext` (almost always needed — see below) |
|
|
103
103
|
| dry-rb | `rigor-dry-types`, `rigor-dry-struct`, and `rigor-dry-schema` / `rigor-dry-validation` when those gems are present |
|
|
104
104
|
| Sinatra | `rigor-sinatra` |
|
|
105
|
-
| RSpec | `rigor-rspec` |
|
|
105
|
+
| RSpec | `rigor-rspec` — **only if `paths:` will include `spec/`**. The conventional scope (see "Path scope" above) excludes `spec/`, and a plugin whose rules only fire on spec files contributes nothing outside it. Skip it under the default scope; add it later if the project opts spec files into analysis. |
|
|
106
106
|
| Devise / Pundit / Sidekiq present | `rigor-devise` / `rigor-pundit` / `rigor-sidekiq` |
|
|
107
107
|
| Sorbet present | `rigor-sorbet` (ingests existing `sig` blocks / RBI as type sources) |
|
|
108
108
|
| plain Ruby | none required — the core analyzer covers it |
|
|
@@ -37,8 +37,11 @@ automatically and emit the message described:
|
|
|
37
37
|
|
|
38
38
|
## Severity profile — follows the mode
|
|
39
39
|
|
|
40
|
-
The `severity_profile:` key
|
|
41
|
-
|
|
40
|
+
The `severity_profile:` key sets every rule's severity — and for some
|
|
41
|
+
rules, whether they fire at all: a rule can be *off* under `lenient`
|
|
42
|
+
and surface under `balanced` / `strict` (e.g. the toplevel
|
|
43
|
+
unresolved-call rule). Switching profiles can therefore **change the
|
|
44
|
+
diagnostic count**, not just the labels. Set it from the Phase 2 mode:
|
|
42
45
|
|
|
43
46
|
| Mode | `severity_profile` | Why |
|
|
44
47
|
| --- | --- | --- |
|
|
@@ -48,6 +51,14 @@ from the Phase 2 mode:
|
|
|
48
51
|
|
|
49
52
|
`balanced` is the built-in default — omit the key to get it.
|
|
50
53
|
|
|
54
|
+
**The Phase 4 choice is provisional.** The ">100 errors" condition is
|
|
55
|
+
measured by Phase 6's triage, which has not run yet — so write the
|
|
56
|
+
best guess now and **revisit it when the triage counts land**: if the
|
|
57
|
+
measured `error` count is small, prefer `balanced` even for a large
|
|
58
|
+
acknowledge-mode app. Make any profile change **before** generating
|
|
59
|
+
the baseline (Phase 7), and re-run `rigor triage` after changing it —
|
|
60
|
+
the count the baseline snapshots must come from the final profile.
|
|
61
|
+
|
|
51
62
|
## No separate installation needed
|
|
52
63
|
|
|
53
64
|
All plugins ship **bundled inside the `rigortype` gem**. The
|
|
@@ -57,9 +68,10 @@ the gem's own load path. No Gemfile entry, no `bundle install`,
|
|
|
57
68
|
no separate gem channel.
|
|
58
69
|
|
|
59
70
|
**The `rigortype` gem itself stays out of the project's
|
|
60
|
-
`Gemfile`** — install it standalone per the manual's
|
|
61
|
-
|
|
62
|
-
|
|
71
|
+
`Gemfile`** — install it standalone per the manual's Installing
|
|
72
|
+
Rigor chapter (`rigor docs manual/01-installation`, or pre-install
|
|
73
|
+
<https://github.com/rigortype/rigor/blob/master/docs/manual/01-installation.md>;
|
|
74
|
+
`mise use gem:rigortype` is the recommended channel). The
|
|
63
75
|
project's Gemfile is untouched by this workflow.
|
|
64
76
|
|
|
65
77
|
## The template
|
|
@@ -191,6 +203,28 @@ shape):
|
|
|
191
203
|
rigor plugins --strict
|
|
192
204
|
```
|
|
193
205
|
|
|
206
|
+
### `rigor plugins` verifies activation, not analysis-time health
|
|
207
|
+
|
|
208
|
+
A plugin that activates cleanly can still degrade **during
|
|
209
|
+
analysis** — e.g. its model-index / routes-parsing stage fails on
|
|
210
|
+
this machine — and that failure surfaces only as a
|
|
211
|
+
`plugin.<id>.load-error` **warning inside the diagnostic stream**,
|
|
212
|
+
easy to miss among hundreds of lines. After the first full
|
|
213
|
+
`rigor check` / `rigor triage` run (Phase 6), check for it
|
|
214
|
+
explicitly:
|
|
215
|
+
|
|
216
|
+
```sh
|
|
217
|
+
rigor check --format json | jq '[.diagnostics[] | select(.rule | endswith("load-error"))]'
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Any hit means that plugin contributed **no diagnostics and no
|
|
221
|
+
types** for the capability that failed — reduced coverage, silently.
|
|
222
|
+
Surface it to the user rather than letting it slide into the
|
|
223
|
+
baseline: a `load-error` bucket in `.rigor-baseline.yml` would hide
|
|
224
|
+
the degradation permanently. If the error reproduces across runs,
|
|
225
|
+
report it upstream (<https://github.com/rigortype/rigor/issues>)
|
|
226
|
+
with the message text.
|
|
227
|
+
|
|
194
228
|
## Output of this module
|
|
195
229
|
|
|
196
230
|
A committed `.rigor.dist.yml` with `paths:`, `exclude:`,
|
|
@@ -14,6 +14,14 @@ the triage command instead:
|
|
|
14
14
|
rigor triage --format json
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
+
> **Parsing the JSON from a script**: diagnostic messages can carry
|
|
18
|
+
> non-ASCII bytes from the project's own strings (i18n content,
|
|
19
|
+
> comments), which makes a naive `File.read` + `JSON.parse` raise
|
|
20
|
+
> `Encoding::InvalidByteSequenceError`. Force UTF-8 and scrub —
|
|
21
|
+
> `JSON.parse(File.read(f, encoding: "UTF-8").scrub)` — or use `jq`,
|
|
22
|
+
> which handles it natively. The same applies to
|
|
23
|
+
> `rigor check --format json`.
|
|
24
|
+
|
|
17
25
|
`rigor triage` runs the same analysis as `rigor check`, then returns
|
|
18
26
|
a structured summary instead of the per-line dump. It is read-only
|
|
19
27
|
and advisory — it never edits config and never writes a baseline.
|
|
@@ -79,6 +87,18 @@ If triage flags `activesupport-core-ext` (or any config gap),
|
|
|
79
87
|
baseline and the real-bug review should both run against the
|
|
80
88
|
post-config diagnostic set, not the inflated one.
|
|
81
89
|
|
|
90
|
+
### Check for analysis-time plugin failures before baselining
|
|
91
|
+
|
|
92
|
+
Scan the `distribution` for any `plugin.<id>.load-error` rule (or run
|
|
93
|
+
the `jq` one-liner in
|
|
94
|
+
[`02-configure.md`](02-configure.md) § "`rigor plugins` verifies
|
|
95
|
+
activation, not analysis-time health"). A `load-error` means the
|
|
96
|
+
plugin activated but one of its capabilities failed while analysing
|
|
97
|
+
*this* project — its diagnostics and types are silently absent from
|
|
98
|
+
the stream you are about to snapshot. Surface it to the user and
|
|
99
|
+
resolve (or at least record) it **before** Phase 7: a `load-error`
|
|
100
|
+
bucket baked into the baseline hides the degradation permanently.
|
|
101
|
+
|
|
82
102
|
## Phase 6a — Pre-baseline cleanup
|
|
83
103
|
|
|
84
104
|
Before generating the baseline, **apply every quick fix that triage
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Reference 05 — JIT and run speed (YJIT / ZJIT)
|
|
2
|
+
|
|
3
|
+
Operational, not part of onboarding proper: how Rigor uses a Ruby JIT to
|
|
4
|
+
speed up analysis, how to tell whether it is active in your install, and the
|
|
5
|
+
trade-off (and break-even point) if you want to override the default. Read
|
|
6
|
+
this when a project is large enough that `rigor check` wall time matters —
|
|
7
|
+
Rails monorepos especially. On a small project you can ignore it entirely:
|
|
8
|
+
Rigor already does the right thing with no configuration.
|
|
9
|
+
|
|
10
|
+
## TL;DR
|
|
11
|
+
|
|
12
|
+
- **You usually do nothing.** Rigor enables YJIT automatically once a run has
|
|
13
|
+
been going long enough to be worth it, and leaves it off for short runs
|
|
14
|
+
where it would only add warm-up cost. This is on by default.
|
|
15
|
+
- **Break-even is ~5 seconds.** A JIT pays for its warm-up only on longer
|
|
16
|
+
runs; below it, JIT-on is *slower*. Rigor's 5-second deadline is set at
|
|
17
|
+
that crossover.
|
|
18
|
+
- **YJIT, not ZJIT.** Ruby 4.0 ships both JITs, but YJIT is faster for Rigor's
|
|
19
|
+
workload — measurably so on every run large enough for a JIT to help. Rigor
|
|
20
|
+
uses YJIT; there is no reason to force ZJIT.
|
|
21
|
+
|
|
22
|
+
## How Rigor uses a JIT
|
|
23
|
+
|
|
24
|
+
`rigor check` and `rigor coverage` arm a background timer at the start of a
|
|
25
|
+
run. If the run is still going after ~5 seconds, YJIT is switched on for the
|
|
26
|
+
remainder; a run that finishes first never pays any JIT cost. So:
|
|
27
|
+
|
|
28
|
+
- A quick check (a small project, or a warm cache hit) runs on the plain
|
|
29
|
+
interpreter — no warm-up penalty.
|
|
30
|
+
- A long cold analysis of a large project gets YJIT for its dominant tail,
|
|
31
|
+
where the speed-up more than repays the warm-up.
|
|
32
|
+
|
|
33
|
+
Diagnostics and their counts are identical whether or not the JIT engages —
|
|
34
|
+
a JIT changes wall time only, never results.
|
|
35
|
+
|
|
36
|
+
The long-running servers `rigor lsp` and `rigor mcp` turn YJIT on at start
|
|
37
|
+
instead, since they always run long.
|
|
38
|
+
|
|
39
|
+
## Is a JIT available in my install?
|
|
40
|
+
|
|
41
|
+
The deferred-YJIT feature only helps if the Ruby your `rigor` runs on was
|
|
42
|
+
built with YJIT support. Rigor is installed standalone (see the manual's
|
|
43
|
+
Installing Rigor chapter: `rigor docs manual/01-installation`), so this is a
|
|
44
|
+
property of *that* Ruby, which may differ from your project's Ruby.
|
|
45
|
+
|
|
46
|
+
Check the Ruby that runs `rigor`:
|
|
47
|
+
|
|
48
|
+
```sh
|
|
49
|
+
ruby --yjit -e 'require "rbconfig"; puts "YJIT: #{RbConfig::CONFIG["YJIT_SUPPORT"]}"; puts "ZJIT: #{RbConfig::CONFIG["ZJIT_SUPPORT"]}"'
|
|
50
|
+
# YJIT: yes → the deferred-YJIT engages automatically on long runs
|
|
51
|
+
# YJIT: no → the feature is a silent no-op; runs use the interpreter
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
- Most prebuilt CRuby 3.3+ (mise / rbenv / asdf, the recommended install
|
|
55
|
+
channels) ship with YJIT built in but **not** ZJIT — a prebuilt
|
|
56
|
+
mise Ruby 4.0, for example, reports `YJIT: yes` / `ZJIT: no`, because
|
|
57
|
+
ZJIT needs `rustc` at build time and the prebuilt binary omits it. That
|
|
58
|
+
is exactly the configuration Rigor wants: YJIT is the faster JIT for
|
|
59
|
+
Rigor (see the YJIT-vs-ZJIT section below), so a missing ZJIT costs you
|
|
60
|
+
nothing.
|
|
61
|
+
- The Nix flake package of Rigor bundles a Ruby with **both** JITs; that
|
|
62
|
+
is the only common install where `ZJIT: yes` — and Rigor still uses
|
|
63
|
+
YJIT there.
|
|
64
|
+
- A YJIT-less Ruby is not an error — Rigor just runs interpreted, correctly.
|
|
65
|
+
|
|
66
|
+
## Overriding the default
|
|
67
|
+
|
|
68
|
+
Three environment variables, documented in the CLI reference's
|
|
69
|
+
"Environment variables" section (`rigor docs manual/02-cli-reference`):
|
|
70
|
+
|
|
71
|
+
| Want | Do |
|
|
72
|
+
| --- | --- |
|
|
73
|
+
| Force YJIT on for the **whole** run (including the first 5 s) | `RUBYOPT="--yjit" rigor check …` |
|
|
74
|
+
| Turn Rigor's JIT off entirely | `RIGOR_DISABLE_YJIT=1 rigor check …` |
|
|
75
|
+
| Change the deadline (advanced) | `RIGOR_YJIT_DEADLINE=<seconds> rigor check …` |
|
|
76
|
+
|
|
77
|
+
When would you override?
|
|
78
|
+
|
|
79
|
+
- **Force-on** helps only if *every* run you care about is long (a big
|
|
80
|
+
monorepo in CI where even the fast path is > 5 s). On mixed workloads the
|
|
81
|
+
default deadline is better — it protects your quick local re-checks.
|
|
82
|
+
- **Off** is the escape hatch if a JIT ever misbehaves on your platform, or to
|
|
83
|
+
get a clean interpreter baseline when measuring.
|
|
84
|
+
|
|
85
|
+
## Why the ~5-second break-even (the trade-off)
|
|
86
|
+
|
|
87
|
+
A JIT compiles hot code to machine code, which costs time up front and repays
|
|
88
|
+
it over the rest of the run. On a short run the compilation never amortizes,
|
|
89
|
+
so JIT-on loses. Measured cold `rigor check`, by project size:
|
|
90
|
+
|
|
91
|
+
| run size | interpreter | YJIT | effect |
|
|
92
|
+
| --- | --- | --- | --- |
|
|
93
|
+
| ~1.6 s (small lib) | baseline | **+67 %** | JIT-on loses badly |
|
|
94
|
+
| ~4 s (medium lib) | baseline | ~break-even | a wash |
|
|
95
|
+
| ~8 s | baseline | **−13 %** | JIT-on wins |
|
|
96
|
+
| ~19 s (Rails monorepo) | baseline | **−20 %** | wins more |
|
|
97
|
+
| ~17 s (large Rails app) | baseline | **−28 %** | biggest win |
|
|
98
|
+
|
|
99
|
+
The deadline parks the enable point just past the loss zone: runs that finish
|
|
100
|
+
before ~5 s never pay warm-up; longer runs collect the win on their tail.
|
|
101
|
+
|
|
102
|
+
## YJIT vs ZJIT (Ruby 4.0)
|
|
103
|
+
|
|
104
|
+
Ruby 4.0 ships a second JIT, **ZJIT** (a newer method-based JIT;
|
|
105
|
+
[upstream docs](https://docs.ruby-lang.org/en/master/jit/zjit_md.html)). It
|
|
106
|
+
is real and correctness-safe on Rigor (identical diagnostics), but **slower
|
|
107
|
+
than YJIT for Rigor's workload** at Ruby 4.0. Measured cold, on the runs where
|
|
108
|
+
a JIT helps at all:
|
|
109
|
+
|
|
110
|
+
| target | YJIT vs interpreter | ZJIT vs interpreter | ZJIT vs YJIT |
|
|
111
|
+
| --- | --- | --- | --- |
|
|
112
|
+
| ~8 s | −13 % | −4 % | +10 % |
|
|
113
|
+
| ~19 s (monorepo) | −20 % | −13 % | +10 % |
|
|
114
|
+
| ~17 s (large app) | −28 % | −14 % | +21 % |
|
|
115
|
+
|
|
116
|
+
YJIT wins on every workload large enough to warrant a JIT; ZJIT's peak
|
|
117
|
+
speed-up is roughly half. ZJIT only edges YJIT on tiny runs — the zone where
|
|
118
|
+
you want no JIT at all. **Do not force ZJIT** (`RUBYOPT="--zjit"`) for Rigor;
|
|
119
|
+
the automatic YJIT is faster. Full measurement + methodology (a
|
|
120
|
+
contributor-side note, web only):
|
|
121
|
+
<https://github.com/rigortype/rigor/blob/master/docs/notes/20260714-jit-evaluation-yjit-zjit-ruby40.md>.
|
|
122
|
+
|
|
123
|
+
This verdict is Ruby-4.0-specific; ZJIT is younger than YJIT and improving, so
|
|
124
|
+
it may close the gap in a later Ruby. Until then YJIT is the right default —
|
|
125
|
+
which is what Rigor already does.
|