rigortype 0.2.0 → 0.2.2

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.
Files changed (142) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +82 -20
  3. data/data/core_overlay/numeric.rbs +33 -0
  4. data/data/core_overlay/pathname.rbs +25 -0
  5. data/data/core_overlay/string_scanner.rbs +28 -0
  6. data/data/gem_overlay/activesupport/core_ext.rbs +473 -0
  7. data/data/vendored_gem_sigs/ast/ast.rbs +130 -0
  8. data/data/vendored_gem_sigs/bcrypt/bcrypt.rbs +47 -0
  9. data/data/vendored_gem_sigs/bundler/bundler.rbs +238 -0
  10. data/data/vendored_gem_sigs/cgi/cgi_extras.rbs +34 -0
  11. data/data/vendored_gem_sigs/did_you_mean/did_you_mean_extras.rbs +34 -0
  12. data/data/vendored_gem_sigs/idn-ruby/idn.rbs +54 -0
  13. data/data/vendored_gem_sigs/mysql2/client.rbs +55 -0
  14. data/data/vendored_gem_sigs/mysql2/error.rbs +5 -0
  15. data/data/vendored_gem_sigs/mysql2/result.rbs +31 -0
  16. data/data/vendored_gem_sigs/mysql2/statement.rbs +5 -0
  17. data/data/vendored_gem_sigs/nokogiri/nokogiri.rbs +2332 -0
  18. data/data/vendored_gem_sigs/nokogiri/nokogiri_html5.rbs +47 -0
  19. data/data/vendored_gem_sigs/pg/pg.rbs +212 -0
  20. data/data/vendored_gem_sigs/prism/prism_supplement.rbs +44 -0
  21. data/data/vendored_gem_sigs/redis/errors.rbs +50 -0
  22. data/data/vendored_gem_sigs/redis/future.rbs +5 -0
  23. data/data/vendored_gem_sigs/redis/redis.rbs +348 -0
  24. data/data/vendored_gem_sigs/redis/redis_extras.rbs +130 -0
  25. data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +226 -0
  26. data/docs/handbook/01-getting-started.md +311 -0
  27. data/docs/handbook/02-everyday-types.md +337 -0
  28. data/docs/handbook/03-narrowing.md +359 -0
  29. data/docs/handbook/04-tuples-and-shapes.md +321 -0
  30. data/docs/handbook/05-methods-and-blocks.md +339 -0
  31. data/docs/handbook/06-classes.md +305 -0
  32. data/docs/handbook/07-rbs-and-extended.md +427 -0
  33. data/docs/handbook/08-understanding-errors.md +373 -0
  34. data/docs/handbook/09-plugins.md +241 -0
  35. data/docs/handbook/10-sorbet.md +347 -0
  36. data/docs/handbook/11-sig-gen.md +312 -0
  37. data/docs/handbook/12-lightweight-hkt.md +333 -0
  38. data/docs/handbook/README.md +275 -0
  39. data/docs/handbook/appendix-elixir.md +370 -0
  40. data/docs/handbook/appendix-go.md +399 -0
  41. data/docs/handbook/appendix-java-csharp.md +470 -0
  42. data/docs/handbook/appendix-liskov.md +580 -0
  43. data/docs/handbook/appendix-mypy.md +370 -0
  44. data/docs/handbook/appendix-phpstan.md +338 -0
  45. data/docs/handbook/appendix-protocols-and-structural-typing.md +292 -0
  46. data/docs/handbook/appendix-rust.md +446 -0
  47. data/docs/handbook/appendix-steep.md +336 -0
  48. data/docs/handbook/appendix-type-theory.md +1662 -0
  49. data/docs/handbook/appendix-typeprof.md +416 -0
  50. data/docs/handbook/appendix-typescript.md +332 -0
  51. data/docs/install.md +189 -0
  52. data/docs/llms.txt +72 -0
  53. data/docs/manual/01-installation.md +342 -0
  54. data/docs/manual/02-cli-reference.md +557 -0
  55. data/docs/manual/03-configuration.md +152 -0
  56. data/docs/manual/04-diagnostics.md +206 -0
  57. data/docs/manual/05-inspecting-types.md +109 -0
  58. data/docs/manual/06-baseline.md +104 -0
  59. data/docs/manual/07-plugins.md +92 -0
  60. data/docs/manual/08-skills.md +143 -0
  61. data/docs/manual/09-editor-integration.md +245 -0
  62. data/docs/manual/10-mcp-server.md +532 -0
  63. data/docs/manual/11-ci.md +274 -0
  64. data/docs/manual/12-caching.md +116 -0
  65. data/docs/manual/13-troubleshooting.md +120 -0
  66. data/docs/manual/14-rails-quickstart.md +332 -0
  67. data/docs/manual/15-type-protection-coverage.md +204 -0
  68. data/docs/manual/16-rbs-extended-annotations.md +190 -0
  69. data/docs/manual/17-driving-improvement.md +160 -0
  70. data/docs/manual/README.md +87 -0
  71. data/docs/manual/ci-templates/README.md +58 -0
  72. data/docs/manual/plugins/README.md +86 -0
  73. data/docs/manual/plugins/rigor-actioncable.md +78 -0
  74. data/docs/manual/plugins/rigor-actionmailer.md +74 -0
  75. data/docs/manual/plugins/rigor-actionpack.md +80 -0
  76. data/docs/manual/plugins/rigor-activejob.md +58 -0
  77. data/docs/manual/plugins/rigor-activerecord.md +102 -0
  78. data/docs/manual/plugins/rigor-activestorage.md +74 -0
  79. data/docs/manual/plugins/rigor-activesupport-core-ext.md +86 -0
  80. data/docs/manual/plugins/rigor-devise.md +70 -0
  81. data/docs/manual/plugins/rigor-dry-schema.md +56 -0
  82. data/docs/manual/plugins/rigor-dry-struct.md +60 -0
  83. data/docs/manual/plugins/rigor-dry-types.md +59 -0
  84. data/docs/manual/plugins/rigor-dry-validation.md +62 -0
  85. data/docs/manual/plugins/rigor-factorybot.md +76 -0
  86. data/docs/manual/plugins/rigor-graphql.md +89 -0
  87. data/docs/manual/plugins/rigor-hanami.md +83 -0
  88. data/docs/manual/plugins/rigor-mangrove.md +73 -0
  89. data/docs/manual/plugins/rigor-minitest.md +86 -0
  90. data/docs/manual/plugins/rigor-pundit.md +72 -0
  91. data/docs/manual/plugins/rigor-rails-i18n.md +92 -0
  92. data/docs/manual/plugins/rigor-rails-routes.md +94 -0
  93. data/docs/manual/plugins/rigor-rails.md +44 -0
  94. data/docs/manual/plugins/rigor-rbs-inline.md +83 -0
  95. data/docs/manual/plugins/rigor-rspec-rails.md +72 -0
  96. data/docs/manual/plugins/rigor-rspec.md +86 -0
  97. data/docs/manual/plugins/rigor-shoulda-matchers.md +78 -0
  98. data/docs/manual/plugins/rigor-sidekiq.md +78 -0
  99. data/docs/manual/plugins/rigor-sinatra.md +61 -0
  100. data/docs/manual/plugins/rigor-sorbet.md +63 -0
  101. data/docs/manual/plugins/rigor-statesman.md +75 -0
  102. data/docs/manual/plugins/rigor-typescript-utility-types.md +71 -0
  103. data/exe/rigor +1 -1
  104. data/lib/rigor/analysis/incremental_session.rb +4 -2
  105. data/lib/rigor/analysis/run_stats.rb +13 -1
  106. data/lib/rigor/analysis/runner.rb +54 -12
  107. data/lib/rigor/cli/check_command.rb +26 -3
  108. data/lib/rigor/cli/coverage_command.rb +67 -92
  109. data/lib/rigor/cli/coverage_mutation.rb +149 -0
  110. data/lib/rigor/cli/docs_command.rb +248 -0
  111. data/lib/rigor/cli/fused_protection_renderer.rb +67 -0
  112. data/lib/rigor/cli/fused_protection_report.rb +76 -0
  113. data/lib/rigor/cli/skill_command.rb +103 -41
  114. data/lib/rigor/cli/skill_describe.rb +346 -0
  115. data/lib/rigor/cli.rb +25 -3
  116. data/lib/rigor/config_audit.rb +152 -0
  117. data/lib/rigor/configuration.rb +12 -0
  118. data/lib/rigor/environment/rbs_loader.rb +27 -0
  119. data/lib/rigor/environment.rb +49 -1
  120. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +140 -38
  121. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +37 -6
  122. data/lib/rigor/inference/scope_indexer.rb +87 -89
  123. data/lib/rigor/inference/statement_evaluator.rb +27 -0
  124. data/lib/rigor/plugin/isolation.rb +5 -5
  125. data/lib/rigor/plugin/loader.rb +4 -2
  126. data/lib/rigor/protection/diagnostic_oracle.rb +51 -0
  127. data/lib/rigor/protection/mutation_scanner.rb +98 -38
  128. data/lib/rigor/protection/mutator.rb +21 -0
  129. data/lib/rigor/protection/test_suite_oracle.rb +68 -0
  130. data/lib/rigor/signature_path_audit.rb +92 -0
  131. data/lib/rigor/version.rb +1 -1
  132. data/skills/rigor-ask/SKILL.md +172 -0
  133. data/skills/rigor-doctor/SKILL.md +87 -0
  134. data/skills/rigor-editor-setup/SKILL.md +114 -0
  135. data/skills/rigor-mcp-setup/SKILL.md +117 -0
  136. data/skills/rigor-monkeypatch-resolve/SKILL.md +79 -0
  137. data/skills/rigor-next-steps/SKILL.md +113 -0
  138. data/skills/rigor-plugin-tune/SKILL.md +79 -0
  139. data/skills/rigor-protection-uplift/SKILL.md +133 -0
  140. data/skills/rigor-rbs-setup/SKILL.md +128 -0
  141. data/skills/rigor-upgrade/SKILL.md +79 -0
  142. metadata +120 -1
@@ -0,0 +1,226 @@
1
+ #
2
+ # Rigor-side supplement to the rbs gem's
3
+ # `core/rubygems/*.rbs` declarations.
4
+ #
5
+ # Re-opens the rubygems classes / Gem module to ADD the
6
+ # declarations the upstream RBS omits. Every block below adds
7
+ # new method signatures only — it does NOT redeclare anything
8
+ # the upstream RBS already covers, so the loader does not raise
9
+ # `RBS::DuplicatedDeclarationError`.
10
+ #
11
+ # Driven by the `references/ruby/lib` survey
12
+ # (`docs/CURRENT_WORK.md` § "Queued engine items") which
13
+ # clusters `undefined-method` on Gem-side selectors most-touched
14
+ # by rubygems' own source tree and by bundler. Returns are
15
+ # `untyped` where Rigor cannot infer a precise shape — the goal
16
+ # is to silence false positives, not to author precise sigs.
17
+ #
18
+ module Gem
19
+ # Singleton additions surfacing in the survey but absent from
20
+ # the upstream `core/rubygems/rubygems.rbs`. All are real
21
+ # methods defined in `lib/rubygems/defaults.rb`,
22
+ # `lib/rubygems.rb`, or the platform-specific override files.
23
+ def self.target_rbconfig: () -> untyped
24
+ def self.set_target_rbconfig: (untyped) -> untyped
25
+ def self.load_safe_marshal: () -> untyped
26
+ def self.safe_load_marshal: (untyped) -> untyped
27
+ def self.verbose: () -> bool
28
+ def self.really_verbose: () -> bool
29
+ def self.discover_gems_on_require: () -> bool
30
+ def self.discover_gems_on_require=: (bool) -> bool
31
+ def self.default_user_install: () -> bool
32
+ def self.dynamic_library_suffixes: () -> Array[String]
33
+ def self.extension_api_version: () -> String
34
+ def self.find_default_spec: (untyped) -> untyped?
35
+ def self.install_extension_in_lib: () -> bool
36
+ def self.load_bundler_extensions: () -> untyped
37
+ def self.load_plugin_files: (?untyped plugins) -> untyped
38
+ def self.open_file: (String path, String mode) { (IO) -> untyped } -> untyped
39
+ def self.open_file_with_lock: (String path) { (IO) -> untyped } -> untyped
40
+ def self.state_file: () -> String
41
+ def self.vendor_dir: () -> String
42
+ def self.activate_bin_path: (String name, ?String exec_name, ?untyped requirements) -> String
43
+ end
44
+
45
+ class Gem::Platform
46
+ # `Gem::Platform.new(arg)` is the canonical constructor; the
47
+ # upstream `core/rubygems/platform.rbs` declares the class
48
+ # but no `initialize`, which forces RBS to default to
49
+ # `(): void` and the wrong-arity rule fires for every call
50
+ # site. The accessor / predicate set covers the most-used
51
+ # surface bundler and rubygems internals touch.
52
+ def initialize: (*untyped) -> void
53
+ attr_accessor cpu: String?
54
+ attr_accessor os: String?
55
+ attr_accessor version: String?
56
+ def to_s: () -> String
57
+ def ==: (untyped) -> bool
58
+ def ===: (untyped) -> bool
59
+ def =~: (untyped) -> bool?
60
+ def eql?: (untyped) -> bool
61
+ def hash: () -> Integer
62
+ def match_spec?: (untyped) -> bool
63
+ def match_gem?: (untyped, untyped) -> bool
64
+
65
+ def self.local: () -> Platform
66
+ def self.match_spec?: (untyped) -> bool
67
+ def self.match_gem?: (untyped, untyped) -> bool
68
+ def self.installable?: (untyped) -> bool
69
+ def self.sort_priority: (untyped) -> Integer
70
+ end
71
+
72
+ class Gem::Dependency
73
+ # `Gem::Dependency.new(name, *requirements, type: :runtime)`.
74
+ # Upstream RBS declares the class shell but no constructor; a
75
+ # permissive splat keeps the canonical and the
76
+ # `(name, requirements, type)` invocation forms both silent.
77
+ def initialize: (*untyped) -> void
78
+ attr_accessor name: String
79
+ attr_accessor requirement: Gem::Requirement
80
+ attr_accessor type: Symbol
81
+ attr_accessor prerelease: bool
82
+ def matches_spec?: (untyped) -> bool
83
+ def match?: (*untyped) -> bool
84
+ def to_s: () -> String
85
+ def ==: (untyped) -> bool
86
+ def eql?: (untyped) -> bool
87
+ def hash: () -> Integer
88
+ end
89
+
90
+ class Gem::Specification
91
+ def self.find_all_by_name: (String name, *untyped requirements) -> Array[Gem::Specification]
92
+ def self.stubs: () -> Array[untyped]
93
+ def self.default_stubs: (?String pattern) -> Array[untyped]
94
+ def self.reset: () -> void
95
+ def self.from_yaml: (untyped) -> Gem::Specification
96
+ def self.load: (String path) -> Gem::Specification?
97
+ def self._all: () -> Array[Gem::Specification]
98
+ def self.each: () { (Gem::Specification) -> untyped } -> void
99
+
100
+ # Instance-side surface widely used by bundler / rubygems
101
+ # internals. `attr_accessor` mirrors the writer + reader pair
102
+ # so the survey's `.name=` / `.version=` / `.full_gem_path=` /
103
+ # `.loaded_from=` / `.post_install_message=` writes resolve too.
104
+ def activate: () -> untyped
105
+ def specification_version: () -> Integer
106
+ def files: () -> Array[String]
107
+ def default_gem?: () -> bool
108
+ def spec_file: () -> String
109
+ def runtime_dependencies: () -> Array[Gem::Dependency]
110
+ def required_rubygems_version: () -> Gem::Requirement
111
+ def required_ruby_version: () -> Gem::Requirement
112
+ def require_paths: () -> Array[String]
113
+ attr_accessor name: String
114
+ attr_accessor version: Gem::Version
115
+ attr_accessor full_gem_path: String
116
+ attr_accessor loaded_from: String?
117
+ attr_accessor post_install_message: String?
118
+ end
119
+
120
+ class Gem::BasicSpecification
121
+ def version: () -> Gem::Version
122
+ def executables: () -> Array[String]
123
+ def name: () -> String
124
+ def full_name: () -> String
125
+ def gem_dir: () -> String
126
+ end
127
+
128
+ class Gem::Version
129
+ def segments: () -> Array[untyped]
130
+ end
131
+
132
+ class Gem::Requirement
133
+ def requirements: () -> Array[untyped]
134
+ def self.create: (*untyped) -> Gem::Requirement
135
+ end
136
+
137
+ class Gem::ConfigFile
138
+ def []: (untyped) -> untyped
139
+ def []=: (untyped, untyped) -> untyped
140
+ def write: () -> void
141
+ def verbose: () -> bool
142
+ def verbose=: (bool) -> bool
143
+ def really_verbose: () -> bool
144
+ def ssl_ca_cert: () -> String?
145
+ def ssl_client_cert: () -> String?
146
+ def ssl_verify_mode: () -> Integer?
147
+ def credentials_path: () -> String
148
+ def api_keys: () -> Hash[String, String]
149
+ def cert_expiration_length_days: () -> Integer
150
+ def sources: () -> Array[String]
151
+ def update_sources=: (bool) -> bool
152
+ def unset_api_key!: () -> bool
153
+ def state_file_writable?: () -> bool
154
+ def set_api_key: (untyped host, untyped key) -> untyped
155
+ def rubygems_api_key: () -> String?
156
+ def rubygems_api_key=: (String?) -> String?
157
+ def last_update_check: () -> Integer
158
+ def last_update_check=: (Integer) -> Integer
159
+ def ipv4_fallback_enabled: () -> bool
160
+ def install_extension_in_lib: () -> bool
161
+ def each: () { ([String, untyped]) -> untyped } -> void
162
+ end
163
+
164
+ class Gem::LoadError < LoadError
165
+ attr_accessor name: String
166
+ attr_accessor requirement: Gem::Requirement
167
+ end
168
+
169
+ class Gem::SourceList
170
+ include Enumerable[untyped]
171
+ def initialize: () -> void
172
+ def include?: (untyped) -> bool
173
+ def each: () { (untyped) -> untyped } -> void
174
+ def to_a: () -> Array[untyped]
175
+ def from: (untyped) -> SourceList
176
+ end
177
+
178
+ class Gem::RequestSet
179
+ def initialize: (*untyped) -> void
180
+ def import: (untyped) -> untyped
181
+ def source_set: () -> untyped
182
+ def installed_gems: () -> Hash[String, untyped]
183
+ def install: (?untyped options) { (?untyped) -> untyped } -> untyped
184
+ def install_from_gemdeps: (untyped options) { (?untyped) -> untyped } -> untyped
185
+ def resolve: (?untyped) -> untyped
186
+ def resolve_current: () -> untyped
187
+ def resolve_dependencies: () -> untyped
188
+ def errors: () -> Array[untyped]
189
+ attr_accessor remote: bool
190
+ attr_accessor development: bool
191
+ attr_accessor development_shallow: bool
192
+ attr_accessor ignore_dependencies: bool
193
+ attr_accessor prerelease: bool
194
+ attr_accessor soft_missing: bool
195
+ attr_accessor without_groups: Array[Symbol]
196
+ attr_accessor always_install: untyped
197
+ attr_accessor installing: bool
198
+ end
199
+
200
+ class Gem::DependencyInstaller
201
+ def initialize: (*untyped) -> void
202
+ def install: (untyped, ?untyped) -> Array[untyped]
203
+ def installed_gems: () -> Array[untyped]
204
+ def errors: () -> Array[untyped]
205
+ end
206
+
207
+ class Gem::Uninstaller
208
+ def initialize: (*untyped) -> void
209
+ def uninstall: () -> void
210
+ end
211
+
212
+ class Gem::PathSupport
213
+ def initialize: (*untyped) -> void
214
+ end
215
+
216
+ class Gem::Installer
217
+ def initialize: (*untyped) -> void
218
+ end
219
+
220
+ class Gem::DependencyInstaller
221
+ def initialize: (*untyped) -> void
222
+ end
223
+
224
+ class Gem::MissingSpecError < StandardError
225
+ def initialize: (*untyped) -> void
226
+ end
@@ -0,0 +1,311 @@
1
+ # Getting started
2
+
3
+ By the end of this chapter you will be able to:
4
+
5
+ - get `rigor` onto your `PATH` (the fast AI-assisted way, or
6
+ by hand);
7
+ - run `rigor check` and read the diagnostics it prints;
8
+ - understand the "no annotations needed" stance that sets
9
+ Rigor apart from most checkers, and the escape hatches for
10
+ when inference falls short.
11
+
12
+ It is the only chapter you must read top to bottom. The rest
13
+ of the handbook is reference you can dip into later.
14
+
15
+ ## Installing Rigor
16
+
17
+ Rigor is a tool, not a library — like a linter or a compiler, it
18
+ analyses your project but is not part of its runtime. **Do not add
19
+ it to your application's `Gemfile`.** Install it on its own and
20
+ point it at your project.
21
+
22
+ Rigor itself runs on Ruby 4.0, independently of the Ruby your own
23
+ code targets. The [`target_ruby:` config key](#the-config-file)
24
+ tells Rigor which Ruby *your* project runs.
25
+
26
+ ### The fast path: let an AI agent set it up
27
+
28
+ If you work with an AI coding agent (Claude Code or any assistant
29
+ that supports [Agent Skills](https://agentskills.io/)), hand it
30
+ this prompt:
31
+
32
+ ```
33
+ Install Rigor in this project by following the instructions at
34
+ https://raw.githubusercontent.com/rigortype/rigor/refs/heads/master/docs/install.md
35
+ ```
36
+
37
+ The agent detects your environment, installs Rigor, then runs the
38
+ **`rigor-project-init` skill** — which walks your `Gemfile`,
39
+ proposes a plugin set matched to your framework, picks an adoption
40
+ mode, and writes `.rigor.dist.yml` for you. No manual YAML editing.
41
+ This is the recommended path; the [config file](#the-config-file)
42
+ section below shows what the skill produces so you can read and
43
+ tweak it afterwards.
44
+
45
+ The same prompt is available in sixteen languages in the
46
+ [Rails quickstart](../manual/14-rails-quickstart.md#step-1--install-ruby-40-and-rigor-common-to-both-paths).
47
+
48
+ ### The manual path: mise
49
+
50
+ If you prefer to drive the setup yourself, the recommended runtime
51
+ version manager is [`mise`](https://mise.jdx.dev/), which
52
+ provisions both Ruby 4.0 and Rigor:
53
+
54
+ ```sh
55
+ mise use ruby@4.0
56
+ mise use gem:rigortype
57
+ ```
58
+
59
+ With mise activated in your shell, `rigor` is on your `PATH`. The
60
+ gem is named `rigortype` (the name `rigor` was taken on RubyGems);
61
+ the executable it installs is `rigor`. If you already have Ruby
62
+ 4.0, `gem install rigortype` works too.
63
+
64
+ For shell activation and shims, `asdf`, and developing inside a
65
+ container, see [Installing Rigor](../manual/01-installation.md);
66
+ for continuous integration, see
67
+ [Running Rigor in CI](../manual/11-ci.md).
68
+
69
+ ## What does `rigor check` look at?
70
+
71
+ Rigor reads your `.rb` files, runs a flow-sensitive type
72
+ inference engine over each one, consults any `sig/*.rbs`
73
+ declarations available to your project, and reports a small
74
+ catalogue of bugs:
75
+
76
+ - methods called on the wrong receiver class;
77
+ - methods called with the wrong number of arguments;
78
+ - arithmetic that can be proved to raise (`5 / 0`);
79
+ - arguments whose type does not satisfy a refined parameter
80
+ contract;
81
+ - a few more, all listed in
82
+ [Chapter 8 — Understanding errors](08-understanding-errors.md).
83
+
84
+ Rigor does **not** ask you to write type annotations in
85
+ your Ruby source. It infers as much as it can, and stays
86
+ silent everywhere it cannot prove a narrower type.
87
+ A diagnostic only fires when Rigor has enough static
88
+ information to be confident.
89
+
90
+ ## The smallest working session
91
+
92
+ Drop into your project root and run:
93
+
94
+ ```sh
95
+ rigor check lib
96
+ ```
97
+
98
+ That walks every `.rb` under `lib/`. When the analyzer finds
99
+ nothing to complain about, it prints `No diagnostics` and
100
+ exits `0`:
101
+
102
+ ```text
103
+ No diagnostics
104
+ ```
105
+
106
+ When it does find something, each diagnostic is one line. Given
107
+ this file:
108
+
109
+ ```ruby
110
+ # lib/demo.rb
111
+ "hello".no_such_method # typo'd method name
112
+ [1, 2, 3].rotate(1, 2) # too many arguments
113
+ ```
114
+
115
+ `rigor check lib/demo.rb` prints:
116
+
117
+ ```text
118
+ lib/demo.rb:1:9: error: undefined method `no_such_method' for "hello" [call.undefined-method]
119
+ lib/demo.rb:2:11: error: wrong number of arguments to `rotate' on Array (given 2, expected 0..1) [call.wrong-arity]
120
+ ```
121
+
122
+ To run on a single file, pass the file instead of a directory:
123
+
124
+ ```sh
125
+ rigor check path/to/file.rb
126
+ ```
127
+
128
+ And to ask what Rigor inferred at one precise position:
129
+
130
+ ```sh
131
+ rigor type-of lib/foo.rb:10:5
132
+ ```
133
+
134
+ That prints both the rich Rigor type and the conservative
135
+ RBS erasure (the type a non-Rigor RBS tool would see). It is
136
+ the fastest way to ask "what does Rigor think this expression
137
+ produces?"
138
+
139
+ ## Reading a diagnostic
140
+
141
+ Take the first line from the run above:
142
+
143
+ ```text
144
+ lib/demo.rb:1:9: error: undefined method `no_such_method' for "hello" [call.undefined-method]
145
+ ```
146
+
147
+ | Slice | Meaning |
148
+ | --- | --- |
149
+ | `lib/demo.rb:1:9` | File, 1-indexed line, 1-indexed column |
150
+ | `error` | Severity (`error` / `warning` / `info`) |
151
+ | `undefined method ...` | Human-readable message |
152
+ | `[call.undefined-method]` | The qualified rule identifier |
153
+
154
+ The qualified rule identifier is the handle you use to silence,
155
+ demote, or look up a rule. The quickest is an in-source comment
156
+ on the offending line:
157
+
158
+ ```ruby
159
+ "hello".no_such_method # rigor:disable call.undefined-method
160
+ ```
161
+
162
+ The same identifier also drives the `disable:` and
163
+ `severity_overrides:` config keys, and family wildcards work
164
+ (`# rigor:disable call` suppresses every `call.*` rule on that
165
+ line). The full list of families and rules, and when to reach
166
+ for each suppression mechanism, is in
167
+ [Chapter 8 — Understanding errors](08-understanding-errors.md).
168
+
169
+ ## The "no annotations" stance
170
+
171
+ Most static checkers ask the user to annotate types. Rigor
172
+ does the opposite: it looks at what your Ruby code does and
173
+ **proves** types from the values themselves. Three quick
174
+ examples:
175
+
176
+ ```ruby
177
+ n = 100
178
+ m = n + 1
179
+ assert_type("101", m) # arithmetic folds
180
+ ```
181
+
182
+ ```ruby
183
+ def kind(x)
184
+ case x
185
+ when Integer then :int
186
+ when String then :str
187
+ end
188
+ end
189
+ assert_type(":int | :str | nil", kind(7)) # union of all case branches
190
+ ```
191
+
192
+ ```ruby
193
+ greeting = "Hello, " # Constant<"Hello, ">
194
+ name = ARGV.first # String? (RBS-declared)
195
+ hello = "#{greeting}#{name}!" # literal-string carrier:
196
+ # every interpolated part
197
+ # is itself literal-string-
198
+ # compatible, so the result
199
+ # is "provably source-derived"
200
+ ```
201
+
202
+ You did not write a single annotation. Rigor reasons about the
203
+ values directly.
204
+
205
+ > The `assert_type(...)` line is Rigor's introspection helper,
206
+ > not a runtime check — it pins the inferred type at that point
207
+ > so you can compare the prose to the analyzer's actual output.
208
+ > See [How to read this handbook](README.md#how-to-read-this-handbook)
209
+ > for the full snippet convention.
210
+
211
+ When inference cannot prove a narrower type, the engine
212
+ returns `Dynamic[top]` (the gradual carrier — "could be any
213
+ Ruby value") and stays silent. Rigor never invents
214
+ diagnostics it cannot prove.
215
+
216
+ ## When inference is not enough
217
+
218
+ *First read? Skip this section.* Out of the box, inference plus
219
+ any RBS your gems already ship covers most code, and the next
220
+ chapters teach you to read what it produces. Come back here when
221
+ Rigor resolves something to `Dynamic[top]` that you wish it knew
222
+ more about. For most projects only escape hatches (1) and (2)
223
+ ever come up.
224
+
225
+ There are five escape hatches, in rough order of how often you
226
+ will need them:
227
+
228
+ 1. **Add an `.rbs` file.** Drop a signature into `sig/` and
229
+ Rigor picks it up automatically. This is the most common
230
+ reason inference does not see further than the local
231
+ `def` — by default the analyzer treats every external gem
232
+ as `Dynamic[top]` unless the gem ships RBS or you
233
+ opt in to gem-source inference per (4) below.
234
+ 2. **Tighten an existing RBS sig with `RBS::Extended`.** Add
235
+ a `%a{rigor:v1:return: non-empty-string}` annotation
236
+ above the method's `def ... -> ::String` line. Rigor
237
+ sees the refinement; ordinary RBS tools see a comment.
238
+ 3. **Write a plugin.** When your project has a domain DSL
239
+ (`Lisp.eval`, `100.kilometers`, `transition_to(:foo)`)
240
+ that no general-purpose analyzer can know about, a
241
+ plugin teaches Rigor about it.
242
+ 4. **Opt in to gem-source inference.** When a no-RBS gem's
243
+ methods would otherwise resolve to `Dynamic[top]`, list
244
+ the gem under `dependencies.source_inference:` in
245
+ `.rigor.dist.yml` and Rigor will walk its `lib/` the same way
246
+ it walks project source. Returns are wrapped in
247
+ `Dynamic[T]` so the call site retains the provenance.
248
+ See [ADR-10](../adr/10-dependency-source-inference.md)
249
+ for the trade-offs (per-gem opt-in by design — broad
250
+ defaults would inflate budgets and make `bundle update`
251
+ noisy).
252
+ 5. **Use the `rigor-sorbet` adapter.** If your project
253
+ already uses [Sorbet](https://sorbet.org/), Rigor can
254
+ read your existing `sig { ... }` blocks, RBI files, and
255
+ `T.let` / `T.cast` / `T.must` / `T.unsafe` assertions
256
+ as type sources without rewriting anything. See
257
+ [Chapter 10](10-sorbet.md) for the migration patterns
258
+ and the translation table.
259
+
260
+ Chapters 7 and 9 cover (1)–(3) in detail; chapter 10 covers
261
+ (5).
262
+
263
+ ## The config file
264
+
265
+ The minimum useful run needs no config file at all —
266
+ `rigor check lib` works out of the box. A config file is for
267
+ non-default behaviours: extra `paths`, an alternative
268
+ `severity_profile`, project-wide rule disables, plugins.
269
+
270
+ If you used the [AI-assisted setup](#the-fast-path-let-an-ai-agent-set-it-up),
271
+ the `rigor-project-init` skill already wrote one for you. To
272
+ write a starter by hand, `rigor init` emits `.rigor.dist.yml`
273
+ — the project default that gets committed:
274
+
275
+ ```yaml
276
+ target_ruby: "3.4" # your project's Ruby — not Rigor's own 4.0
277
+
278
+ paths:
279
+ - lib
280
+
281
+ # signature_paths: [sig] # auto-detected when omitted
282
+
283
+ severity_profile: balanced
284
+
285
+ # severity_overrides:
286
+ # call.argument-type-mismatch: warning
287
+
288
+ # disable: []
289
+
290
+ # plugins: []
291
+ ```
292
+
293
+ That is all most projects need. The remaining mechanics —
294
+ editor autocomplete from the bundled JSON schema, the
295
+ `.rigor.yml` vs `.rigor.dist.yml` precedence rule, `includes:`
296
+ composition, and how path-bearing keys resolve relative to the
297
+ declaring file — are covered in
298
+ [Configuration](../manual/03-configuration.md). The one rule
299
+ worth knowing up front: when a developer keeps a local
300
+ `.rigor.yml`, it is the *sole* source of config for their runs
301
+ (the two files are never merged automatically), so to extend
302
+ the shared default it must list it under `includes:`.
303
+
304
+ ## What's next
305
+
306
+ Chapter 2 introduces the carriers Rigor uses to represent
307
+ types — the part of the model that distinguishes Rigor from
308
+ ordinary RBS. After that, Chapter 3 (narrowing) makes the
309
+ carriers come alive: the carriers describe values, and
310
+ narrowing describes how those carriers change as control
311
+ flow passes through `if` / `case` / predicate methods.