rigortype 0.4.4 → 0.4.6
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 -5
- data/docs/handbook/11-sig-gen.md +100 -18
- data/docs/manual/02-cli-reference.md +85 -16
- data/docs/manual/03-configuration.md +12 -9
- data/docs/manual/04-diagnostics.md +17 -6
- data/docs/manual/05-inspecting-types.md +2 -2
- data/docs/manual/06-baseline.md +1 -1
- data/docs/manual/07-plugins.md +2 -1
- data/docs/manual/08-skills.md +7 -0
- data/docs/manual/10-mcp-server.md +17 -2
- data/docs/manual/12-caching.md +8 -1
- data/docs/manual/18-removing-dead-code.md +18 -7
- data/docs/manual/plugins/rigor-actionmailer.md +30 -0
- data/docs/manual/plugins/rigor-actionpack.md +30 -0
- data/docs/manual/plugins/rigor-devise.md +15 -0
- data/docs/manual/plugins/rigor-rspec.md +102 -1
- data/lib/rigor/analysis/buffer_binding.rb +14 -0
- data/lib/rigor/analysis/check_rules/clause_coverage.rb +134 -0
- data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +30 -9
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +142 -15
- data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +115 -7
- data/lib/rigor/analysis/check_rules/rbs_receiver_surface.rb +253 -0
- data/lib/rigor/analysis/check_rules/rebinding_block_callees/dispatch.rb +112 -0
- data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +394 -0
- data/lib/rigor/analysis/check_rules/ruby_deprecations.rb +7 -0
- data/lib/rigor/analysis/check_rules/rule_ids.rb +3 -1
- data/lib/rigor/analysis/check_rules/source_arity.rb +32 -3
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +64 -4
- data/lib/rigor/analysis/check_rules.rb +319 -21
- data/lib/rigor/analysis/dependency_recorder.rb +11 -0
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +2 -1
- data/lib/rigor/analysis/edit_replay.rb +67 -0
- data/lib/rigor/analysis/incremental.rb +86 -0
- data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +36 -18
- data/lib/rigor/analysis/incremental_session.rb +336 -46
- data/lib/rigor/analysis/path_expansion.rb +44 -1
- data/lib/rigor/analysis/reachability/graph.rb +90 -13
- data/lib/rigor/analysis/reachability/scan.rb +279 -39
- data/lib/rigor/analysis/require_relative_closure.rb +100 -0
- data/lib/rigor/analysis/rule_catalog.rb +22 -5
- data/lib/rigor/analysis/run_disclosure_credit.rb +80 -0
- data/lib/rigor/analysis/run_disclosure_ledger.rb +155 -0
- data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -2
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +11 -3
- data/lib/rigor/analysis/runner/pool_coordinator.rb +82 -16
- data/lib/rigor/analysis/runner/project_pre_passes.rb +45 -3
- data/lib/rigor/analysis/runner/scan_environment.rb +51 -0
- data/lib/rigor/analysis/runner.rb +222 -111
- data/lib/rigor/analysis/severity_stamp.rb +11 -0
- data/lib/rigor/analysis/worker_session.rb +24 -1
- data/lib/rigor/cache/descriptor.rb +40 -7
- data/lib/rigor/cache/engine_source.rb +3 -2
- data/lib/rigor/cache/incremental_snapshot.rb +49 -8
- data/lib/rigor/cache/rbs_descriptor.rb +2 -1
- data/lib/rigor/cache/store.rb +4 -3
- data/lib/rigor/cli/check_command.rb +93 -4
- data/lib/rigor/cli/command.rb +3 -1
- data/lib/rigor/cli/diff_command.rb +2 -1
- data/lib/rigor/cli/docs_command.rb +3 -2
- data/lib/rigor/cli/doctor_command.rb +1 -1
- data/lib/rigor/cli/explain_command.rb +3 -2
- data/lib/rigor/cli/lens_command.rb +5 -5
- data/lib/rigor/cli/mcp_command.rb +2 -1
- data/lib/rigor/cli/options.rb +20 -3
- data/lib/rigor/cli/plugin_command.rb +2 -1
- data/lib/rigor/cli/plugins_command.rb +7 -1
- data/lib/rigor/cli/plugins_renderer.rb +24 -2
- data/lib/rigor/cli/show_bleedingedge_command.rb +2 -1
- data/lib/rigor/cli/sig_gen_command.rb +73 -13
- data/lib/rigor/cli/skill_command.rb +2 -1
- data/lib/rigor/cli/skill_describe.rb +3 -2
- data/lib/rigor/cli/type_of_command.rb +2 -1
- data/lib/rigor/cli/type_of_project_scope.rb +27 -0
- data/lib/rigor/cli/unused_command.rb +10 -7
- data/lib/rigor/cli.rb +3 -2
- data/lib/rigor/config_audit.rb +72 -16
- data/lib/rigor/configuration/severity_profile.rb +20 -2
- data/lib/rigor/configuration.rb +43 -7
- data/lib/rigor/effects/signature_sources.rb +3 -1
- data/lib/rigor/environment/bundle_sig_discovery.rb +3 -2
- data/lib/rigor/environment/gated_signature_guard.rb +2 -1
- data/lib/rigor/environment/gated_signature_plan.rb +2 -1
- data/lib/rigor/environment/installed_gem_set.rb +2 -1
- data/lib/rigor/environment/missing_gem_constant_index.rb +4 -3
- data/lib/rigor/environment/rbs_loader.rb +136 -7
- data/lib/rigor/environment/required_features.rb +136 -29
- data/lib/rigor/environment/ruby_library_roots.rb +122 -0
- data/lib/rigor/environment.rb +4 -2
- data/lib/rigor/inference/block_call_timing.rb +7 -1
- data/lib/rigor/inference/builtins/method_catalog.rb +2 -1
- data/lib/rigor/inference/callee_mutation_chain.rb +115 -0
- data/lib/rigor/inference/captured_locals.rb +44 -13
- data/lib/rigor/inference/clause_widening.rb +90 -0
- data/lib/rigor/inference/deep_erasure.rb +138 -0
- data/lib/rigor/inference/def_return_typer.rb +159 -9
- data/lib/rigor/inference/definer_resolution.rb +101 -20
- data/lib/rigor/inference/error_info.rb +17 -3
- data/lib/rigor/inference/expression_typer.rb +97 -18
- data/lib/rigor/inference/global_write_census.rb +36 -1
- data/lib/rigor/inference/intervening_ivar_reset.rb +303 -0
- data/lib/rigor/inference/ivar_writer_census.rb +195 -0
- data/lib/rigor/inference/loop_value_record.rb +53 -0
- data/lib/rigor/inference/macro_block_self_type.rb +20 -6
- data/lib/rigor/inference/match_rebinding/frame.rb +19 -0
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +278 -42
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +6 -2
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +50 -2
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +53 -20
- data/lib/rigor/inference/method_dispatcher.rb +113 -19
- data/lib/rigor/inference/module_function_state/spellings.rb +64 -0
- data/lib/rigor/inference/module_function_state.rb +149 -42
- data/lib/rigor/inference/mutation_widening.rb +5 -2
- data/lib/rigor/inference/narrowing.rb +116 -11
- data/lib/rigor/inference/object_mixins.rb +13 -1
- data/lib/rigor/inference/parameter_inference_collector.rb +4 -26
- data/lib/rigor/inference/project_patched_methods.rb +168 -4
- data/lib/rigor/inference/project_patched_mixins.rb +162 -0
- data/lib/rigor/inference/project_patched_scanner.rb +51 -12
- data/lib/rigor/inference/project_patched_visibility.rb +214 -0
- data/lib/rigor/inference/project_seed_tables.rb +121 -0
- data/lib/rigor/inference/project_yielder.rb +128 -0
- data/lib/rigor/inference/refined_dispatch/constructed_class.rb +113 -0
- data/lib/rigor/inference/refined_dispatch/own_method.rb +41 -12
- data/lib/rigor/inference/reflective_ivar_write.rb +6 -6
- data/lib/rigor/inference/return_barrier.rb +16 -0
- data/lib/rigor/inference/scope_indexer/certainty.rb +9 -0
- data/lib/rigor/inference/scope_indexer/declared_ivar_types.rb +64 -0
- data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +162 -0
- data/lib/rigor/inference/scope_indexer/unseen_ivar_writers.rb +133 -0
- data/lib/rigor/inference/scope_indexer.rb +1144 -197
- data/lib/rigor/inference/self_call_ivar_writes.rb +29 -12
- data/lib/rigor/inference/singleton_external_ahead.rb +64 -0
- data/lib/rigor/inference/singleton_hook_decline.rb +7 -0
- data/lib/rigor/inference/statement_evaluator.rb +1088 -213
- data/lib/rigor/inference/synthetic_method_index.rb +23 -0
- data/lib/rigor/inference/synthetic_method_scanner/project_members.rb +130 -0
- data/lib/rigor/inference/synthetic_method_scanner.rb +72 -12
- data/lib/rigor/inference/unthreaded_rebinds.rb +3 -1
- data/lib/rigor/language_server/buffer_table.rb +6 -0
- data/lib/rigor/language_server/diagnostic_publisher.rb +20 -6
- data/lib/rigor/language_server/project_context.rb +59 -3
- data/lib/rigor/language_server/server.rb +5 -3
- data/lib/rigor/lens/contract_reader.rb +45 -2
- data/lib/rigor/lens/document.rb +2 -1
- data/lib/rigor/lens/file_reader.rb +12 -2
- data/lib/rigor/lens/members.rb +16 -2
- data/lib/rigor/lens/run.rb +34 -7
- data/lib/rigor/mcp/server.rb +11 -3
- data/lib/rigor/plugin/base.rb +29 -13
- data/lib/rigor/plugin/io_boundary.rb +42 -6
- data/lib/rigor/plugin/isolation.rb +4 -3
- data/lib/rigor/plugin/manifest.rb +29 -3
- data/lib/rigor/plugin/registry.rb +38 -0
- data/lib/rigor/protection/discovery_seed.rb +64 -36
- data/lib/rigor/protection/mutation_cache.rb +8 -2
- data/lib/rigor/rooted_glob.rb +36 -0
- data/lib/rigor/scope/discovery_index.rb +5 -0
- data/lib/rigor/scope/resolution_chain/relevance.rb +14 -0
- data/lib/rigor/scope/resolution_chain.rb +40 -5
- data/lib/rigor/scope.rb +59 -7
- data/lib/rigor/sig_gen/alias_index.rb +2 -1
- data/lib/rigor/sig_gen/classification.rb +4 -1
- data/lib/rigor/sig_gen/generator.rb +465 -49
- data/lib/rigor/sig_gen/layout_index.rb +5 -2
- data/lib/rigor/sig_gen/observation_collector.rb +141 -12
- data/lib/rigor/sig_gen/rbs_validity.rb +1 -1
- data/lib/rigor/sig_gen/renderer.rb +16 -155
- data/lib/rigor/sig_gen/skip_reason_catalog.rb +11 -0
- data/lib/rigor/sig_gen/type_elaborator.rb +21 -5
- data/lib/rigor/sig_gen/write_reporter.rb +203 -0
- data/lib/rigor/sig_gen/write_result.rb +3 -1
- data/lib/rigor/sig_gen/writer.rb +5 -3
- data/lib/rigor/signature_path_audit.rb +4 -2
- data/lib/rigor/type/combinator.rb +4 -3
- data/lib/rigor/type/constant.rb +54 -3
- data/lib/rigor/type/hash_shape.rb +14 -9
- data/lib/rigor/type/struct_instance.rb +3 -2
- data/lib/rigor/version.rb +1 -1
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +1 -1
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +61 -44
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +20 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/render_locals.rb +6 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +1 -1
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +13 -0
- data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +4 -3
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +1 -1
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +4 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +1 -1
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +1 -1
- data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_discoverer.rb +1 -1
- data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +5 -3
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +7 -6
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +38 -9
- data/plugins/rigor-ffi/lib/rigor/plugin/ffi/target_detector.rb +1 -1
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +1 -1
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +1 -1
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +7 -2
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -5
- data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +18 -17
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +102 -11
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +1 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +4 -2
- data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +4 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +2 -2
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +2 -2
- data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer.rb +1 -1
- data/sig/rigor/analysis/baseline.rbs +3 -2
- data/sig/rigor/effects/file_collection.rbs +5 -6
- data/sig/rigor/effects/registry.rbs +8 -6
- data/sig/rigor/effects/summary.rbs +6 -5
- data/sig/rigor/environment.rbs +5 -3
- data/sig/rigor/inference/optimistic_origin.rbs +11 -0
- data/sig/rigor/inference.rbs +6 -0
- data/sig/rigor/plugin/additional_initializer.rbs +5 -5
- data/sig/rigor/plugin/effect_ancestry.rbs +6 -4
- data/sig/rigor/plugin/effect_attribution.rbs +25 -10
- data/sig/rigor/plugin/effect_edge.rbs +12 -5
- data/sig/rigor/plugin/effect_entry_points.rbs +8 -3
- data/sig/rigor/plugin/io_boundary.rbs +5 -2
- data/sig/rigor/plugin/manifest.rbs +16 -18
- data/sig/rigor/plugin/protocol_contract.rbs +10 -9
- data/sig/rigor/plugin/registry.rbs +1 -1
- data/sig/rigor/rooted_glob.rbs +7 -0
- data/sig/rigor/scope.rbs +34 -52
- data/sig/rigor/type.rbs +0 -6
- data/skills/rigor-doctor/references/01-checks.md +23 -11
- data/skills/rigor-type-oracle/references/01-oracle-commands.md +2 -1
- metadata +32 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 608dc6b03dfc9267330c24c6493f3f4ee2d025ac5ce01a044202828fc282e258
|
|
4
|
+
data.tar.gz: 395692680ea16ec6dc42d4187fc57c10ce00731eea4cd1d03176fcc2daa07c14
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c7145aafeaf9fd22fe3759970c9b4eaafd8720796048641a387ec7a02d4e579689fde37540db686da9904b7d8419a6338b9db7caa38997d7184960c346253f5a
|
|
7
|
+
data.tar.gz: bff814da943de361c7c1e5bee99fd0e4847d16bb8a9bd3e36609e69246e8eb0e141c6c6e87b425720d709e2277195d9a14100a3018fef75670361fe2acd2160b
|
data/README.md
CHANGED
|
@@ -106,7 +106,7 @@ https://raw.githubusercontent.com/rigortype/rigor/refs/heads/master/docs/install
|
|
|
106
106
|
```
|
|
107
107
|
|
|
108
108
|
Ready-made prompts — 日本語, 简体中文, 繁體中文, 한국어, Español,
|
|
109
|
-
Português, Français, Deutsch, Italiano, Tiếng Việt, ภาษาไทย,
|
|
109
|
+
Português (Brasil), Português, Français, Deutsch, Italiano, Tiếng Việt, ภาษาไทย,
|
|
110
110
|
Bahasa Indonesia, Polski, Українська, Русский, Română, Türkçe,
|
|
111
111
|
العربية, فارسی — are in
|
|
112
112
|
the [installation guide](https://rigor.typedduck.fail/manual/01-installation/#set-up-in-your-language).
|
|
@@ -232,7 +232,7 @@ rigor docs --list # list every bundled page
|
|
|
232
232
|
|
|
233
233
|
## Status
|
|
234
234
|
|
|
235
|
-
Current release: **`v0.4.
|
|
235
|
+
Current release: **`v0.4.6`** (2026-10-23) — on the
|
|
236
236
|
`0.4.x` line, continuing the evaluation era opened by `v0.2.0`, the first
|
|
237
237
|
publicly-announced (general / evaluation) release. The line publishes
|
|
238
238
|
an enumerated [compatibility surface](docs/compatibility.md) as a
|
|
@@ -256,9 +256,9 @@ Mozilla Public License Version 2.0. See [`LICENSE`](LICENSE).
|
|
|
256
256
|
|
|
257
257
|
## Sponsors
|
|
258
258
|
|
|
259
|
-
Rigor is built and maintained by one person, who has
|
|
260
|
-
|
|
261
|
-
|
|
259
|
+
Rigor is built and maintained by one person, who has put nearly every
|
|
260
|
+
spare hour of personal time into it since development began in April
|
|
261
|
+
2026. Sustainable development depends on sustainable funding.
|
|
262
262
|
|
|
263
263
|
**The current monthly budget is well short of the $200 minimum needed
|
|
264
264
|
to keep this pace going; $600 or more would let me work on it
|
data/docs/handbook/11-sig-gen.md
CHANGED
|
@@ -165,6 +165,11 @@ The `sig.skipped.*` reasons are:
|
|
|
165
165
|
RBS for it, so `--write` and `--check` are unaffected. Its
|
|
166
166
|
JSON row has no `class` key. `def self.x`, a `def` inside a
|
|
167
167
|
block body and one inside `class << obj` are not reported.
|
|
168
|
+
- `sig.skipped.uncertain-module-function` — a bare `public`,
|
|
169
|
+
`private` or `protected` that may not run (under an `if`, in
|
|
170
|
+
a block) sits between a bare `module_function` and the
|
|
171
|
+
method, so it is either a module function or an instance
|
|
172
|
+
method only. sig-gen writes neither.
|
|
168
173
|
- `sig.skipped.unrenderable-rbs` — the signature Rigor
|
|
169
174
|
rendered for this method does not parse as RBS. This one
|
|
170
175
|
is a **bug in Rigor**, not a property of your code: every
|
|
@@ -527,8 +532,23 @@ A declared `untyped` that you wrote in `sig/` is treated
|
|
|
527
532
|
the same way for a value literal: an Integer, String or
|
|
528
533
|
Symbol the body proves is widened to its class (`-> 1`
|
|
529
534
|
becomes `-> Integer`, `-> ("S" | nil)` becomes
|
|
530
|
-
`-> (String | nil)`),
|
|
531
|
-
|
|
535
|
+
`-> (String | nil)`), and `true` or `false` is widened to
|
|
536
|
+
`bool` (written `(false | true)`), so a subclass that
|
|
537
|
+
overrides the method with the other one is not reported as
|
|
538
|
+
widening the return. A body that returns only `nil` keeps
|
|
539
|
+
the `untyped`: it is the shape of a template method a
|
|
540
|
+
subclass fills in, and `-> nil` would make every override
|
|
541
|
+
that returns a value a widening. A literal inside a tuple, a
|
|
542
|
+
record or a type argument is widened the same way while the
|
|
543
|
+
shape stays (`-> [1, "a"]` becomes `-> [Integer, String]`,
|
|
544
|
+
`-> { a: 1 }` becomes `-> { a: Integer }`); of the generic
|
|
545
|
+
types, only the arguments of `Array`, `Hash`, `Set`, `Range`
|
|
546
|
+
and `Enumerator` are widened. `nil` inside a union or a shape
|
|
547
|
+
and a computed `Float` are proposed as they are. When the
|
|
548
|
+
method also carries an inline `# @rbs` annotation for its
|
|
549
|
+
parameters only, a return holding a literal that would be
|
|
550
|
+
widened, nested or not, is not proposed at all and the
|
|
551
|
+
`untyped` stays. An `untyped` that
|
|
532
552
|
only the inline reader synthesizes for a `def` you did not
|
|
533
553
|
annotate keeps the literal, as does a method no `.rbs`
|
|
534
554
|
declares — the literal is still the strictest thing the body
|
|
@@ -590,9 +610,16 @@ list is the current state):
|
|
|
590
610
|
`attr_writer` / `attr_accessor` ivar reads untyped.
|
|
591
611
|
The accessor, and any other method returning that
|
|
592
612
|
ivar, is skipped even beside a concrete write such as
|
|
593
|
-
`@logger = Logger.new
|
|
594
|
-
|
|
595
|
-
from.
|
|
613
|
+
`@logger = Logger.new`. `--params=observed` types an
|
|
614
|
+
`attr_reader` from the constructor parameter its ivar
|
|
615
|
+
is assigned from (`Box.new(0)` gives
|
|
616
|
+
`def count: () -> 0`) only when that assignment is the
|
|
617
|
+
ivar's only write in the class's body in that file: an
|
|
618
|
+
attr writer, a hand-written setter, or a write in any
|
|
619
|
+
other instance method leaves both reader and writer
|
|
620
|
+
skipped. A writer declared in another file, in a
|
|
621
|
+
superclass or module, or through `class_eval` from
|
|
622
|
+
outside is not seen. The
|
|
596
623
|
generator emits the long-form `def name: () -> T`
|
|
597
624
|
spelling so the writer's merge path applies unchanged;
|
|
598
625
|
existing short-form `attr_reader name: T` declarations
|
|
@@ -616,9 +643,28 @@ two are wired today, one is reserved.
|
|
|
616
643
|
| Policy | Behaviour |
|
|
617
644
|
| --- | --- |
|
|
618
645
|
| `untyped` (default) | Every parameter is spelled `untyped`. No inference-derived parameter contract is imposed on future callers. The user retains complete authorship over parameter typing. |
|
|
619
|
-
| `observed` | Collect argument types from every call site under `--observe=PATH...` (defaults to the configured `test_paths:`, or whichever of `spec/` and `test/` exist), union per parameter position, erase to RBS, emit the union. With no test root to observe, or a declared root that does not exist, sig-gen says so on stderr. |
|
|
646
|
+
| `observed` | Collect argument types from every call site under `--observe=PATH...` (defaults to the configured `test_paths:`, or whichever of `spec/` and `test/` exist), union per parameter position, erase to RBS, emit the union. With no test root to observe, or a declared root that does not exist or is not read, sig-gen says so on stderr, and `--format=json` carries the same fact in a top-level `observation` key (below). |
|
|
620
647
|
| `observed-strict` | Reserved. Will additionally widen to capability roles (`_ToStr`, `_ToS`, …) once the role catalog ships. Currently rejected with a usage error. |
|
|
621
648
|
|
|
649
|
+
Under `--params=observed`, the `--print` / `--diff` JSON payload gains
|
|
650
|
+
a top-level `observation` key next to `candidates`, so a consumer that
|
|
651
|
+
never reads stderr (the MCP `rigor_sig_gen` tool included) can tell
|
|
652
|
+
"no test root was observed" from "the tests pass no typed arguments":
|
|
653
|
+
|
|
654
|
+
```json
|
|
655
|
+
{ "candidates": [ … ],
|
|
656
|
+
"observation": { "paths": ["/app/spec"], "missing": ["/app/tests"] } }
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
`paths` are the roots the collector reads (a directory or a `.rb`
|
|
660
|
+
file); `missing` are the other declared roots: ones that do not exist,
|
|
661
|
+
or exist but are not read (`README.md`). An identical repeat is listed once. `paths: []` with `missing: []` means
|
|
662
|
+
there was no test root at all. Paths are spelled as the run resolved
|
|
663
|
+
them: `--observe=PATH` as typed, `test_paths:` as absolute paths, resolved against the
|
|
664
|
+
config file's directory, auto-detected `spec/` / `test/` absolute. The key is
|
|
665
|
+
absent under `--params=untyped`, and absent from the `--write` and
|
|
666
|
+
`--check` JSON payloads, which have their own shapes.
|
|
667
|
+
|
|
622
668
|
The default deliberately favours `untyped` because of
|
|
623
669
|
[ADR-5](../adr/5-robustness-principle.md)'s clause 2: a
|
|
624
670
|
method's parameter contract should be the **most permissive**
|
|
@@ -704,7 +750,7 @@ output it got before.
|
|
|
704
750
|
## RSpec-aware observations
|
|
705
751
|
|
|
706
752
|
When the observed test roots hold an RSpec suite, the
|
|
707
|
-
generator recognises
|
|
753
|
+
generator recognises four RSpec-shaped binding patterns
|
|
708
754
|
and uses them to type receivers that would otherwise
|
|
709
755
|
degrade to `Dynamic[top]`:
|
|
710
756
|
|
|
@@ -712,10 +758,12 @@ degrade to `Dynamic[top]`:
|
|
|
712
758
|
RSpec.describe Calc do
|
|
713
759
|
subject { Calc.new } # binds :subject → Nominal[Calc]
|
|
714
760
|
let(:other) { Calc.new } # binds :other → Nominal[Calc]
|
|
761
|
+
before { @calc = Calc.new } # binds @calc → Nominal[Calc]
|
|
715
762
|
|
|
716
763
|
it "..." do
|
|
717
764
|
subject.greet("Alice") # observed: Calc#greet receives String
|
|
718
765
|
other.greet("Bob") # observed: same
|
|
766
|
+
@calc.greet("Carol") # observed: same
|
|
719
767
|
described_class.new.add(1, 2) # observed: Calc#add receives Integer, Integer
|
|
720
768
|
end
|
|
721
769
|
end
|
|
@@ -724,17 +772,36 @@ end
|
|
|
724
772
|
The recogniser handles `RSpec.describe Foo`, bare
|
|
725
773
|
`describe Foo` (no `RSpec.` receiver), `subject { … }`,
|
|
726
774
|
`subject(:name) { … }`, `let(:name) { … }`, `let!(:name)`,
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
775
|
+
an ivar assigned by a top-level statement of a `before`
|
|
776
|
+
block, and `described_class.new(...)`. Same-name `subject` /
|
|
777
|
+
`let` bindings across nested scopes are last-wins; the
|
|
778
|
+
recogniser does not re-implement RSpec's full scope rules —
|
|
779
|
+
the typical one-spec-file shape is the target. A `before`
|
|
780
|
+
ivar is scoped: it types reads only inside the `describe` /
|
|
781
|
+
`context` block that holds the `before`, through nested
|
|
782
|
+
`describe` / `context` / `it` / hook blocks but not through
|
|
783
|
+
any other block (`Class.new { … }`, `instance_eval { … }`,
|
|
784
|
+
`each { … }`), and a read that two
|
|
785
|
+
enclosing `before`s assign different classes is not
|
|
786
|
+
observed.
|
|
731
787
|
|
|
732
788
|
A Minitest suite under `test/` is observed too, with no
|
|
733
789
|
recogniser of its own: a call whose receiver types to one
|
|
734
|
-
class counts. A receiver assigned in `setup`
|
|
735
|
-
inside a `test_*` method
|
|
736
|
-
a
|
|
737
|
-
|
|
790
|
+
class counts. A receiver assigned in `setup` reads as
|
|
791
|
+
`Foo | nil` inside a `test_*` method, since `setup` is not
|
|
792
|
+
`initialize`; a receiver of that shape — one class or class
|
|
793
|
+
object plus `nil` — is observed as that class when `nil`
|
|
794
|
+
does not answer the method, because such a call cannot
|
|
795
|
+
have been made on `nil`. A call of a method `nil` does
|
|
796
|
+
answer (`==`, `respond_to?`, `is_a?`) is not observed, as
|
|
797
|
+
it may have run on `nil`; with `&.` it is, since `&.` skips
|
|
798
|
+
`nil`. Which methods `nil` answers comes from the loaded
|
|
799
|
+
RBS for `NilClass`, `Object` and `Kernel`, so a method a
|
|
800
|
+
library adds to `nil` (ActiveSupport's `try`, `as_json`)
|
|
801
|
+
counts only when that library's RBS is loaded.
|
|
802
|
+
`@foo&.bar(1)` counts the same way. A receiver
|
|
803
|
+
that could be one of two classes (`Foo | Bar | nil`), or
|
|
804
|
+
`untyped | nil`, is still not observed.
|
|
738
805
|
|
|
739
806
|
The recogniser is part of the generator itself; you do not
|
|
740
807
|
need to install `rigor-rspec` to benefit from it. If you
|
|
@@ -765,16 +832,31 @@ by side without coordination.
|
|
|
765
832
|
`def f`, a plain proposal over a `def self?.f`, or a
|
|
766
833
|
method declared in a later opening is skipped. A
|
|
767
834
|
`def f: ... | ...` overload is never replaced and never
|
|
768
|
-
counts as the file that declares `f`. An `alias`
|
|
769
|
-
`
|
|
835
|
+
counts as the file that declares `f`. An `alias new old`
|
|
836
|
+
(or `alias self.new self.old`) declares `new` under its own
|
|
837
|
+
kind and is never replaced, so a proposal for that name is
|
|
838
|
+
skipped.
|
|
770
839
|
- **Will not** touch files outside the configured signature
|
|
771
840
|
tree.
|
|
841
|
+
- **Will** name every proposal it skipped, one
|
|
842
|
+
` skipped Class#method (reason): <proposed RBS>` line under
|
|
843
|
+
its target (`Class.method` for a singleton), with the same
|
|
844
|
+
rows under `skipped` in `--format=json`. The reason is
|
|
845
|
+
`user-authored` (the target declares the member and the
|
|
846
|
+
writer does not replace it) or `outside-sig-root` (the
|
|
847
|
+
member routes to a file in a signature path other than the
|
|
848
|
+
first, which `--write` does not modify, whether the member
|
|
849
|
+
is declared there or is new to a class that lives there).
|
|
850
|
+
A target left unchanged by skips alone prints
|
|
851
|
+
`unchanged <file>`. The exit status stays `0`: a skip is
|
|
852
|
+
not a refusal, and `--check` still reports `sig/` as up to
|
|
853
|
+
date.
|
|
772
854
|
- **Will not** replace an existing method declaration
|
|
773
855
|
unless `--overwrite` is set AND the candidate is a
|
|
774
856
|
`tighter-return`, and then only its return changes: the
|
|
775
857
|
rest of the declaration is kept. Without `--overwrite`,
|
|
776
858
|
existing declarations are user-authored and the new method
|
|
777
|
-
is
|
|
859
|
+
is skipped.
|
|
778
860
|
- **Will not** change an existing method declaration that
|
|
779
861
|
disagrees with the method's inline declaration unless
|
|
780
862
|
`--overwrite` is set; it reports the method as `REFUSED`
|
|
@@ -28,15 +28,25 @@ rigor check [paths...]
|
|
|
28
28
|
`paths` are files or directories; when omitted, Rigor checks
|
|
29
29
|
the `paths:` list from the configuration file.
|
|
30
30
|
|
|
31
|
+
Only the files you name are reported on, but Rigor reads the
|
|
32
|
+
declarations in the configured `paths:` too, so a file resolves a
|
|
33
|
+
class or constant that another file declares. A file outside
|
|
34
|
+
`paths:` also reads the files it loads with a literal
|
|
35
|
+
`require_relative "..."`, and the files those load in turn. Their
|
|
36
|
+
classes, modules and constants resolve and their methods count as
|
|
37
|
+
defined, but Rigor does not infer what those methods return or
|
|
38
|
+
check their arity, since another file may redefine them.
|
|
39
|
+
|
|
31
40
|
| Option | Effect |
|
|
32
41
|
| --- | --- |
|
|
33
|
-
| `--config=PATH` | Use a specific config file instead of auto-discovery. |
|
|
42
|
+
| `--config=PATH` | Use a specific config file instead of auto-discovery. A path that does not exist is an error (exit 64), not a run with the defaults. |
|
|
34
43
|
| `--format=FORMAT` | Output format. Default `text`. Also `json` (structured stream), plus the CI-native renderings `sarif`, `github`, `gitlab`, `checkstyle`, `junit`, and `teamcity` — see [Running Rigor in CI](11-ci.md). |
|
|
35
44
|
| `--no-ci-detect` | Disable CI auto-detection — by default `text` output also emits the running CI's native annotations / hint (`RIGOR_CI_DETECT=0` does the same). See [Running Rigor in CI § auto-detection](11-ci.md). |
|
|
36
45
|
| `--explain` | Surface fail-soft fallback events as `info` diagnostics. |
|
|
37
46
|
| `--no-cache` | Skip the persistent cache for this run. With `--incremental` it is a cold full analysis: the incremental snapshot and the run-result cache are neither read nor written, so the next plain `--incremental` run still uses the snapshot it had before. |
|
|
38
47
|
| `--incremental` | Re-analyse only the files changed since the last run plus the files that depend on them, serving the rest from a cross-process disk snapshot (ADR-46). Diagnostics are identical to a full run; a config / gem / version change (or a file added or removed) transparently forces a full re-analysis. A run with nothing changed is answered from the cache without starting the analyzer. See [Caching](12-caching.md). |
|
|
39
|
-
| `--verify-incremental` | Acceptance gate: run the incremental analyzer against a full `--no-cache` run and assert the diagnostics agree as a set and in the same order, then exit (0 on match, 1 on mismatch: the differing diagnostics for a set mismatch, or the first index where the order diverges for a same-set, different-order mismatch).
|
|
48
|
+
| `--verify-incremental` | Acceptance gate: run the incremental analyzer against a full `--no-cache` run and assert the diagnostics agree as a set and in the same order, then exit (0 on match, 1 on mismatch: the differing diagnostics for a set mismatch, or the first index where the order diverges for a same-set, different-order mismatch). It re-analyses half of an unchanged tree and serves the other half from cache, so it checks the cache-merge path but not that an edit re-checks every file it affects; `--verify-incremental-edits` adds that. |
|
|
49
|
+
| `--verify-incremental-edits=N` | With `--verify-incremental` (and implying it): after the check above, replay two edits on each of `N` files spread over the analysed list, through an editor buffer, never on disk: the file emptied, then the file reduced to its `class` / `module` declarations with every body dropped (skipped when that is the file itself, or holds no declaration). After each edit, the warm recheck of the baseline must equal a full analysis of the edited tree, or the run fails naming the edit and the file. Each replay costs a recheck and a full analysis. A replay the recheck declines (it would have been a full analysis) is counted in the OK line, and the run fails if every replay declined. Only these two removals are replayed, never an addition. Default 0. |
|
|
40
50
|
| `--clear-cache` | Delete the cache directory before running. |
|
|
41
51
|
| `--cache-stats` | Print the on-disk cache inventory when finished — on stdout under `--format text`, on stderr under every other format, so machine-readable output stays a parseable document. `--clear-cache`'s and `--verify-incremental`'s notes follow the same rule. |
|
|
42
52
|
| `--[no-]stats` | Print a run summary (files, classes, memory, wall time) to stderr. Default on. |
|
|
@@ -60,7 +70,9 @@ that want the stricter reading.
|
|
|
60
70
|
### Editor mode scope
|
|
61
71
|
|
|
62
72
|
`--tmp-file` / `--instead-of` on their own analyse **only** the
|
|
63
|
-
buffer's file
|
|
73
|
+
buffer's file. It still reads the rest of the project's declarations
|
|
74
|
+
(the classes and methods other files define), as `rigor check FILE`
|
|
75
|
+
does, but it reports nothing about what the unsaved edit does to the
|
|
64
76
|
rest of the project.
|
|
65
77
|
|
|
66
78
|
Adding `--incremental` analyses the **whole project with the buffer
|
|
@@ -138,6 +150,19 @@ truncation explicit. `--trace` records fail-soft fallbacks,
|
|
|
138
150
|
after the rows of a line table in text output. The editor-mode
|
|
139
151
|
`--tmp-file` / `--instead-of` pair is accepted as on `check`.
|
|
140
152
|
|
|
153
|
+
A class or constant that another project file declares reads as
|
|
154
|
+
`rigor check` types it (`singleton(User)`) when a bare
|
|
155
|
+
`rigor check --incremental` has left a snapshot for the project:
|
|
156
|
+
`type-of` folds the cross-file declarations from that snapshot's
|
|
157
|
+
per-file bundles, re-reading any file whose bytes changed since,
|
|
158
|
+
and never writes it. With no reusable snapshot (none yet, or one a
|
|
159
|
+
change made unusable, for example a changed configuration, lockfile, `sig/`, `pre_eval:`
|
|
160
|
+
file or required feature, an engine upgrade, or a snapshot written
|
|
161
|
+
for other roots) the probe types the one file on its own, so such a name reads
|
|
162
|
+
`Dynamic[top]`, and it prints nothing about it. Only those
|
|
163
|
+
declarations are shared: the types `check` infers for an unannotated
|
|
164
|
+
parameter are not.
|
|
165
|
+
|
|
141
166
|
A template a plugin compiles into Ruby (an ERB view under
|
|
142
167
|
`rigor-actionpack`) is probed the way `rigor check` analyses
|
|
143
168
|
it: the compiled Ruby is typed under the view's declared
|
|
@@ -162,7 +187,9 @@ and is probed as plain Ruby, parse error included.
|
|
|
162
187
|
|
|
163
188
|
The four probe commands — `type-of`, `type-scan`, `trace` and
|
|
164
189
|
`annotate` — build their environment fresh on every invocation
|
|
165
|
-
and never read or write the persistent cache
|
|
190
|
+
and never read or write the persistent cache (apart from the
|
|
191
|
+
read-only snapshot lookup `type-of` makes for the project's
|
|
192
|
+
declarations, above). That is why none
|
|
166
193
|
of them takes `--no-cache`: the flag would have nothing to skip.
|
|
167
194
|
A probe therefore types against the environment `rigor check
|
|
168
195
|
--no-cache` analyses with. The environment a cached (default)
|
|
@@ -224,7 +251,7 @@ rigor type-scan PATH...
|
|
|
224
251
|
`--show-recognized` includes fully-covered classes,
|
|
225
252
|
`--threshold=RATIO` makes the command exit non-zero when the
|
|
226
253
|
unrecognized-node ratio exceeds `RATIO`, and `--format=text|json`
|
|
227
|
-
selects the output format.
|
|
254
|
+
selects the output format. `--config=PATH` overrides config discovery.
|
|
228
255
|
|
|
229
256
|
## `rigor effects`
|
|
230
257
|
|
|
@@ -354,6 +381,8 @@ accept `--config=PATH`, `--format=text|json` and `--full`;
|
|
|
354
381
|
configured one — `--baseline <(git show
|
|
355
382
|
origin/main:.rigor-effects.yml)` in a bot),
|
|
356
383
|
`--strict-tolerated` and `--no-tolerated-effects`.
|
|
384
|
+
`explain` additionally accepts `--symbol=KEY` to explain one
|
|
385
|
+
specific unit instead of the changed ones.
|
|
357
386
|
|
|
358
387
|
The file records two tables. `methods:` holds each method's
|
|
359
388
|
**direct** summary — what its own body does, block literals
|
|
@@ -476,12 +505,13 @@ Print the catalogue entry for a diagnostic rule, or list every
|
|
|
476
505
|
rule when called with no argument.
|
|
477
506
|
|
|
478
507
|
```sh
|
|
479
|
-
rigor explain [rule]
|
|
508
|
+
rigor explain [options] [rule]
|
|
480
509
|
```
|
|
481
510
|
|
|
482
511
|
`rule` is a rule ID (`call.undefined-method`), a legacy alias,
|
|
483
|
-
or a family wildcard (`call`, `flow`, `def`, `assert`, `dump`
|
|
484
|
-
|
|
512
|
+
or a family wildcard (`call`, `flow`, `def`, `assert`, `dump`, `global`,
|
|
513
|
+
`suppression`, `static`, `effect`).
|
|
514
|
+
`--format=text|json` selects the output format. Exit `64` for an unknown rule.
|
|
485
515
|
|
|
486
516
|
It also answers the `sig.skipped.*` identifiers `rigor sig-gen`
|
|
487
517
|
prints when it declines to write a signature — `rigor explain
|
|
@@ -495,11 +525,13 @@ Compare the current diagnostics against a saved baseline JSON
|
|
|
495
525
|
and report only what is new.
|
|
496
526
|
|
|
497
527
|
```sh
|
|
498
|
-
rigor diff <baseline.json> [paths...]
|
|
528
|
+
rigor diff [options] <baseline.json> [paths...]
|
|
499
529
|
```
|
|
500
530
|
|
|
501
531
|
`--current=PATH` compares against a saved diagnostics JSON
|
|
502
|
-
instead of running a fresh check.
|
|
532
|
+
instead of running a fresh check. `--format=text|json` selects the
|
|
533
|
+
output format (default `text`), and `--config=PATH` passes a custom
|
|
534
|
+
configuration to the implicit check. Exit `1` when new
|
|
503
535
|
diagnostics appear.
|
|
504
536
|
|
|
505
537
|
## `rigor sig-gen`
|
|
@@ -527,12 +559,22 @@ rigor sig-gen [paths]
|
|
|
527
559
|
| `--no-cache` | Do not read or write the analysis cache. Only effect collection uses it. |
|
|
528
560
|
| `--format=text\|json` | Output format. |
|
|
529
561
|
|
|
562
|
+
A `paths` argument that does not exist, or is neither a directory nor a `.rb` file, is reported the way
|
|
563
|
+
`rigor check` reports it: on stderr as `path:1:1: error: …`, or under `diagnostics` in `--print` and `--diff`
|
|
564
|
+
JSON output. When no argument is readable this exits `1`, under every mode, `--write` and `--check` included.
|
|
565
|
+
The configured default `paths:` count too, so `paths: [lib]` with no `lib/` directory exits `1`. A `--config`
|
|
566
|
+
that names no file, is a directory, or is empty is a usage error and exits `64`.
|
|
567
|
+
|
|
530
568
|
`--print`, `--diff`, `--write` and `--check` are mutually exclusive.
|
|
531
569
|
`--check` fails exactly when `--write` would create, change or
|
|
532
570
|
refuse a file, so a tighter return `--write` declines without
|
|
533
571
|
`--overwrite` does not fail it; `--check --overwrite` counts one.
|
|
534
572
|
See [handbook chapter 11](../handbook/11-sig-gen.md#keeping-sig-current-in-ci).
|
|
535
573
|
|
|
574
|
+
`--write` names each proposal it skipped (a member `sig/` already declares, an `alias` included, or one that
|
|
575
|
+
routes to a signature path it does not write to, because it is declared there or its class lives there) on its own `skipped Class#method (reason)` line; the exit status is
|
|
576
|
+
unchanged. See [handbook chapter 11](../handbook/11-sig-gen.md#safety-what---write-will-and-will-not-do).
|
|
577
|
+
|
|
536
578
|
A method declared inline with `# @rbs` / `#:` is written as that
|
|
537
579
|
declaration, not as what its body infers; a parameter-only
|
|
538
580
|
annotation keeps its parameters and takes the return from the body,
|
|
@@ -616,7 +658,8 @@ rigor baseline <generate|regenerate|dump|drift|prune> [options]
|
|
|
616
658
|
|
|
617
659
|
`generate` and `regenerate` accept `--output=PATH` and
|
|
618
660
|
`--match-mode=rule|message`; `dump`, `drift`, and `prune` accept
|
|
619
|
-
`--baseline=PATH` to read a non-default baseline file.
|
|
661
|
+
`--baseline=PATH` to read a non-default baseline file. `dump`
|
|
662
|
+
also accepts `--format=text|json` for machine-readable output.
|
|
620
663
|
|
|
621
664
|
## `rigor triage`
|
|
622
665
|
|
|
@@ -630,7 +673,7 @@ rigor triage [paths]
|
|
|
630
673
|
|
|
631
674
|
`--top=N` sets the hotspot count (default 10); `--hints-only`,
|
|
632
675
|
`--selectors-only`, and `--no-hints` select which sections print.
|
|
633
|
-
`triage` is advisory and
|
|
676
|
+
`triage` is advisory and exits `0` once it runs — it never gates a build; a usage error exits `64`.
|
|
634
677
|
|
|
635
678
|
By default the distribution, selectors, and hotspots sections count
|
|
636
679
|
only the **actionable** diagnostics (`error` + `warning`). `info`
|
|
@@ -936,6 +979,11 @@ loaded from (a `path:` line in text, a `"path"` key in JSON), so
|
|
|
936
979
|
if a stale installed gem is shadowing a newer checkout's bundled
|
|
937
980
|
plugin copy the mismatch is visible at a glance.
|
|
938
981
|
|
|
982
|
+
A plugin that declares `per_invocation_classes:` or
|
|
983
|
+
`per_invocation_non_action_macros:` ([ADR-125](../adr/125-per-invocation-classes.md))
|
|
984
|
+
also gets a line for each in text, and a key in JSON (an empty
|
|
985
|
+
array when it declares none).
|
|
986
|
+
|
|
939
987
|
```sh
|
|
940
988
|
rigor plugins [--format=text|json] [--strict] [--capabilities] [--config=PATH]
|
|
941
989
|
```
|
|
@@ -1163,8 +1211,17 @@ Runs a scoped analysis and audits:
|
|
|
1163
1211
|
- **Plugin load errors** — whether every configured plugin loaded.
|
|
1164
1212
|
- **Baseline drift** — whether the current diagnostics have drifted
|
|
1165
1213
|
from the saved baseline.
|
|
1166
|
-
- **
|
|
1167
|
-
|
|
1214
|
+
- **Plugin gap** — whether the project's direct dependencies (the
|
|
1215
|
+
`DEPENDENCIES` section of `Gemfile.lock`, plus the Rails components
|
|
1216
|
+
a direct `rails` stands for) include gems a bundled plugin models —
|
|
1217
|
+
Rails, Devise, Pundit, Sidekiq, ... — while `plugins:` does not list
|
|
1218
|
+
that plugin ([ADR-96](../adr/96-plugin-target-gems.md)). An entry
|
|
1219
|
+
with `enabled: false` counts as listed. Each missing plugin is a
|
|
1220
|
+
warning, since leaving one out can be deliberate; it is a failure
|
|
1221
|
+
only when `plugins:` lists none of the bundled plugins that model
|
|
1222
|
+
gems (those declaring `target_gems:`).
|
|
1223
|
+
Skipped when no `.rigor.yml` or `.rigor.dist.yml` is discovered in
|
|
1224
|
+
the current directory.
|
|
1168
1225
|
- **Gemfile install** — whether Rigor itself resolves as one of the
|
|
1169
1226
|
project's dependencies, which
|
|
1170
1227
|
[Installing Rigor](01-installation.md) tells you not to do. Only a
|
|
@@ -1177,6 +1234,16 @@ Runs a scoped analysis and audits:
|
|
|
1177
1234
|
versioned together, so a copy from another installation can run
|
|
1178
1235
|
the engine with a mismatched plugin. A warning, not a failure;
|
|
1179
1236
|
third-party plugins from your own bundle are never flagged.
|
|
1237
|
+
- **Bundle layout** — whether Rigor can locate the bundle a
|
|
1238
|
+
`Gemfile.lock` in the current directory was installed into; when it
|
|
1239
|
+
cannot (typically gems in the active Ruby's default gem home), no
|
|
1240
|
+
gem-shipped `sig/` is discovered. Rigor finds a bundle only where the
|
|
1241
|
+
project records it. A `bundler.bundle_path:` in `.rigor.yml` is used
|
|
1242
|
+
alone and must exist. Without one — and unless
|
|
1243
|
+
`bundler.auto_detect: false` — Rigor reads `.bundle/config`'s
|
|
1244
|
+
`BUNDLE_PATH`, then an in-tree `vendor/bundle`, then the global
|
|
1245
|
+
Bundler config. A warning, not a failure: the default layout is
|
|
1246
|
+
correct, and most gems ship no `sig/`.
|
|
1180
1247
|
|
|
1181
1248
|
Text output prints `[PASS]`, `[FAIL]`, or `[WARN]` per check plus a
|
|
1182
1249
|
routed hint (e.g. "Run `rigor baseline regenerate`"). JSON output
|
|
@@ -1237,9 +1304,11 @@ diagnostics about Rigor's own inference cutoffs and memory — see
|
|
|
1237
1304
|
| `0` | Success — no diagnostic at or above the exit threshold (`error` by default; `rigor check --fail-on=SEVERITY` lowers it to `warning` or `info`). |
|
|
1238
1305
|
| `1` | Diagnostics found at or above the threshold, or a per-command failure (parse error, missing file, new diagnostics on `diff`, effect drift on `effects check`). |
|
|
1239
1306
|
| `64` | Usage error — unknown command, bad flag, malformed argument, or a value in `.rigor.yml` the loader cannot proceed on. |
|
|
1307
|
+
| `70` | Internal fatal error — `SystemStackError` or `NoMemoryError` inside the analysis; Rigor could not finish the run, so its report on disk is empty rather than clean. |
|
|
1240
1308
|
|
|
1241
|
-
`rigor triage` is the exception: it is advisory and
|
|
1242
|
-
|
|
1309
|
+
`rigor triage` is the exception: it is advisory and exits `0`
|
|
1310
|
+
whenever it runs to completion. A usage error, such as a
|
|
1311
|
+
`--config` that names no file, still exits `64`.
|
|
1243
1312
|
|
|
1244
1313
|
A configuration mistake prints one `rigor:` line naming the key
|
|
1245
1314
|
and nothing else — no backtrace, on every command:
|
|
@@ -67,7 +67,7 @@ cache:
|
|
|
67
67
|
| `target_ruby` | String | `"4.0"` | The lowest Ruby version *your* project runs on — `"X.Y"`, `"X.Y.Z"`, or `"latest"`. Independent of the Ruby Rigor itself runs on. Every value selects the syntax Rigor parses. A version you set explicitly also turns on the Ruby-deprecation rules for that version ([`call.deprecated-ruby2-keywords`](04-diagnostics.md#rule-call-deprecated-ruby2-keywords) from `"4.1"`); the default and `"latest"` never do, which is why `rigor init` writes the key commented out. |
|
|
68
68
|
| `paths` | Array | `["lib"]` | Directories or files to analyse. |
|
|
69
69
|
| `exclude` | Array | `[]` | Glob patterns to skip. A relative pattern is matched against the file's path relative to the directory of the config file in use (the working directory when no config file is used), so it excludes the same files whether the path came from `paths:` or from a command-line argument, and it never matches on a directory above that base (a file outside it is seen as `../…`, so `../shared/lib/gen/**` reaches a sibling directory that `paths:` names). A leading `./` is ignored, and a pattern starting with `.` (such as `.*`) also matches files outside the base, which appear as `../…`. A symlink to a file is matched only under its own name: the realpath pass resolves directories, not files. `*` and `**` also cross `/`, so `lib/*.rb` matches nested files too. An absolute pattern is matched against the absolute path. Patterns from an `includes:` file resolve against the top-level config's directory, not the included file's. `vendor/bundle`, `.bundle`, and `node_modules` are always excluded. |
|
|
70
|
-
| `test_paths` | Array | `nil` | The project's test roots: the directories (or files) holding its tests. Relative entries resolve against the config file's directory. Unset auto-detects whichever of `spec/` and `test/` exist; `[]` declares none. `rigor sig-gen --params=observed` reads call sites there to type parameters, and names on stderr a declared root that does not exist. Test roots are not analysed unless `paths:` also lists them, and changing them invalidates no cache. |
|
|
70
|
+
| `test_paths` | Array | `nil` | The project's test roots: the directories (or files) holding its tests. Relative entries resolve against the config file's directory. Unset auto-detects whichever of `spec/` and `test/` exist in the config file's directory, or in the working directory when the config file's directory has neither or no config file is used; `[]` declares none. `rigor sig-gen --params=observed` reads call sites there to type parameters, and names on stderr a declared root that does not exist. Test roots are not analysed unless `paths:` also lists them, and changing them invalidates no cache. |
|
|
71
71
|
| `includes` | Array | `[]` | Other config files to layer underneath this one. |
|
|
72
72
|
| `fold_platform_specific_paths` | Boolean | `false` | Resolve Ruby-version-conditional load paths when discovering sources. |
|
|
73
73
|
| `parameter_inference` | Boolean | `false` | Opt-in call-site parameter type inference on the `check` walk ([ADR-67](../adr/67-parameter-type-inference.md) WD6). When `true`, an undeclared `def` / `initialize` / setter parameter is typed to the union of its resolved call-site argument types, sharpening downstream ivar reads, folds, and protection coverage. Precision-additive only — the negative rules never fire against an inferred parameter. Cannot be combined with `--incremental`. |
|
|
@@ -106,6 +106,7 @@ rigor: disable: "call.undefined-methdo" is not a recognized rule id; the suppres
|
|
|
106
106
|
rigor: disable: "plugin_loader.load-error" names a run-level row, which `disable:` does not reach; use `severity_overrides:` or a baseline
|
|
107
107
|
rigor: disable: "plugin.shoulda-matchers.unknown-column" names no plugin `plugins:` configures; the suppression has no effect
|
|
108
108
|
rigor: severity_overrides: "flow.bogus" is not a recognized rule id; the override has no effect
|
|
109
|
+
rigor: severity_overrides: "undefined-method" is a legacy alias of `call.undefined-method`; the override has no effect, so write `call.undefined-method`
|
|
109
110
|
rigor: bundler.lockfile: "./missing/Gemfile.lock" does not exist
|
|
110
111
|
```
|
|
111
112
|
|
|
@@ -128,18 +129,20 @@ These are warnings, not errors — partial or optional bundles and
|
|
|
128
129
|
forward-looking config are valid setups. The audit only fires on explicit,
|
|
129
130
|
working-setup-safe signals: an unset default (auto-detected `<root>/sig`,
|
|
130
131
|
auto-detected bundle) is never warned about, and a
|
|
131
|
-
`
|
|
132
|
-
`
|
|
133
|
-
|
|
134
|
-
closely, because what
|
|
132
|
+
`rbs_extended.…`, `rbs.…` or `dynamic.…` key in `severity_overrides:` or
|
|
133
|
+
`disable:` is left alone, since the engine emits those ids outside the
|
|
134
|
+
catalogue. `disable:` and `severity_overrides:` are both checked
|
|
135
|
+
closely, because what they can reach is known: a plugin's rule counts
|
|
135
136
|
only in its qualified `plugin.<id>.<rule>` form, so a bare plugin rule
|
|
136
137
|
(`rspec.let-binding`) is warned about. A qualified token is checked
|
|
137
138
|
against `plugins:` only when every entry's plugin ID is known without
|
|
138
139
|
loading it — an explicit `id:`, or a bundled plugin's gem name minus
|
|
139
140
|
`rigor-`; one third-party gem listed without `id:` turns that check
|
|
140
|
-
off. A token under `rbs_extended.…`, `rbs.…` or `dynamic.…`
|
|
141
|
-
alone, other than the reserved run-level rows (`rbs.coverage.…`,
|
|
142
|
-
`dynamic.dependency-source.…`), which are flagged
|
|
141
|
+
off. A `disable:` token under `rbs_extended.…`, `rbs.…` or `dynamic.…`
|
|
142
|
+
is left alone, other than the reserved run-level rows (`rbs.coverage.…`,
|
|
143
|
+
`dynamic.dependency-source.…`), which are flagged; in `severity_overrides:`
|
|
144
|
+
those rows are the intended keys and are never flagged, while a bare plugin
|
|
145
|
+
rule, `all`, or a `plugin.`-qualified key for an unconfigured plugin is. The same findings appear in the
|
|
143
146
|
`--format=json` payload under `config_warnings` (each tagged with a
|
|
144
147
|
`kind`), so CI can assert on them.
|
|
145
148
|
|
|
@@ -149,7 +152,7 @@ alone, other than the reserved run-level rows (`rbs.coverage.…`,
|
|
|
149
152
|
| --- | --- | --- | --- |
|
|
150
153
|
| `disable` | Array | `[]` | Rule IDs or families to suppress project-wide; `all` suppresses every built-in check rule. A plugin's diagnostics are named by qualified ID (`plugin.<id>.<rule>`, or `plugin.<id>` for the whole plugin); a bare ID or `all` does not reach them. The run-level `.rigor.yml` rows (`plugin_loader.*`, `dynamic.dependency-source.*`, `rbs.coverage.*`, `plugin_trust.*`, `pool-degraded`) are not reached by any spelling; use `severity_overrides:` for those. See [Diagnostics](04-diagnostics.md#suppressing-a-diagnostic). |
|
|
151
154
|
| `severity_profile` | String | `"balanced"` | `lenient`, `balanced`, or `strict` — see [Diagnostics](04-diagnostics.md). |
|
|
152
|
-
| `severity_overrides` | Hash | `{}` | Per-rule / per-family severity, e.g. `{ call: warning, flow.always-truthy-condition: off }`. |
|
|
155
|
+
| `severity_overrides` | Hash | `{}` | Per-rule / per-family severity, e.g. `{ call: warning, flow.always-truthy-condition: off }`. A plugin's diagnostics are named by qualified ID (`plugin.<id>.<rule>`, or `plugin.<id>` for the whole plugin); a bare rule or family does not reach them. The plugin loader's own rows are `plugin_loader.load-error` / `plugin_loader.runtime-error` (or the bare `load-error` / `runtime-error`). |
|
|
153
156
|
| `baseline` | String / `false` | `nil` | Path to a `.rigor-baseline.yml`, or `false` to disable an inherited one. See [Baselines](06-baseline.md). |
|
|
154
157
|
| `bleeding_edge` | Boolean / Array / Hash | `false` | Adopt the next major's queued changes early ([ADR-50](../adr/50-release-engineering-and-stability-strategy.md) § WD2). `false` adopts none; `true` adopts the whole overlay; a list of feature ids adopts only those; `{ all: true, except: [ids] }` adopts all but the named. Orthogonal to `severity_profile`. Override it for a single run with [`rigor check --bleeding-edge[=ids]`](02-cli-reference.md#rigor-check) / `--no-bleeding-edge`. Inspect with [`rigor show-bleedingedge`](02-cli-reference.md#rigor-show-bleedingedge). |
|
|
155
158
|
|
|
@@ -67,7 +67,7 @@ carries no `documentation_url`.
|
|
|
67
67
|
| <a id="rule-flow-return-in-ensure"></a>`flow.return-in-ensure` | An explicit `return` inside an `ensure` clause — it overrides the method's in-flight return value and silently swallows any in-flight exception. A `return` in a nested `def`, lambda, or `define_method` block inside the `ensure` does not fire (it exits that inner frame). | high |
|
|
68
68
|
| <a id="rule-flow-shadowed-rescue-clause"></a>`flow.shadowed-rescue-clause` | A `rescue` clause can never run because an earlier clause of the same chain already catches a superclass (or the same class) of every exception class it names. | high |
|
|
69
69
|
| <a id="rule-def-return-type-mismatch"></a>`def.return-type-mismatch` | The method body's result violates its declared RBS return type. | medium |
|
|
70
|
-
| <a id="rule-def-ivar-write-mismatch"></a>`def.ivar-write-mismatch` | An instance variable is written with a type disagreeing with its first write, unless its RBS declaration (`@x: T` or an `attr_*` member) admits both writes. | high |
|
|
70
|
+
| <a id="rule-def-ivar-write-mismatch"></a>`def.ivar-write-mismatch` | An instance variable is written with a type disagreeing with its first write, unless its RBS declaration (`@x: T` or an `attr_*` member) admits both writes. With `rigor-actionpack` or `rigor-actionmailer` loaded, writes in two different actions of a controller or mailer are not compared, since Rails runs one action per instance. A write in a private or protected helper, `initialize` or the same action still is, and so is one in a callback target or `helper_method` that a macro in the same file names. A public method named only by a macro in another file or a concern, or reached through a lambda callback, counts as an action, and so does an action another action calls. | high |
|
|
71
71
|
| <a id="rule-def-method-visibility-mismatch"></a>`def.method-visibility-mismatch` | An explicit-receiver call reaches a private method. | high |
|
|
72
72
|
| <a id="rule-def-override-visibility-reduced"></a>`def.override-visibility-reduced` | An override reduces the visibility it inherits from a project-defined ancestor. | high |
|
|
73
73
|
| <a id="rule-def-override-return-widened"></a>`def.override-return-widened` | An override's declared return type widens the inherited return (covariance). | high |
|
|
@@ -162,6 +162,15 @@ the diagnostic from the result entirely, which makes
|
|
|
162
162
|
below — both silence a rule; the override reads as "this one
|
|
163
163
|
rule, at this severity" alongside the rest of the profile.
|
|
164
164
|
|
|
165
|
+
A plugin's diagnostic is named by its qualified ID, as in `disable:`:
|
|
166
|
+
`plugin.<id>.<rule>` for one rule (`plugin.rails-i18n.load-error: off`),
|
|
167
|
+
or `plugin.<id>` for every row of that plugin. A bare rule or a family
|
|
168
|
+
never reaches a plugin's row, so `load-error: off` leaves each plugin's
|
|
169
|
+
own `load-error` alone. The plugin loader's rows are
|
|
170
|
+
`plugin_loader.load-error` and `plugin_loader.runtime-error`, which also
|
|
171
|
+
answer to the bare `load-error` / `runtime-error`. `rigor check` warns
|
|
172
|
+
about a key it can tell reaches no row.
|
|
173
|
+
|
|
165
174
|
YAML reserves the bareword `off` as a boolean. If an override
|
|
166
175
|
that names it seems not to apply, quote it — `"off"` — and the
|
|
167
176
|
same for `on`.
|
|
@@ -252,7 +261,8 @@ named rules on that line:
|
|
|
252
261
|
config.merge(extra) # rigor:disable call.undefined-method
|
|
253
262
|
```
|
|
254
263
|
|
|
255
|
-
It accepts qualified IDs, family wildcards (`call`
|
|
264
|
+
It accepts qualified IDs, family wildcards (`call`, `flow`, `def`, `assert`,
|
|
265
|
+
`dump`, `global`, `suppression`, `static`, `effect`, `pre-eval`), a
|
|
256
266
|
comma- or space-separated list, or `all`. A plugin's diagnostic
|
|
257
267
|
is reached only by its qualified ID as printed in brackets
|
|
258
268
|
(`plugin.shoulda-matchers.unknown-column`) or by the plugin's
|
|
@@ -332,10 +342,11 @@ gem that did not resolve (`dynamic.dependency-source.*`), the
|
|
|
332
342
|
under any spelling, `plugin_loader.load-error` included. Turn
|
|
333
343
|
those off with [`severity_overrides:`](#severity-profiles) set
|
|
334
344
|
to `off`, or record them in a [baseline](06-baseline.md).
|
|
335
|
-
`severity_overrides:`
|
|
336
|
-
|
|
337
|
-
`
|
|
338
|
-
|
|
345
|
+
In `severity_overrides:` the plugin loader's rows are
|
|
346
|
+
`plugin_loader.load-error` and `plugin_loader.runtime-error`, or the
|
|
347
|
+
bare `load-error` / `runtime-error` (the qualified key wins if both
|
|
348
|
+
are set); a bare key does not reach a
|
|
349
|
+
plugin's own row of the same name (`plugin.<id>.load-error`). `rigor check` warns when a
|
|
339
350
|
`disable:` entry names one of these rows, names no rule at all,
|
|
340
351
|
or names a plugin `plugins:` does not configure (see
|
|
341
352
|
[Config validation warnings](03-configuration.md#config-validation-warnings)).
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Rigor's analysis is invisible by default — it only speaks up to
|
|
4
4
|
report a diagnostic. When you want to *see* what type the
|
|
5
|
-
engine assigns an expression, there are
|
|
6
|
-
helpers and
|
|
5
|
+
engine assigns an expression, there are six tools: two source
|
|
6
|
+
helpers and four CLI commands.
|
|
7
7
|
|
|
8
8
|
## `dump_type` — print a type from source
|
|
9
9
|
|
data/docs/manual/06-baseline.md
CHANGED
|
@@ -73,7 +73,7 @@ a diagnostic another layer has already suppressed.
|
|
|
73
73
|
| --- | --- |
|
|
74
74
|
| `rigor baseline generate` | Write a fresh baseline from the current diagnostics. Refuses to clobber an existing file without `--force`. |
|
|
75
75
|
| `rigor baseline regenerate` | Rewrite unconditionally — run it after fixing diagnostics so the file shrinks. |
|
|
76
|
-
| `rigor baseline dump` | Print the baseline, filterable with `--rule` and `--file
|
|
76
|
+
| `rigor baseline dump` | Print the baseline, filterable with `--rule` and `--file` (`--format=json` for tooling). |
|
|
77
77
|
| `rigor baseline drift` | Show how buckets have moved — `--only=over` for buckets that grew, `reducible` for ones you have already improved past, `cleared` for empty ones. |
|
|
78
78
|
| `rigor baseline prune` | Drop buckets that match nothing any more. `--dry-run` previews. |
|
|
79
79
|
|
data/docs/manual/07-plugins.md
CHANGED
|
@@ -5,7 +5,8 @@ application DSL that plain inference cannot see — Rails route
|
|
|
5
5
|
helpers, RSpec `let` bindings, dry-rb struct attributes, and
|
|
6
6
|
so on. This page is about *activating* plugins. Writing one is
|
|
7
7
|
covered by [`examples/`](../../examples/README.md) and the
|
|
8
|
-
[`rigor-plugin-author` skill](08-skills.md)
|
|
8
|
+
[`rigor-plugin-author` skill](08-skills.md); reviewing and modernising
|
|
9
|
+
an existing plugin is [`rigor-plugin-review`](08-skills.md).
|
|
9
10
|
|
|
10
11
|
## Activating a plugin
|
|
11
12
|
|
data/docs/manual/08-skills.md
CHANGED
|
@@ -87,6 +87,10 @@ is not repeated here.)
|
|
|
87
87
|
- **`rigor-monkeypatch-resolve`** — resolves an `undefined-method`
|
|
88
88
|
cluster that is really your project's own monkey-patches by wiring the
|
|
89
89
|
defining files into `pre_eval:`.
|
|
90
|
+
- **`rigor-unused-adjudicate`** — adjudicates a `rigor unused` report
|
|
91
|
+
safely before proposing dead-code removals, checking framework roots
|
|
92
|
+
and false-positive shapes. See
|
|
93
|
+
[Removing dead code with `rigor unused`](18-removing-dead-code.md).
|
|
90
94
|
- **`rigor-type-oracle`** — sources every type an agent writes from
|
|
91
95
|
Rigor (`type-of` / `annotate` / `sig-gen`) instead of from reading the
|
|
92
96
|
code, and reports the gaps rather than filling them. It is triggered by
|
|
@@ -119,6 +123,9 @@ is not repeated here.)
|
|
|
119
123
|
repository to teach Rigor an application DSL or metaprogramming
|
|
120
124
|
pattern it cannot infer. Reach for it when no bundled plugin covers a
|
|
121
125
|
framework or DSL your project depends on.
|
|
126
|
+
- **`rigor-plugin-review`** — audits an existing Rigor plugin against
|
|
127
|
+
the current authoring contract and produces a prioritized upgrade
|
|
128
|
+
path. The review/upgrade counterpart to `rigor-plugin-author`.
|
|
122
129
|
|
|
123
130
|
## Discovering skills from the CLI
|
|
124
131
|
|