rigortype 0.1.7 → 0.1.9
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 +186 -513
- data/lib/rigor/analysis/check_rules.rb +23 -1
- data/lib/rigor/analysis/diagnostic.rb +17 -3
- data/lib/rigor/analysis/runner.rb +178 -3
- data/lib/rigor/analysis/worker_session.rb +14 -3
- data/lib/rigor/cli/annotate_command.rb +224 -0
- data/lib/rigor/cli/baseline_command.rb +36 -16
- data/lib/rigor/cli/prism_colorizer.rb +111 -0
- data/lib/rigor/cli/triage_command.rb +83 -0
- data/lib/rigor/cli/triage_renderer.rb +77 -0
- data/lib/rigor/cli.rb +71 -5
- data/lib/rigor/environment.rb +9 -1
- data/lib/rigor/inference/builtins/method_catalog.rb +17 -1
- data/lib/rigor/inference/builtins/time_catalog.rb +10 -1
- data/lib/rigor/inference/expression_typer.rb +300 -18
- data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +109 -0
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +173 -10
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +53 -1
- data/lib/rigor/inference/method_dispatcher/math_folding.rb +149 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +20 -1
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +33 -8
- data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +81 -0
- data/lib/rigor/inference/method_dispatcher/set_folding.rb +81 -0
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +316 -2
- data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +126 -0
- data/lib/rigor/inference/method_dispatcher/time_folding.rb +56 -0
- data/lib/rigor/inference/method_dispatcher/uri_folding.rb +67 -0
- data/lib/rigor/inference/method_dispatcher.rb +179 -4
- data/lib/rigor/inference/method_parameter_binder.rb +67 -10
- data/lib/rigor/inference/narrowing.rb +29 -10
- data/lib/rigor/inference/scope_indexer.rb +156 -6
- data/lib/rigor/inference/statement_evaluator.rb +43 -21
- data/lib/rigor/plugin/base.rb +39 -0
- data/lib/rigor/plugin/loader.rb +22 -1
- data/lib/rigor/plugin/manifest.rb +73 -10
- data/lib/rigor/plugin/protocol_contract.rb +185 -0
- data/lib/rigor/plugin/registry.rb +66 -0
- data/lib/rigor/scope.rb +46 -0
- data/lib/rigor/triage/catalogue.rb +296 -0
- data/lib/rigor/triage/hint.rb +27 -0
- data/lib/rigor/triage.rb +89 -0
- data/lib/rigor/type/constant.rb +29 -2
- data/lib/rigor/version.rb +1 -1
- data/sig/rigor/inference.rbs +1 -0
- data/sig/rigor/scope.rbs +6 -0
- metadata +16 -1
data/README.md
CHANGED
|
@@ -4,106 +4,114 @@
|
|
|
4
4
|
[](https://github.com/rigortype/rigor/blob/master/LICENSE)
|
|
5
5
|
[](https://deepwiki.com/rigortype/rigor)
|
|
6
6
|
|
|
7
|
-
**Inference-first static analysis for Ruby.**
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
(`
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
When you want tighter types than RBS expresses, refine them
|
|
46
|
-
through the
|
|
47
|
-
[`RBS::Extended`](docs/type-specification/rbs-extended.md)
|
|
48
|
-
annotation surface — `rigor:v1:return:` /
|
|
49
|
-
`rigor:v1:param:` / `rigor:v1:assert` directives accept the
|
|
50
|
-
imported-built-in refinement names (`non-empty-string`,
|
|
51
|
-
`positive-int`, `non-empty-array[Integer]`, `int<5, 10>`,
|
|
52
|
-
`literal-string`, `non-lowercase-string`, …) without changing
|
|
53
|
-
the underlying RBS.
|
|
7
|
+
**Inference-first static analysis for Ruby.** Run `rigor check` over your
|
|
8
|
+
code — no annotations, no runtime dependency, no DSL.
|
|
9
|
+
|
|
10
|
+
Rigor parses Ruby with [Prism](https://github.com/ruby/prism), runs a
|
|
11
|
+
flow-sensitive type-inference engine over each file, and reports a small
|
|
12
|
+
but trustworthy catalogue of bugs: undefined methods on typed receivers,
|
|
13
|
+
wrong-argument-count calls, provable divisions by zero, and more.
|
|
14
|
+
|
|
15
|
+
**Your team never needs to write type annotations** — Rigor derives
|
|
16
|
+
everything from the code itself. An AI Skill gets a project running in
|
|
17
|
+
minutes with a configuration tailored to its stack. When you want to go
|
|
18
|
+
deeper — tighter checks, framework-aware analysis, your own refinements —
|
|
19
|
+
Rigor's type system is remarkably powerful, and a Skill takes you there
|
|
20
|
+
step-by-step.
|
|
21
|
+
|
|
22
|
+
**Three design commitments drive Rigor.**
|
|
23
|
+
|
|
24
|
+
1. **Types are facts, not wishes.** Hand-written annotations drift the
|
|
25
|
+
moment they are written. Rigor infers from the code itself — every
|
|
26
|
+
type is derived from what your source actually produces. When you do
|
|
27
|
+
want RBS in `sig/`, [`rigor sig-gen`](https://rigor.typedduck.fail/reference/adr/14-rbs-sig-generation/)
|
|
28
|
+
emits it from inference results so the written form starts in sync
|
|
29
|
+
with reality.
|
|
30
|
+
2. **Your specs are types.** Do you really need to write type
|
|
31
|
+
annotations? Your `spec/` is already type information. RSpec
|
|
32
|
+
`expect(x).to be_a(T)` / `eq(literal)` assertions contribute
|
|
33
|
+
narrowing facts from that point forward; `subject` / `let` bindings
|
|
34
|
+
propagate the SUT's type into downstream `it` bodies; factory
|
|
35
|
+
definitions in `spec/factories/` feed attribute shapes back into
|
|
36
|
+
`rigor-activerecord` and `rigor-factorybot`'s cross-plugin channels.
|
|
37
|
+
The test suite you already have becomes a live type oracle.
|
|
38
|
+
3. **Programmable inference beyond unions.** A plain union
|
|
39
|
+
(`Integer | nil`) is not the type story Ruby needs. Rigor reasons
|
|
40
|
+
about *what values an expression actually produces* — literal values,
|
|
41
|
+
integer ranges, refinement carriers, per-position tuple / hash shapes
|
|
42
|
+
— and exposes a plugin API plus a
|
|
43
|
+
[macro / DSL expansion substrate](https://rigor.typedduck.fail/reference/adr/16-macro-expansion/)
|
|
44
|
+
so Rails-shape DSLs become first-class type sources.
|
|
54
45
|
|
|
55
46
|
## Installation
|
|
56
47
|
|
|
57
|
-
|
|
58
|
-
|
|
48
|
+
Rigor is a tool, not a library — install it independently, **not** in
|
|
49
|
+
your project's `Gemfile`. It runs on Ruby 4.0, regardless of which Ruby
|
|
50
|
+
version your project targets.
|
|
59
51
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
gem "rigortype", require: false
|
|
63
|
-
end
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
Install:
|
|
52
|
+
The recommended setup uses [`mise`](https://mise.jdx.dev/), which
|
|
53
|
+
provisions both Ruby 4.0 and Rigor pinned per project:
|
|
67
54
|
|
|
68
55
|
```sh
|
|
69
|
-
|
|
56
|
+
mise use ruby@4.0
|
|
57
|
+
mise use gem:rigortype
|
|
70
58
|
```
|
|
71
59
|
|
|
72
|
-
|
|
60
|
+
If you already have Ruby 4.0 available, `gem install rigortype` works too.
|
|
61
|
+
The gem is named `rigortype` (the name `rigor` was taken on RubyGems);
|
|
62
|
+
the executable it installs is `rigor`.
|
|
63
|
+
|
|
64
|
+
Full options — `asdf`, dev containers, CI workflow template — are in the
|
|
65
|
+
[installation guide](https://rigor.typedduck.fail/reference/manual/01-installation/)
|
|
66
|
+
and [CI guide](https://rigor.typedduck.fail/reference/manual/10-ci/).
|
|
67
|
+
|
|
68
|
+
## Getting started with AI Skills
|
|
69
|
+
|
|
70
|
+
The fastest path from zero to a running Rigor setup is the
|
|
71
|
+
**`rigor-project-init` Skill** — an AI agent skill bundled under
|
|
72
|
+
[`skills/`](skills/) that detects your stack, recommends the right plugins,
|
|
73
|
+
and writes `.rigor.dist.yml` for you:
|
|
73
74
|
|
|
74
|
-
```
|
|
75
|
-
|
|
75
|
+
```
|
|
76
|
+
# In Claude Code or any AI assistant that supports Agent Skills:
|
|
77
|
+
Use the rigor-project-init skill
|
|
76
78
|
```
|
|
77
79
|
|
|
78
|
-
The
|
|
79
|
-
|
|
80
|
+
The Skill walks your `Gemfile`, proposes a plugin set matched to your
|
|
81
|
+
framework (Rails, Sinatra, dry-rb, …), lets you choose an adoption mode —
|
|
82
|
+
**baseline** (acknowledge existing diagnostics and work them down
|
|
83
|
+
incrementally) or **strict** (zero-diagnostic gate from day one) — then
|
|
84
|
+
commits a ready-to-use configuration. No manual YAML editing required.
|
|
80
85
|
|
|
81
|
-
|
|
86
|
+
Two companion Skills continue the journey once you are up and running:
|
|
82
87
|
|
|
83
|
-
|
|
88
|
+
| Skill | Use when |
|
|
89
|
+
| --- | --- |
|
|
90
|
+
| [`rigor-baseline-reduce`](skills/rigor-baseline-reduce/) | Reducing an existing baseline: `rigor triage` prioritisation → site-by-site classification → fix / `# rigor:disable` / open a Rigor issue |
|
|
91
|
+
| [`rigor-plugin-author`](skills/rigor-plugin-author/) | Teaching Rigor about a project-specific DSL or framework — authors the plugin gem or project-private plugin |
|
|
84
92
|
|
|
85
|
-
|
|
93
|
+
All three Skills follow the [agentskills.io](https://agentskills.io/)
|
|
94
|
+
convention and work with any AI assistant that discovers skills in your
|
|
95
|
+
project directory.
|
|
96
|
+
|
|
97
|
+
## Quick start
|
|
86
98
|
|
|
87
99
|
```sh
|
|
88
|
-
#
|
|
89
|
-
|
|
90
|
-
bundle exec rigor check lib
|
|
100
|
+
# Check lib/ for bugs.
|
|
101
|
+
rigor check lib
|
|
91
102
|
|
|
92
|
-
# Drop a starter .rigor.yml
|
|
93
|
-
|
|
103
|
+
# Drop a starter .rigor.yml.
|
|
104
|
+
rigor init
|
|
94
105
|
|
|
95
106
|
# Print the inferred type at a precise FILE:LINE:COL position.
|
|
96
|
-
|
|
107
|
+
rigor type-of lib/foo.rb:10:5
|
|
97
108
|
|
|
98
|
-
#
|
|
99
|
-
|
|
100
|
-
bundle exec rigor type-scan lib
|
|
109
|
+
# Summarise the diagnostic stream: distribution, hotspots, heuristic hints.
|
|
110
|
+
rigor triage lib
|
|
101
111
|
|
|
102
|
-
# Emit RBS
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
bundle exec rigor sig-gen --diff lib/foo.rb
|
|
106
|
-
bundle exec rigor sig-gen --write lib/foo.rb
|
|
112
|
+
# Emit RBS from inference results — review with --diff, write with --write.
|
|
113
|
+
rigor sig-gen --diff lib/foo.rb
|
|
114
|
+
rigor sig-gen --write lib/foo.rb
|
|
107
115
|
```
|
|
108
116
|
|
|
109
117
|
### Sample output
|
|
@@ -113,482 +121,147 @@ $ cat /tmp/demo.rb
|
|
|
113
121
|
"hello".no_such_method # undefined method
|
|
114
122
|
[1, 2, 3].rotate(1, 2) # wrong number of arguments
|
|
115
123
|
|
|
116
|
-
$
|
|
124
|
+
$ rigor check /tmp/demo.rb
|
|
117
125
|
/tmp/demo.rb:1:9: error: undefined method `no_such_method' for "hello"
|
|
118
126
|
/tmp/demo.rb:2:11: error: wrong number of arguments to `rotate' on Array (given 2, expected 0..1)
|
|
119
127
|
```
|
|
120
128
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
known and the method set on that class is enumerable through
|
|
124
|
-
RBS or in-source `def` / `define_method` discovery. Implicit-
|
|
125
|
-
self calls, dynamic receivers, and constant-decl alias classes
|
|
126
|
-
(e.g. `YAML` → `Psych`) are skipped to avoid false positives.
|
|
129
|
+
Diagnostics fire only when the receiver type is statically known and the
|
|
130
|
+
evidence is conclusive — Rigor never surfaces a warning it cannot prove.
|
|
127
131
|
|
|
128
132
|
### Faster runs through the cache
|
|
129
133
|
|
|
130
|
-
Rigor caches
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
`rigor check` is significantly faster than the first. The cache
|
|
134
|
-
is keyed by your project's `.rbs` file digests + the locked
|
|
135
|
-
`rbs` gem version, so a signature change or a gem upgrade
|
|
136
|
-
invalidates exactly what it should.
|
|
134
|
+
Rigor caches RBS work (loaded environment, type translation, class
|
|
135
|
+
hierarchy) under `.rigor/cache/` — the second `rigor check` is
|
|
136
|
+
significantly faster than the first. Add `.rigor/` to your `.gitignore`.
|
|
137
137
|
|
|
138
138
|
```sh
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
# Wipe the cache (do this if you suspect staleness).
|
|
143
|
-
bundle exec rigor check --clear-cache lib
|
|
144
|
-
|
|
145
|
-
# Run with caching disabled.
|
|
146
|
-
bundle exec rigor check --no-cache lib
|
|
139
|
+
rigor check --cache-stats lib # inspect cache hit/miss
|
|
140
|
+
rigor check --clear-cache lib # wipe if you suspect staleness
|
|
147
141
|
```
|
|
148
142
|
|
|
149
|
-
|
|
150
|
-
and contains nothing reproducible to share.
|
|
151
|
-
|
|
152
|
-
## Beyond `Integer` and `String`: Rigor's richer type vocabulary
|
|
143
|
+
## The type vocabulary
|
|
153
144
|
|
|
154
|
-
A vanilla
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
classes, but real-world code carries far more structure (a count
|
|
159
|
-
that's always non-negative, a name that's never empty, a flag
|
|
160
|
-
that's one of three Symbols). Rigor reasons about that structure
|
|
161
|
-
out of the box, without you writing a single annotation.
|
|
162
|
-
|
|
163
|
-
### The carrier zoo
|
|
145
|
+
A vanilla type checker answers "what *class* is this object?" Rigor
|
|
146
|
+
answers "what *subset of values* can this expression produce?" — tracking
|
|
147
|
+
literal values, integer ranges, refinement carriers, and structural shapes
|
|
148
|
+
through the whole analysis, without a single annotation.
|
|
164
149
|
|
|
165
150
|
| Carrier | What it records | Example |
|
|
166
151
|
| --- | --- | --- |
|
|
167
|
-
| **Literal
|
|
168
|
-
| **Integer
|
|
169
|
-
| **Refinement
|
|
170
|
-
| **
|
|
171
|
-
| **
|
|
172
|
-
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
###
|
|
183
|
-
|
|
184
|
-
```ruby
|
|
185
|
-
# Rigor doesn't just see "Integer", it sees "non-negative integer".
|
|
186
|
-
n = ARGV.size # int<0, max> (non-negative-int)
|
|
187
|
-
m = n + 1 # int<1, max> (positive-int)
|
|
188
|
-
m.zero? # Constant<false> — proven; the
|
|
189
|
-
# branch elision can drop the `else`
|
|
190
|
-
|
|
191
|
-
# String composition stays as precise as the inputs allow.
|
|
192
|
-
greeting = "Hello, " # Constant<"Hello, ">
|
|
193
|
-
name = ARGV.first # String? — RBS-declared
|
|
194
|
-
hello = "Hello, #{name}!" # literal-string — every part is
|
|
195
|
-
# literal-bearing, so the result is
|
|
196
|
-
# provably source-derived.
|
|
197
|
-
|
|
198
|
-
# Tuple-shaped destructuring stays per-position.
|
|
199
|
-
first, _middle, last = [10, 20, 30]
|
|
200
|
-
first # Constant<10>
|
|
201
|
-
last # Constant<30>
|
|
202
|
-
|
|
203
|
-
# Constant folding through user methods.
|
|
204
|
-
def is_odd(n) = n.odd?
|
|
205
|
-
is_odd(3) # Constant<true> — folded through
|
|
206
|
-
# the body, not just typed as `bool`
|
|
207
|
-
|
|
208
|
-
# Case/when narrowing produces a literal-set Union.
|
|
209
|
-
label = case n
|
|
210
|
-
when 0 then :zero
|
|
211
|
-
when 1..9 then :small
|
|
212
|
-
else :large
|
|
213
|
-
end
|
|
214
|
-
label # Constant<:zero> | Constant<:small>
|
|
215
|
-
# | Constant<:large>
|
|
216
|
-
|
|
217
|
-
# Method bindings keep their receiver — `.method(:sym).call`
|
|
218
|
-
# round-trips through the original dispatch.
|
|
219
|
-
[:to_i, :to_f, :to_sym].map { |m| "1".method(m).call }
|
|
220
|
-
# Tuple[Constant<1>, Constant<1.0>, Constant<:"1">]
|
|
221
|
-
# — per-element fold + BoundMethod backward fold
|
|
222
|
-
|
|
223
|
-
# RBS::Extended directives let you tighten beyond what RBS expresses.
|
|
224
|
-
class Slug
|
|
225
|
-
%a{rigor:v1:return: non-empty-string}
|
|
226
|
-
def normalise: (::String id) -> ::String
|
|
227
|
-
end
|
|
228
|
-
Slug.new.normalise("foo").size # positive-int — provably ≥ 1
|
|
229
|
-
```
|
|
152
|
+
| **Literal** (`Constant`) | A single Ruby value | `Constant<42>`, `Constant<"hello">`, `Constant<:foo>` |
|
|
153
|
+
| **Integer range** | A bounded interval | `positive-int = int<1, max>`, `int<5, 10>` |
|
|
154
|
+
| **Refinement** | Base type minus a point, or restricted by a predicate | `non-empty-string = String - ""`, `lowercase-string` |
|
|
155
|
+
| **Tuple / HashShape** | Heterogeneous arrays / known-key hashes | `[1, "two"]` → `Tuple[Constant<1>, Constant<"two">]` |
|
|
156
|
+
| **Union** | Finite enumerable set of literals | `Constant<:zero> \| Constant<:small> \| Constant<:large>` |
|
|
157
|
+
| **`Dynamic[T]`** | Gradual carrier — "could be anything" | `Dynamic[Top]` when proof is unavailable |
|
|
158
|
+
|
|
159
|
+
Every narrower carrier **erases to its base class** for RBS interop, so
|
|
160
|
+
importing Rigor is a strictly additive change.
|
|
161
|
+
|
|
162
|
+
The full type model — constant folding, `Method` bindings, LightweightHKT,
|
|
163
|
+
`RBS::Extended` directives — is in the
|
|
164
|
+
[handbook](https://rigor.typedduck.fail/reference/handbook/) and
|
|
165
|
+
[type specification](https://rigor.typedduck.fail/reference/type-specification/).
|
|
166
|
+
|
|
167
|
+
### Unlocking tighter types with `RBS::Extended`
|
|
230
168
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
or `RBS::Extended` directives the user opted into. When the
|
|
236
|
-
inference cannot prove a value is in a narrower carrier, it
|
|
237
|
-
stays at the wider one (or `Dynamic[Top]`) and Rigor stays
|
|
238
|
-
silent — diagnostics fire only when the narrow type is
|
|
239
|
-
genuinely proved.
|
|
240
|
-
|
|
241
|
-
### Where the type model is documented
|
|
242
|
-
|
|
243
|
-
- **End-user handbook** — chapter-by-chapter walkthrough of
|
|
244
|
-
the type model written for Ruby programmers without prior
|
|
245
|
-
static-typing background:
|
|
246
|
-
[`docs/handbook/`](docs/handbook/README.md). Start here if
|
|
247
|
-
you want a guided tour of how Rigor sees your code rather
|
|
248
|
-
than a spec deep-dive.
|
|
249
|
-
- One-page mental model:
|
|
250
|
-
[`docs/types.md`](docs/types.md).
|
|
251
|
-
- Binding spec corpus:
|
|
252
|
-
[`docs/type-specification/`](docs/type-specification/README.md).
|
|
253
|
-
- Imported refinement names (kebab-case catalogue):
|
|
254
|
-
[`docs/type-specification/imported-built-in-types.md`](docs/type-specification/imported-built-in-types.md).
|
|
255
|
-
- The `RBS::Extended` annotation grammar that opens this
|
|
256
|
-
vocabulary up to your own RBS:
|
|
257
|
-
[`docs/type-specification/rbs-extended.md`](docs/type-specification/rbs-extended.md).
|
|
258
|
-
|
|
259
|
-
## How Rigor finds your types
|
|
260
|
-
|
|
261
|
-
Rigor consults, in order:
|
|
262
|
-
|
|
263
|
-
1. **In-source RBS.** If your project has a `sig/` directory,
|
|
264
|
-
Rigor auto-loads it. `rigor init` writes a `.rigor.yml`
|
|
265
|
-
that points at `sig/` by default.
|
|
266
|
-
2. **Bundled RBS core + stdlib.** Pathname, OptParse, JSON,
|
|
267
|
-
YAML, etc. ship with the analyzer.
|
|
268
|
-
3. **Gem RBS.** RBS files vendored with installed gems
|
|
269
|
-
(Prism's own `.rbs`, the `rbs` gem's, …).
|
|
270
|
-
4. **In-source class discovery.** When no RBS is available,
|
|
271
|
-
Rigor walks `def` / `define_method` / `attr_*` /
|
|
272
|
-
`Data.define(*Symbol)` so user-defined methods on a class
|
|
273
|
-
are recognised.
|
|
274
|
-
5. **Opt-in gem-source inference (ADR-10).** Gems listed
|
|
275
|
-
under `dependencies.source_inference:` in `.rigor.yml`
|
|
276
|
-
have their `lib/` walked the same way project source is,
|
|
277
|
-
so methods on those gems' classes resolve even without
|
|
278
|
-
RBS. Inferred returns crossing the gem boundary are
|
|
279
|
-
wrapped in `Dynamic[T]` so the call site retains the
|
|
280
|
-
provenance — RBS / RBS::Inline / generated stubs / plugin
|
|
281
|
-
contracts always win on conflict. Default behaviour is
|
|
282
|
-
unchanged: gems not listed stay at the
|
|
283
|
-
RBS-or-`Dynamic[Top]` boundary.
|
|
284
|
-
|
|
285
|
-
If a type cannot be proved, the engine returns `Dynamic[Top]`
|
|
286
|
-
(Rigor's gradual carrier) and stays silent — Rigor never invents
|
|
287
|
-
diagnostics it cannot prove.
|
|
288
|
-
|
|
289
|
-
## Refining types through `RBS::Extended`
|
|
290
|
-
|
|
291
|
-
When the RBS-declared type is too wide, attach a
|
|
292
|
-
`%a{rigor:v1:…}` annotation to the relevant method in your
|
|
293
|
-
`sig/` file. The annotation is a no-op for ordinary RBS tools
|
|
294
|
-
and a tightening signal for Rigor.
|
|
169
|
+
When you *want* to express more than RBS allows, attach a
|
|
170
|
+
`%a{rigor:v1:…}` annotation in your `sig/` file. The annotation is a
|
|
171
|
+
no-op for ordinary RBS tools; Rigor uses it to tighten call-site and
|
|
172
|
+
body types.
|
|
295
173
|
|
|
296
174
|
```rbs
|
|
297
175
|
class Slug
|
|
298
|
-
# The runtime always returns a non-empty string. The override
|
|
299
|
-
# tightens the call-site result to non-empty-string and tells
|
|
300
|
-
# the body's `assert_type` that `id` cannot be "".
|
|
301
176
|
%a{rigor:v1:return: non-empty-string}
|
|
302
177
|
%a{rigor:v1:param: id is non-empty-string}
|
|
303
178
|
def normalise: (::String id) -> ::String
|
|
304
179
|
end
|
|
305
180
|
```
|
|
306
181
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
- **Imported-built-in refinement names** (kebab-case):
|
|
312
|
-
- Point-removal — `non-empty-string`, `non-zero-int`,
|
|
313
|
-
`non-empty-array[T]`, `non-empty-hash[K, V]`.
|
|
314
|
-
- IntegerRange aliases — `positive-int`, `non-negative-int`,
|
|
315
|
-
`negative-int`, `non-positive-int`, `int<min, max>`.
|
|
316
|
-
- Predicate refinements — `lowercase-string`,
|
|
317
|
-
`uppercase-string`, `numeric-string`, `decimal-int-string`,
|
|
318
|
-
`octal-int-string`, `hex-int-string`.
|
|
319
|
-
- Paired complements (`~T`-symmetric) —
|
|
320
|
-
`non-lowercase-string`, `non-uppercase-string`,
|
|
321
|
-
`non-numeric-string`. Writing `~lowercase-string` narrows
|
|
322
|
-
`String` to `non-lowercase-string` instead of the generic
|
|
323
|
-
`Difference[String, lowercase-string]` fallback.
|
|
324
|
-
- Composed shapes — `non-empty-lowercase-string`,
|
|
325
|
-
`non-empty-uppercase-string`, `non-empty-literal-string`.
|
|
326
|
-
- Flow-tracked source-literal — `literal-string`. Rigor lifts
|
|
327
|
-
`"hi #{name}!"`, `"a" + literal_str`, and `literal_str * 3`
|
|
328
|
-
to `literal-string` when every operand is itself
|
|
329
|
-
literal-bearing.
|
|
330
|
-
|
|
331
|
-
The full directive table is in
|
|
332
|
-
[`docs/type-specification/rbs-extended.md`](docs/type-specification/rbs-extended.md);
|
|
333
|
-
the catalogue of refinement names is in
|
|
334
|
-
[`docs/type-specification/imported-built-in-types.md`](docs/type-specification/imported-built-in-types.md).
|
|
335
|
-
|
|
336
|
-
### Example: argument-type-mismatch caught at the call site
|
|
182
|
+
This is entirely optional — Rigor runs without any annotations.
|
|
183
|
+
The directive grammar and the full refinement-name catalogue are in
|
|
184
|
+
[`RBS::Extended`](https://rigor.typedduck.fail/reference/type-specification/rbs-extended/)
|
|
185
|
+
and [imported built-in types](https://rigor.typedduck.fail/reference/type-specification/imported-built-in-types/).
|
|
337
186
|
|
|
338
|
-
|
|
339
|
-
# sig/normaliser.rbs
|
|
340
|
-
class Normaliser
|
|
341
|
-
%a{rigor:v1:param: id is non-empty-string}
|
|
342
|
-
def normalise: (::String id) -> ::String
|
|
343
|
-
end
|
|
344
|
-
```
|
|
187
|
+
## Plugins
|
|
345
188
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
class Normaliser
|
|
349
|
-
def normalise(id)
|
|
350
|
-
id.upcase
|
|
351
|
-
end
|
|
352
|
-
end
|
|
189
|
+
Production plugins ship under [`plugins/`](plugins/) for the most common
|
|
190
|
+
Ruby frameworks and gems. Activate them in `.rigor.yml`:
|
|
353
191
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
192
|
+
```yaml
|
|
193
|
+
plugins:
|
|
194
|
+
- rigor-activerecord
|
|
195
|
+
- rigor-actionpack
|
|
196
|
+
- rigor-rspec
|
|
357
197
|
```
|
|
358
198
|
|
|
359
|
-
`rigor
|
|
360
|
-
`
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
-
|
|
384
|
-
|
|
385
|
-
optional policies, per-element block fold over
|
|
386
|
-
`map`, `select`, `filter_map`, `flat_map`, `find` /
|
|
387
|
-
`find_index`, `count`, `any?` / `all?` / `none?`, `zip`.
|
|
388
|
-
`&:symbol` block-pass on these methods is treated as
|
|
389
|
-
`{ |x| x.symbol }` and dispatches against the element type
|
|
390
|
-
so `Hash#transform_values(&:freeze)` returns `Hash[K, V]`
|
|
391
|
-
instead of `Enumerator[...]`.
|
|
392
|
-
- **Constant folding** — aggressive arithmetic / string /
|
|
393
|
-
Symbol / Tuple-shaped `divmod` folding, cartesian fold over
|
|
394
|
-
`Union[Constant…]`, integer-range arithmetic
|
|
395
|
-
(`positive-int + 1` → `int<2, max>`), branch elision on
|
|
396
|
-
provably-truthy / falsey predicates,
|
|
397
|
-
`Constant<String>#%` format-string fold against
|
|
398
|
-
`Tuple` / `HashShape` arguments.
|
|
399
|
-
- **Built-in catalogues** — Numeric / Integer / Float, String /
|
|
400
|
-
Symbol, Array, Hash, IO, File, Range, Set, Time, Date /
|
|
401
|
-
DateTime, Comparable, Enumerable, Rational, Complex,
|
|
402
|
-
Pathname, Random, Struct (+ `Data`), Encoding, Regexp /
|
|
403
|
-
MatchData, Proc / Method / UnboundMethod, Exception. Each
|
|
404
|
-
catalog drives the fold dispatcher with per-class blocklists
|
|
405
|
-
for indirect mutators.
|
|
406
|
-
- **Refinement carriers** — `Type::Difference`,
|
|
407
|
-
`Type::Refined`, `Type::Intersection` provide the
|
|
408
|
-
imported-built-in catalogue end-to-end through
|
|
409
|
-
`Builtins::ImportedRefinements`. The parser accepts Symbol
|
|
410
|
-
/ String literals and `|`-unions at type-arg position
|
|
411
|
-
(`pick_of[Shape, :a | :b]`, `Pick[T, "name" | "email"]`).
|
|
412
|
-
- **`Method` carrier (`Type::BoundMethod`)** —
|
|
413
|
-
`Object#method(:sym)` lifts into a binding carrier so
|
|
414
|
-
`.call` / `.()` / `[]` recover the precise dispatch
|
|
415
|
-
(`"1".method(:to_i).call` resolves to `Constant<1>`).
|
|
416
|
-
Reflective Method members (`#owner` / `#name` / `#arity`)
|
|
417
|
-
still resolve via the Method RBS sig.
|
|
418
|
-
- **`RBS::Extended` directive routes** — `return:`, `param:`
|
|
419
|
-
(call-site + body-side), `assert:` /
|
|
420
|
-
`predicate-if-(true|false)` accept refinement payloads, and
|
|
421
|
-
roll up into a single `Rigor::FlowContribution` bundle per
|
|
422
|
-
method (the v0.1.0 plugin contribution merger reads bundles
|
|
423
|
-
directly).
|
|
424
|
-
- **Opt-in gem-source inference (ADR-10)** — gems listed under
|
|
425
|
-
`dependencies.source_inference:` have their `lib/` walked.
|
|
426
|
-
Per-gem budget, per-gem-version cache slice,
|
|
427
|
-
`dynamic.dependency-source.*` diagnostic family covering
|
|
428
|
-
gem-not-found / budget-exceeded / config-conflict /
|
|
429
|
-
boundary-cross (the last surfaces RBS+gem-source overlap
|
|
430
|
-
on `mode: :full` gems for audit).
|
|
431
|
-
|
|
432
|
-
The full per-release surface lives in
|
|
433
|
-
[`CHANGELOG.md`](CHANGELOG.md). The internal contracts the
|
|
434
|
-
analyzer guarantees live under
|
|
435
|
-
[`docs/internal-spec/`](docs/internal-spec/).
|
|
436
|
-
|
|
437
|
-
## Plugins
|
|
438
|
-
|
|
439
|
-
`v0.1.0` introduced the extension API; `v0.1.x` rounds it out
|
|
440
|
-
with the [ADR-9](docs/adr/9-cross-plugin-api.md) cross-plugin
|
|
441
|
-
fact channel (one plugin publishes a fact like `:model_index`,
|
|
442
|
-
another consumes it), [ADR-11](docs/adr/11-sorbet-input-adapter.md)
|
|
443
|
-
Sorbet ingestion, [ADR-13](docs/adr/13-typenode-resolver-plugin.md)
|
|
444
|
-
plugin-supplied type-vocabulary resolvers, and
|
|
445
|
-
[ADR-16](docs/adr/16-macro-expansion.md) macro / DSL expansion
|
|
446
|
-
substrate (declarative Tier A block-as-method / Tier B
|
|
447
|
-
trait-inlining-registry / Tier C heredoc-template / Tier D
|
|
448
|
-
external-file inclusion). Production plugins ship under
|
|
449
|
-
[`plugins/`](plugins/) — each is a fully-shaped plugin gem
|
|
450
|
-
with a runnable demo and an end-to-end integration spec.
|
|
451
|
-
Plugin-contract walkthroughs (deliberately simplified
|
|
452
|
-
virtual use cases that spotlight one architectural surface
|
|
453
|
-
per example) live under [`examples/`](examples/).
|
|
454
|
-
|
|
455
|
-
**Plugin-contract walkthroughs** (`examples/`, focus on a
|
|
456
|
-
single extension-point):
|
|
457
|
-
|
|
458
|
-
- [`rigor-deprecations`](examples/rigor-deprecations/) —
|
|
459
|
-
smallest possible plugin (~80 lines); config-driven rules.
|
|
460
|
-
- [`rigor-lisp-eval`](examples/rigor-lisp-eval/) — typing literal
|
|
461
|
-
AST arguments at a method call.
|
|
462
|
-
- [`rigor-pattern`](examples/rigor-pattern/) — plugin →
|
|
463
|
-
analyzer collaboration via `Scope#type_of` and the
|
|
464
|
-
literal-string carrier.
|
|
465
|
-
- [`rigor-units`](examples/rigor-units/) — local-variable flow
|
|
466
|
-
tracking through arithmetic.
|
|
467
|
-
- [`rigor-routes`](examples/rigor-routes/) — `Plugin::IoBoundary`
|
|
468
|
-
reads under `TrustPolicy` plus cache producers.
|
|
469
|
-
|
|
470
|
-
**Other production plugins for type-language extension** (`plugins/`):
|
|
471
|
-
|
|
472
|
-
- [`rigor-statesman`](plugins/rigor-statesman/) — two-pass DSL
|
|
473
|
-
analysis (collect declarations, then validate references)
|
|
474
|
-
for the Statesman state-machine gem.
|
|
475
|
-
- [`rigor-typescript-utility-types`](plugins/rigor-typescript-utility-types/)
|
|
476
|
-
— `Plugin::TypeNodeResolver` chain wiring TS-canonical names
|
|
477
|
-
(`Pick` / `Omit` / `Partial` / `Required` / `Readonly`) onto
|
|
478
|
-
Rigor's shape-projection type functions.
|
|
479
|
-
|
|
480
|
-
**Macro expansion substrate consumers** (ADR-16 — declarative
|
|
481
|
-
manifest entries, no walker code):
|
|
482
|
-
|
|
483
|
-
- [`rigor-sinatra`](plugins/rigor-sinatra/) — **Tier A**
|
|
484
|
-
block-as-method. Recognises Sinatra's nine class-level HTTP
|
|
485
|
-
verb methods and narrows the route block's `self_type` so
|
|
486
|
-
bare `params` / `redirect` / `halt` resolve through
|
|
487
|
-
`Sinatra::Base`'s RBS.
|
|
488
|
-
- [`rigor-dry-struct`](plugins/rigor-dry-struct/) — **Tier C**
|
|
489
|
-
heredoc-template. Synthesises a reader on every `Dry::Struct`
|
|
490
|
-
subclass for each `attribute :name, T` / `attribute? :name, T`
|
|
491
|
-
call.
|
|
492
|
-
- [`rigor-devise`](plugins/rigor-devise/) — **Tier B**
|
|
493
|
-
trait-inlining registry mirroring `lib/devise/modules.rb`.
|
|
494
|
-
Each `devise :strategy_a, :strategy_b` call explodes the
|
|
495
|
-
included module's RBS instance methods onto the calling model
|
|
496
|
-
class (Devise's `user.valid_password?` returns the module's
|
|
497
|
-
authored `bool`).
|
|
498
|
-
|
|
499
|
-
**Rails ecosystem plugins** (Tier 1 + Tier 2 + Tier 3 + Sorbet):
|
|
500
|
-
|
|
501
|
-
- Tier 1: [`rigor-rails-routes`](plugins/rigor-rails-routes/),
|
|
502
|
-
[`rigor-rails-i18n`](plugins/rigor-rails-i18n/),
|
|
503
|
-
[`rigor-actionmailer`](plugins/rigor-actionmailer/),
|
|
504
|
-
[`rigor-activejob`](plugins/rigor-activejob/).
|
|
505
|
-
- Tier 2: [`rigor-actionpack`](plugins/rigor-actionpack/)
|
|
506
|
-
(4 phases — routes / filters / renders / strong-params),
|
|
507
|
-
[`rigor-factorybot`](plugins/rigor-factorybot/),
|
|
508
|
-
[`rigor-activerecord`](plugins/rigor-activerecord/) —
|
|
509
|
-
publishes `:model_index` via ADR-9 for the other two
|
|
510
|
-
to consume.
|
|
511
|
-
- Tier 3: [`rigor-pundit`](plugins/rigor-pundit/),
|
|
512
|
-
[`rigor-sidekiq`](plugins/rigor-sidekiq/),
|
|
513
|
-
[`rigor-rspec`](plugins/rigor-rspec/),
|
|
514
|
-
[`rigor-actioncable`](plugins/rigor-actioncable/).
|
|
515
|
-
- Parallel: [`rigor-sorbet`](plugins/rigor-sorbet/) — ingests
|
|
516
|
-
Sorbet `sig` / `T.let` / `T.cast` / `T.must` / `T.bind` /
|
|
517
|
-
`T.assert_type!` / `T.reveal_type` / `T.absurd` and RBI
|
|
518
|
-
files as type sources.
|
|
519
|
-
|
|
520
|
-
[`plugins/README.md`](plugins/README.md) is the production
|
|
521
|
-
plugin catalogue (Rails / RSpec / dry-rb / Sorbet / etc.) and
|
|
522
|
-
[`examples/README.md`](examples/README.md) is the walkthrough
|
|
523
|
-
catalogue — comparison table, recommended reading order, and
|
|
524
|
-
the architectural map of which surface each walkthrough
|
|
525
|
-
exercises. The binding contract for the plugin API lives in
|
|
526
|
-
[`docs/adr/2-extension-api.md`](docs/adr/2-extension-api.md);
|
|
527
|
-
the slice-by-slice normative specs are under
|
|
528
|
-
[`docs/internal-spec/plugin*.md`](docs/internal-spec/); the
|
|
529
|
-
sibling ADRs that extend it ride the same surface
|
|
530
|
-
([ADR-9](docs/adr/9-cross-plugin-api.md) cross-plugin facts,
|
|
531
|
-
[ADR-11](docs/adr/11-sorbet-input-adapter.md) Sorbet adapter,
|
|
532
|
-
[ADR-13](docs/adr/13-typenode-resolver-plugin.md) TypeNode
|
|
533
|
-
resolver).
|
|
199
|
+
**Rails ecosystem** — [`rigor-rails-routes`](plugins/rigor-rails-routes/),
|
|
200
|
+
[`rigor-rails-i18n`](plugins/rigor-rails-i18n/),
|
|
201
|
+
[`rigor-activerecord`](plugins/rigor-activerecord/),
|
|
202
|
+
[`rigor-actionpack`](plugins/rigor-actionpack/),
|
|
203
|
+
[`rigor-actionmailer`](plugins/rigor-actionmailer/),
|
|
204
|
+
[`rigor-activejob`](plugins/rigor-activejob/),
|
|
205
|
+
[`rigor-factorybot`](plugins/rigor-factorybot/),
|
|
206
|
+
[`rigor-pundit`](plugins/rigor-pundit/),
|
|
207
|
+
[`rigor-sidekiq`](plugins/rigor-sidekiq/).
|
|
208
|
+
|
|
209
|
+
**Other frameworks** — [`rigor-sinatra`](plugins/rigor-sinatra/),
|
|
210
|
+
[`rigor-devise`](plugins/rigor-devise/),
|
|
211
|
+
[`rigor-dry-struct`](plugins/rigor-dry-struct/),
|
|
212
|
+
[`rigor-rspec`](plugins/rigor-rspec/),
|
|
213
|
+
[`rigor-actioncable`](plugins/rigor-actioncable/).
|
|
214
|
+
|
|
215
|
+
**Type-system extensions** — [`rigor-sorbet`](plugins/rigor-sorbet/)
|
|
216
|
+
(ingests Sorbet `sig` / `T.let` / RBI files),
|
|
217
|
+
[`rigor-statesman`](plugins/rigor-statesman/),
|
|
218
|
+
[`rigor-typescript-utility-types`](plugins/rigor-typescript-utility-types/).
|
|
219
|
+
|
|
220
|
+
See [`plugins/README.md`](plugins/README.md) for the full catalogue. To
|
|
221
|
+
write a plugin for your own DSL or framework, use the
|
|
222
|
+
[`rigor-plugin-author`](skills/rigor-plugin-author/) Skill described above.
|
|
223
|
+
Plugin-contract walkthroughs (simplified virtual use cases spotlighting
|
|
224
|
+
one extension surface each) are under [`examples/`](examples/).
|
|
534
225
|
|
|
535
226
|
## Configuration
|
|
536
227
|
|
|
537
|
-
`rigor init` writes a starter `.rigor.yml
|
|
228
|
+
`rigor init` writes a starter `.rigor.yml`. Most projects only need a
|
|
229
|
+
handful of lines:
|
|
538
230
|
|
|
539
|
-
```
|
|
540
|
-
|
|
541
|
-
|
|
231
|
+
```yaml
|
|
232
|
+
paths: [lib, app]
|
|
233
|
+
target_ruby: "3.3"
|
|
234
|
+
plugins:
|
|
235
|
+
- rigor-activerecord
|
|
236
|
+
- rigor-actionpack
|
|
237
|
+
baseline: .rigor-baseline.yml # when using baseline adoption mode
|
|
542
238
|
```
|
|
543
239
|
|
|
544
|
-
Common knobs
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
-
|
|
552
|
-
|
|
553
|
-
to disable project-RBS loading entirely.
|
|
554
|
-
- `disable` — rule identifiers to silence project-wide. Shipped
|
|
555
|
-
rules: `undefined-method`, `wrong-arity`,
|
|
556
|
-
`argument-type-mismatch`, `possible-nil-receiver`,
|
|
557
|
-
`dump-type`, `assert-type`, `always-raises`. In-source
|
|
558
|
-
`# rigor:disable <rule>` end-of-line comments silence
|
|
559
|
-
per-line; `# rigor:disable all` suppresses every rule.
|
|
240
|
+
Common knobs: `disable` (rule IDs to silence project-wide),
|
|
241
|
+
`signature_paths` (additional `sig/`-style directories),
|
|
242
|
+
`dependencies.source_inference` (opt-in gem-source walk for gems
|
|
243
|
+
without RBS). The `rigor-project-init` Skill writes and populates all
|
|
244
|
+
of this for you. Full reference on the
|
|
245
|
+
[website](https://rigor.typedduck.fail/).
|
|
246
|
+
|
|
247
|
+
In-source suppression: `# rigor:disable <rule>` silences a single line;
|
|
248
|
+
`# rigor:disable all` suppresses every rule on that line.
|
|
560
249
|
|
|
561
250
|
## Status
|
|
562
251
|
|
|
563
|
-
Current released version: **`v0.1.
|
|
564
|
-
on real Ruby code today; the rule catalogue is deliberately
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
[`
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
`v0.1.5` (released 2026-05-16) delivered (full slice list in `CHANGELOG.md` § `[0.1.5]`):
|
|
572
|
-
|
|
573
|
-
- **ADR-15 Ractor migration end-to-end** (Phases 1–4c + 4b.x) — opt-in `rigor check --workers=N` parallelism; pool ≡ sequential proven on 14 real-world projects (31,840 files); spec-suite wall-clock 162s → 27s on 12 cores via `parallel_tests`.
|
|
574
|
-
- **[ADR-16](docs/adr/16-macro-expansion.md) macro / DSL expansion substrate** — four-tier declarative manifest contract (block-as-method, trait-inlining registry, heredoc-template, external-file) with Tier B/C precision promotion and three worked consumer plugins (`rigor-sinatra`, `rigor-devise`, `rigor-dry-struct`). Closes ROADMAP O2 at the WD13 floor.
|
|
575
|
-
- **Real-world Rails / Ruby survey** — fourteen projects swept; opt-in `rigor-activesupport-core-ext` RBS bundle delivers `−75 %` total diagnostics; built-in vendored gem RBS for six native-extension gems (`pg` / `mysql2` / `nokogiri` / `bcrypt` / `redis` / `idn-ruby`); Bundler-aware sig discovery; `RbsLoader#env` failure-memo (~550× speedup on a conflicting sig).
|
|
576
|
-
- **O4 Layer 3 target-project RBS source discovery (slices 1+2+3)** — `Gemfile.lock` parse + bundle-sig filter, `rbs_collection.lock.yaml` awareness, missing-gem `:info` diagnostic.
|
|
577
|
-
- **DEFAULT_LIBRARIES stdlib coverage expansion** — out-of-the-box RBS classes available 1,273 → 1,427 (+154); 31 additional stdlib libraries auto-load.
|
|
578
|
-
- **`is_a?(C)` lexical-nesting constant resolution** — predicate-narrowing now mirrors Ruby's `Module.nesting`-driven lookup.
|
|
579
|
-
|
|
580
|
-
Production plugins ship under [`plugins/`](plugins/) (Rails /
|
|
581
|
-
RSpec / dry-rb / Sorbet / etc.) — see
|
|
582
|
-
[`plugins/README.md`](plugins/README.md) for the catalogue.
|
|
583
|
-
Plugin-contract walkthroughs ship under
|
|
584
|
-
[`examples/`](examples/) — see
|
|
585
|
-
[`examples/README.md`](examples/README.md).
|
|
252
|
+
Current released version: **`v0.1.8`** (2026-05-21). The analyzer is
|
|
253
|
+
usable on real Ruby code today; the rule catalogue is deliberately
|
|
254
|
+
conservative — Rigor's stance is to surface zero false positives while
|
|
255
|
+
the inference surface stabilises.
|
|
256
|
+
|
|
257
|
+
Release history: [`CHANGELOG.md`](CHANGELOG.md). Forward-looking
|
|
258
|
+
commitments: [Roadmap](https://rigor.typedduck.fail/reference/roadmap/).
|
|
586
259
|
|
|
587
260
|
## Contributing
|
|
588
261
|
|
|
589
|
-
See [`CONTRIBUTING.md`](CONTRIBUTING.md) for the
|
|
590
|
-
|
|
591
|
-
|
|
262
|
+
See [`CONTRIBUTING.md`](CONTRIBUTING.md) for the `git clone` →
|
|
263
|
+
green-tests path and a map of the spec / ADR / skill documentation
|
|
264
|
+
contributors should read.
|
|
592
265
|
|
|
593
266
|
## License
|
|
594
267
|
|