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.
- checksums.yaml +4 -4
- data/README.md +82 -20
- data/data/core_overlay/numeric.rbs +33 -0
- data/data/core_overlay/pathname.rbs +25 -0
- data/data/core_overlay/string_scanner.rbs +28 -0
- data/data/gem_overlay/activesupport/core_ext.rbs +473 -0
- data/data/vendored_gem_sigs/ast/ast.rbs +130 -0
- data/data/vendored_gem_sigs/bcrypt/bcrypt.rbs +47 -0
- data/data/vendored_gem_sigs/bundler/bundler.rbs +238 -0
- data/data/vendored_gem_sigs/cgi/cgi_extras.rbs +34 -0
- data/data/vendored_gem_sigs/did_you_mean/did_you_mean_extras.rbs +34 -0
- data/data/vendored_gem_sigs/idn-ruby/idn.rbs +54 -0
- data/data/vendored_gem_sigs/mysql2/client.rbs +55 -0
- data/data/vendored_gem_sigs/mysql2/error.rbs +5 -0
- data/data/vendored_gem_sigs/mysql2/result.rbs +31 -0
- data/data/vendored_gem_sigs/mysql2/statement.rbs +5 -0
- data/data/vendored_gem_sigs/nokogiri/nokogiri.rbs +2332 -0
- data/data/vendored_gem_sigs/nokogiri/nokogiri_html5.rbs +47 -0
- data/data/vendored_gem_sigs/pg/pg.rbs +212 -0
- data/data/vendored_gem_sigs/prism/prism_supplement.rbs +44 -0
- data/data/vendored_gem_sigs/redis/errors.rbs +50 -0
- data/data/vendored_gem_sigs/redis/future.rbs +5 -0
- data/data/vendored_gem_sigs/redis/redis.rbs +348 -0
- data/data/vendored_gem_sigs/redis/redis_extras.rbs +130 -0
- data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +226 -0
- data/docs/handbook/01-getting-started.md +311 -0
- data/docs/handbook/02-everyday-types.md +337 -0
- data/docs/handbook/03-narrowing.md +359 -0
- data/docs/handbook/04-tuples-and-shapes.md +321 -0
- data/docs/handbook/05-methods-and-blocks.md +339 -0
- data/docs/handbook/06-classes.md +305 -0
- data/docs/handbook/07-rbs-and-extended.md +427 -0
- data/docs/handbook/08-understanding-errors.md +373 -0
- data/docs/handbook/09-plugins.md +241 -0
- data/docs/handbook/10-sorbet.md +347 -0
- data/docs/handbook/11-sig-gen.md +312 -0
- data/docs/handbook/12-lightweight-hkt.md +333 -0
- data/docs/handbook/README.md +275 -0
- data/docs/handbook/appendix-elixir.md +370 -0
- data/docs/handbook/appendix-go.md +399 -0
- data/docs/handbook/appendix-java-csharp.md +470 -0
- data/docs/handbook/appendix-liskov.md +580 -0
- data/docs/handbook/appendix-mypy.md +370 -0
- data/docs/handbook/appendix-phpstan.md +338 -0
- data/docs/handbook/appendix-protocols-and-structural-typing.md +292 -0
- data/docs/handbook/appendix-rust.md +446 -0
- data/docs/handbook/appendix-steep.md +336 -0
- data/docs/handbook/appendix-type-theory.md +1662 -0
- data/docs/handbook/appendix-typeprof.md +416 -0
- data/docs/handbook/appendix-typescript.md +332 -0
- data/docs/install.md +189 -0
- data/docs/llms.txt +72 -0
- data/docs/manual/01-installation.md +342 -0
- data/docs/manual/02-cli-reference.md +557 -0
- data/docs/manual/03-configuration.md +152 -0
- data/docs/manual/04-diagnostics.md +206 -0
- data/docs/manual/05-inspecting-types.md +109 -0
- data/docs/manual/06-baseline.md +104 -0
- data/docs/manual/07-plugins.md +92 -0
- data/docs/manual/08-skills.md +143 -0
- data/docs/manual/09-editor-integration.md +245 -0
- data/docs/manual/10-mcp-server.md +532 -0
- data/docs/manual/11-ci.md +274 -0
- data/docs/manual/12-caching.md +116 -0
- data/docs/manual/13-troubleshooting.md +120 -0
- data/docs/manual/14-rails-quickstart.md +332 -0
- data/docs/manual/15-type-protection-coverage.md +204 -0
- data/docs/manual/16-rbs-extended-annotations.md +190 -0
- data/docs/manual/17-driving-improvement.md +160 -0
- data/docs/manual/README.md +87 -0
- data/docs/manual/ci-templates/README.md +58 -0
- data/docs/manual/plugins/README.md +86 -0
- data/docs/manual/plugins/rigor-actioncable.md +78 -0
- data/docs/manual/plugins/rigor-actionmailer.md +74 -0
- data/docs/manual/plugins/rigor-actionpack.md +80 -0
- data/docs/manual/plugins/rigor-activejob.md +58 -0
- data/docs/manual/plugins/rigor-activerecord.md +102 -0
- data/docs/manual/plugins/rigor-activestorage.md +74 -0
- data/docs/manual/plugins/rigor-activesupport-core-ext.md +86 -0
- data/docs/manual/plugins/rigor-devise.md +70 -0
- data/docs/manual/plugins/rigor-dry-schema.md +56 -0
- data/docs/manual/plugins/rigor-dry-struct.md +60 -0
- data/docs/manual/plugins/rigor-dry-types.md +59 -0
- data/docs/manual/plugins/rigor-dry-validation.md +62 -0
- data/docs/manual/plugins/rigor-factorybot.md +76 -0
- data/docs/manual/plugins/rigor-graphql.md +89 -0
- data/docs/manual/plugins/rigor-hanami.md +83 -0
- data/docs/manual/plugins/rigor-mangrove.md +73 -0
- data/docs/manual/plugins/rigor-minitest.md +86 -0
- data/docs/manual/plugins/rigor-pundit.md +72 -0
- data/docs/manual/plugins/rigor-rails-i18n.md +92 -0
- data/docs/manual/plugins/rigor-rails-routes.md +94 -0
- data/docs/manual/plugins/rigor-rails.md +44 -0
- data/docs/manual/plugins/rigor-rbs-inline.md +83 -0
- data/docs/manual/plugins/rigor-rspec-rails.md +72 -0
- data/docs/manual/plugins/rigor-rspec.md +86 -0
- data/docs/manual/plugins/rigor-shoulda-matchers.md +78 -0
- data/docs/manual/plugins/rigor-sidekiq.md +78 -0
- data/docs/manual/plugins/rigor-sinatra.md +61 -0
- data/docs/manual/plugins/rigor-sorbet.md +63 -0
- data/docs/manual/plugins/rigor-statesman.md +75 -0
- data/docs/manual/plugins/rigor-typescript-utility-types.md +71 -0
- data/exe/rigor +1 -1
- data/lib/rigor/analysis/incremental_session.rb +4 -2
- data/lib/rigor/analysis/run_stats.rb +13 -1
- data/lib/rigor/analysis/runner.rb +54 -12
- data/lib/rigor/cli/check_command.rb +26 -3
- data/lib/rigor/cli/coverage_command.rb +67 -92
- data/lib/rigor/cli/coverage_mutation.rb +149 -0
- data/lib/rigor/cli/docs_command.rb +248 -0
- data/lib/rigor/cli/fused_protection_renderer.rb +67 -0
- data/lib/rigor/cli/fused_protection_report.rb +76 -0
- data/lib/rigor/cli/skill_command.rb +103 -41
- data/lib/rigor/cli/skill_describe.rb +346 -0
- data/lib/rigor/cli.rb +25 -3
- data/lib/rigor/config_audit.rb +152 -0
- data/lib/rigor/configuration.rb +12 -0
- data/lib/rigor/environment/rbs_loader.rb +27 -0
- data/lib/rigor/environment.rb +49 -1
- data/lib/rigor/inference/method_dispatcher/constant_folding.rb +140 -38
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +37 -6
- data/lib/rigor/inference/scope_indexer.rb +87 -89
- data/lib/rigor/inference/statement_evaluator.rb +27 -0
- data/lib/rigor/plugin/isolation.rb +5 -5
- data/lib/rigor/plugin/loader.rb +4 -2
- data/lib/rigor/protection/diagnostic_oracle.rb +51 -0
- data/lib/rigor/protection/mutation_scanner.rb +98 -38
- data/lib/rigor/protection/mutator.rb +21 -0
- data/lib/rigor/protection/test_suite_oracle.rb +68 -0
- data/lib/rigor/signature_path_audit.rb +92 -0
- data/lib/rigor/version.rb +1 -1
- data/skills/rigor-ask/SKILL.md +172 -0
- data/skills/rigor-doctor/SKILL.md +87 -0
- data/skills/rigor-editor-setup/SKILL.md +114 -0
- data/skills/rigor-mcp-setup/SKILL.md +117 -0
- data/skills/rigor-monkeypatch-resolve/SKILL.md +79 -0
- data/skills/rigor-next-steps/SKILL.md +113 -0
- data/skills/rigor-plugin-tune/SKILL.md +79 -0
- data/skills/rigor-protection-uplift/SKILL.md +133 -0
- data/skills/rigor-rbs-setup/SKILL.md +128 -0
- data/skills/rigor-upgrade/SKILL.md +79 -0
- metadata +120 -1
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
# Rigor for Rails — step-by-step setup with mise
|
|
2
|
+
|
|
3
|
+
This walkthrough takes a Rails project from zero to a first
|
|
4
|
+
`rigor check` run. It uses [`mise`](https://mise.jdx.dev/) to
|
|
5
|
+
install Rigor alongside Ruby 4.0, keeping the analyser out of
|
|
6
|
+
your project's `Gemfile`.
|
|
7
|
+
|
|
8
|
+
There are two ways through this setup:
|
|
9
|
+
|
|
10
|
+
| | Approach | Best for |
|
|
11
|
+
| --- | --- | --- |
|
|
12
|
+
| **A** | [The `rigor-project-init` skill](#path-a--the-rigor-project-init-skill-recommended) | Most projects — the skill detects your stack, proposes plugins, and writes the config for you. |
|
|
13
|
+
| **B** | [Manual step-by-step](#path-b--manual-step-by-step) | When you want explicit control over each decision. |
|
|
14
|
+
|
|
15
|
+
Both produce the same result. **If you are unsure, follow Path A.**
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Before you start
|
|
20
|
+
|
|
21
|
+
You need:
|
|
22
|
+
|
|
23
|
+
- **`mise` installed** — if not, follow
|
|
24
|
+
[mise's getting-started guide](https://mise.jdx.dev/getting-started.html).
|
|
25
|
+
If you prefer `asdf` or a plain `gem install`, see
|
|
26
|
+
[Installing Rigor](01-installation.md).
|
|
27
|
+
- **`mise` wired into your shell** — add
|
|
28
|
+
`eval "$(mise activate zsh)"` (or the equivalent for your
|
|
29
|
+
shell) to your shell rc so that `rigor` reaches your `PATH`.
|
|
30
|
+
See [Installing Rigor § Putting rigor on your PATH](01-installation.md)
|
|
31
|
+
for detail.
|
|
32
|
+
- **An existing Rails project** at a known path.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Step 1 — Install Ruby 4.0 and Rigor (common to both paths)
|
|
37
|
+
|
|
38
|
+
> **Using an AI agent?** Point it at the machine-readable install
|
|
39
|
+
> guide instead:
|
|
40
|
+
>
|
|
41
|
+
> ```
|
|
42
|
+
> Install Rigor in this project by following the instructions at
|
|
43
|
+
> https://raw.githubusercontent.com/rigortype/rigor/refs/heads/master/docs/install.md
|
|
44
|
+
> ```
|
|
45
|
+
>
|
|
46
|
+
> The agent will detect your environment (mise / asdf / plain Ruby),
|
|
47
|
+
> install the right tools, and hand off to `rigor-project-init`
|
|
48
|
+
> automatically.
|
|
49
|
+
|
|
50
|
+
**Set up in your language** — the prompt is plain natural
|
|
51
|
+
language, so you can write it (and run the whole setup
|
|
52
|
+
conversation) in your mother tongue. Ready-made prompts in 16
|
|
53
|
+
languages are in
|
|
54
|
+
[Installing Rigor § Set up in your language](01-installation.md#set-up-in-your-language).
|
|
55
|
+
|
|
56
|
+
Open a terminal **in your project root** and run:
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
mise use ruby@4.0
|
|
60
|
+
mise use gem:rigortype
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
`mise use` records the chosen versions in a `mise.toml` in the
|
|
64
|
+
current directory and installs them in one step. Verify:
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
rigor --version
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
All plugins ship **bundled inside `rigortype`** — no additional
|
|
71
|
+
gems to install. Plugins are inactive by default; you enable the
|
|
72
|
+
ones you need in `.rigor.dist.yml`. That is the only step that
|
|
73
|
+
differs between projects.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Path A — the rigor-project-init skill (recommended)
|
|
78
|
+
|
|
79
|
+
The `rigor-project-init` skill automates the rest of the setup.
|
|
80
|
+
It works inside any AI coding agent that can read a file and run
|
|
81
|
+
a shell command — there is no Claude-specific machinery involved.
|
|
82
|
+
|
|
83
|
+
### What the skill does
|
|
84
|
+
|
|
85
|
+
It runs eight phases in order:
|
|
86
|
+
|
|
87
|
+
1. **Detect** — reads your `Gemfile` / `Gemfile.lock` to
|
|
88
|
+
identify the framework family (Rails, dry-rb, Sinatra, …)
|
|
89
|
+
and which gems are present.
|
|
90
|
+
2. **Choose an adoption mode** — proposes either *acknowledge*
|
|
91
|
+
(snapshot today's diagnostics into a baseline; catch
|
|
92
|
+
regressions going forward) or *strict* (drive the project to
|
|
93
|
+
zero and keep it there). It recommends acknowledge for
|
|
94
|
+
codebases with more than ~100 initial diagnostics.
|
|
95
|
+
3. **Select plugins** — proposes the plugin set matching your
|
|
96
|
+
detected stack; you confirm or trim the list.
|
|
97
|
+
4. **Write `.rigor.dist.yml`** — the committed shared config,
|
|
98
|
+
with `severity_profile:` tied to the chosen mode.
|
|
99
|
+
5. **Sig uplift** — runs `rigor sig-gen --write` to generate a
|
|
100
|
+
baseline `sig/` from Rigor's own inference.
|
|
101
|
+
6. **Triage** — runs `rigor triage --format json` to diagnose
|
|
102
|
+
the diagnostic stream by cluster.
|
|
103
|
+
7. **Baseline** *(acknowledge mode only)* — generates
|
|
104
|
+
`.rigor-baseline.yml` and wires `baseline:` in the config.
|
|
105
|
+
8. **Surface real bugs** — highlights the clusters most likely
|
|
106
|
+
to be genuine bugs; offers escalation paths for
|
|
107
|
+
application-specific metaprogramming and gaps in Rigor's
|
|
108
|
+
built-in coverage.
|
|
109
|
+
|
|
110
|
+
### How to invoke it
|
|
111
|
+
|
|
112
|
+
Say one of the following to your AI coding agent:
|
|
113
|
+
|
|
114
|
+
> "Set up Rigor in this project."
|
|
115
|
+
> "Configure Rigor for this Rails app."
|
|
116
|
+
> "Add type checking."
|
|
117
|
+
|
|
118
|
+
The agent should respond by running:
|
|
119
|
+
|
|
120
|
+
```sh
|
|
121
|
+
rigor skill rigor-project-init
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
That prints the SKILL definition to stdout — a short header
|
|
125
|
+
(with the absolute paths of the SKILL file and its `references/`
|
|
126
|
+
directory) followed by the SKILL body. The agent then follows
|
|
127
|
+
those instructions, reading the `references/NN-*.md` files in
|
|
128
|
+
turn from the directory the header points at.
|
|
129
|
+
|
|
130
|
+
If the agent does not pick the command up on its own, ask
|
|
131
|
+
explicitly: **"Run `rigor skill rigor-project-init` and
|
|
132
|
+
follow the instructions it prints."**
|
|
133
|
+
|
|
134
|
+
The same flow works against any bundled skill:
|
|
135
|
+
|
|
136
|
+
- `rigor skill --list` — list every bundled skill with its path.
|
|
137
|
+
- `rigor skill <name>` — print the SKILL body.
|
|
138
|
+
- `rigor skill --path <name>` — print just the absolute SKILL.md
|
|
139
|
+
path (handy if your agent prefers to read the file directly).
|
|
140
|
+
|
|
141
|
+
The skill lives inside the installed `rigortype` gem at
|
|
142
|
+
the path printed by `rigor skill --path rigor-project-init`. The
|
|
143
|
+
source-of-truth copy is
|
|
144
|
+
[`skills/rigor-project-init/SKILL.md`](../../skills/rigor-project-init/SKILL.md).
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Path B — manual step-by-step
|
|
149
|
+
|
|
150
|
+
### Step 2 — Choose an adoption mode
|
|
151
|
+
|
|
152
|
+
| Mode | When | What happens |
|
|
153
|
+
| --- | --- | --- |
|
|
154
|
+
| **Acknowledge** | Existing codebase with many diagnostics | Record today's diagnostics in a baseline; surface only new ones on each PR. |
|
|
155
|
+
| **Strict** | New or small project | Zero outstanding diagnostics; no baseline. |
|
|
156
|
+
|
|
157
|
+
If your first `rigor check` reports more than ~100 diagnostics,
|
|
158
|
+
acknowledge mode is the natural starting point. You can tighten
|
|
159
|
+
it later.
|
|
160
|
+
|
|
161
|
+
### Step 3 — Write .rigor.dist.yml
|
|
162
|
+
|
|
163
|
+
The convention is to commit `.rigor.dist.yml` as the shared
|
|
164
|
+
project config and leave `.rigor.yml` for per-developer local
|
|
165
|
+
overrides (gitignored). When both files exist, `.rigor.yml`
|
|
166
|
+
takes precedence.
|
|
167
|
+
|
|
168
|
+
Create `.rigor.dist.yml` at your project root:
|
|
169
|
+
|
|
170
|
+
```yaml
|
|
171
|
+
# .rigor.dist.yml — Rigor configuration (committed; shared).
|
|
172
|
+
|
|
173
|
+
target_ruby: "3.3" # the Ruby version your Rails app targets
|
|
174
|
+
|
|
175
|
+
paths:
|
|
176
|
+
- app
|
|
177
|
+
- lib
|
|
178
|
+
|
|
179
|
+
exclude:
|
|
180
|
+
- vendor
|
|
181
|
+
- tmp
|
|
182
|
+
|
|
183
|
+
plugins:
|
|
184
|
+
# Rails core
|
|
185
|
+
- rigor-activerecord
|
|
186
|
+
- rigor-actionpack
|
|
187
|
+
- rigor-rails-routes
|
|
188
|
+
- rigor-rails-i18n
|
|
189
|
+
- rigor-actionmailer
|
|
190
|
+
- rigor-activejob
|
|
191
|
+
# Optional — the ActiveSupport core_ext RBS is now auto-applied when
|
|
192
|
+
# activesupport is in your Gemfile.lock (ADR-72); add this plugin only
|
|
193
|
+
# for the fuller, opt-in surface (it stands in for the auto overlay).
|
|
194
|
+
- rigor-activesupport-core-ext
|
|
195
|
+
# Testing — keep the ones that match your project
|
|
196
|
+
- rigor-rspec
|
|
197
|
+
- rigor-factorybot
|
|
198
|
+
|
|
199
|
+
severity_profile: lenient # "strict" for strict mode; omit for "balanced"
|
|
200
|
+
|
|
201
|
+
# baseline: .rigor-baseline.yml # uncomment after Step 6 (acknowledge mode only)
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Adjust `target_ruby:` to match your project's Ruby version (the
|
|
205
|
+
value in your `Gemfile` or `.ruby-version`) and trim the
|
|
206
|
+
`plugins:` list to what you actually use.
|
|
207
|
+
|
|
208
|
+
> **ActiveSupport core_ext is covered automatically.** When
|
|
209
|
+
> `activesupport` is in your `Gemfile.lock` but ships no RBS,
|
|
210
|
+
> Rigor auto-loads a bundled core-ext RBS overlay
|
|
211
|
+
> ([ADR-72](../adr/72-gemfile-lock-gated-rbs-overlays.md)), so
|
|
212
|
+
> extension calls (`3.days`, `"x".squish`, `Time.current`, …)
|
|
213
|
+
> resolve without any plugin or config — on a real Rails app this
|
|
214
|
+
> is reliably the single largest false-positive cluster (a
|
|
215
|
+
> Mastodon measurement found ~365 of 489 `call.undefined-method`
|
|
216
|
+
> diagnostics were exactly this source). The
|
|
217
|
+
> `rigor-activesupport-core-ext` plugin above is now optional: add
|
|
218
|
+
> it for the fuller, opt-in surface, and the auto overlay stands
|
|
219
|
+
> down for it. A plain-Ruby project without `activesupport` still
|
|
220
|
+
> gets the genuine diagnostic.
|
|
221
|
+
|
|
222
|
+
Other plugins to consider depending on your stack:
|
|
223
|
+
|
|
224
|
+
| Plugin | When |
|
|
225
|
+
| --- | --- |
|
|
226
|
+
| `rigor-activestorage` | `has_one_attached` / `has_many_attached` |
|
|
227
|
+
| `rigor-actioncable` | ActionCable channels |
|
|
228
|
+
| `rigor-devise` | Devise authentication |
|
|
229
|
+
| `rigor-pundit` | Pundit policies |
|
|
230
|
+
| `rigor-sidekiq` | Sidekiq workers |
|
|
231
|
+
| `rigor-rspec-rails` | RSpec HTTP status matchers |
|
|
232
|
+
| `rigor-shoulda-matchers` | shoulda-matchers |
|
|
233
|
+
| `rigor-minitest` | Minitest / Test::Unit |
|
|
234
|
+
|
|
235
|
+
See [`plugins/README.md`](../../plugins/README.md) for the full
|
|
236
|
+
catalogue.
|
|
237
|
+
|
|
238
|
+
Add the cache directory to `.gitignore`:
|
|
239
|
+
|
|
240
|
+
```
|
|
241
|
+
.rigor/
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Step 4 — First run
|
|
245
|
+
|
|
246
|
+
```sh
|
|
247
|
+
rigor check
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
A large initial count is normal for a project that has never
|
|
251
|
+
been type-checked.
|
|
252
|
+
|
|
253
|
+
### Step 5 — Understand the output
|
|
254
|
+
|
|
255
|
+
`rigor triage` summarises the diagnostic stream instead of
|
|
256
|
+
listing every occurrence:
|
|
257
|
+
|
|
258
|
+
```sh
|
|
259
|
+
rigor triage
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
It groups results by rule ID, shows per-file hotspots, and
|
|
263
|
+
prints a brief "why" hint for common clusters — for example,
|
|
264
|
+
flagging that a large block of `call.undefined-method` errors
|
|
265
|
+
likely comes from a missing ActiveSupport core_ext bundle, or
|
|
266
|
+
that a gem ships no RBS and `rbs collection install` would help.
|
|
267
|
+
|
|
268
|
+
Use the triage output to decide where to start: genuine bugs
|
|
269
|
+
first, then large clusters to record in a baseline.
|
|
270
|
+
|
|
271
|
+
> **Rails route diagnostics.** `rigor-rails-routes` checks route
|
|
272
|
+
> helpers statically. Most standard Rails patterns are supported,
|
|
273
|
+
> but a few produce false-positive `unknown-helper` diagnostics in
|
|
274
|
+
> v0.1.x:
|
|
275
|
+
>
|
|
276
|
+
> - Routes defined only inside `concern :name do ... end` blocks.
|
|
277
|
+
> The concern body is skipped at definition time (to avoid
|
|
278
|
+
> wrong-arity false positives); helpers injected via
|
|
279
|
+
> `concerns: :name` appear as unknown.
|
|
280
|
+
> - Routes generated by `devise_for :users` and other engine
|
|
281
|
+
> macros — the parser does not execute Ruby code.
|
|
282
|
+
>
|
|
283
|
+
> If you see a cluster of `unknown-helper` on routes you know exist,
|
|
284
|
+
> acknowledge mode is the right approach — record them in a baseline
|
|
285
|
+
> and let the remaining diagnostics surface real issues.
|
|
286
|
+
|
|
287
|
+
### Step 6 — Generate a baseline (acknowledge mode)
|
|
288
|
+
|
|
289
|
+
*Skip this step if you chose strict mode.*
|
|
290
|
+
|
|
291
|
+
```sh
|
|
292
|
+
rigor baseline generate
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
This writes `.rigor-baseline.yml` at the project root. Activate
|
|
296
|
+
it by uncommenting the `baseline:` line in `.rigor.dist.yml`:
|
|
297
|
+
|
|
298
|
+
```yaml
|
|
299
|
+
baseline: .rigor-baseline.yml
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
With a baseline active, `rigor check` exits clean on the current
|
|
303
|
+
codebase and surfaces only diagnostics that appear *after* the
|
|
304
|
+
baseline was captured. See [Baselines](06-baseline.md) for the
|
|
305
|
+
full baseline workflow.
|
|
306
|
+
|
|
307
|
+
### Step 7 — Commit
|
|
308
|
+
|
|
309
|
+
```sh
|
|
310
|
+
git add mise.toml .rigor.dist.yml .gitignore
|
|
311
|
+
git add .rigor-baseline.yml # if generated in Step 6
|
|
312
|
+
git commit -m "Add Rigor type checker"
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
`mise.toml` pins Ruby 4.0 and Rigor's version for every
|
|
316
|
+
contributor — `mise install` on another machine restores the
|
|
317
|
+
exact same tools without any project `Gemfile` changes.
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
## What's next
|
|
322
|
+
|
|
323
|
+
- **CI** — add a standalone Rigor job so pull requests are
|
|
324
|
+
gated automatically: [Running Rigor in CI](11-ci.md).
|
|
325
|
+
- **Editor** — inline diagnostics as you type:
|
|
326
|
+
[Editor integration](09-editor-integration.md).
|
|
327
|
+
- **Reducing the baseline** — work through the backlog rule by
|
|
328
|
+
rule using the `rigor-baseline-reduce` skill:
|
|
329
|
+
[Baselines](06-baseline.md).
|
|
330
|
+
- **Plugins** — each plugin's documentation describes its config
|
|
331
|
+
options in detail: [Using plugins](07-plugins.md) and
|
|
332
|
+
[`plugins/`](../../plugins/README.md).
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
# Type-protection coverage
|
|
2
|
+
|
|
3
|
+
Most quality metrics tell you how *much* of something you have:
|
|
4
|
+
lines covered, expressions typed. They rarely answer the question
|
|
5
|
+
that matters most: **if I introduce a bug, would anything catch
|
|
6
|
+
it?**
|
|
7
|
+
|
|
8
|
+
Rigor's type-protection coverage answers that by measuring your
|
|
9
|
+
**types and your tests as one safety net**. A call site is safe
|
|
10
|
+
when *either* the type checker would reject a wrong call *or* a
|
|
11
|
+
test would go red, and it is unguarded only when **neither**
|
|
12
|
+
would. That union is the picture this command draws, with the
|
|
13
|
+
cheaper missing half named at every gap.
|
|
14
|
+
|
|
15
|
+
## Precision is not protection
|
|
16
|
+
|
|
17
|
+
`rigor coverage` on its own reports **type precision** — the
|
|
18
|
+
fraction of expressions Rigor gives a precise (non-`Dynamic`)
|
|
19
|
+
type:
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
rigor coverage [paths]
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
That tells you how much Rigor is inferring, which is useful, but
|
|
26
|
+
precision is not protection. A precisely-typed expression you
|
|
27
|
+
never call the wrong way buys you nothing; an untyped one a test
|
|
28
|
+
hammers is safe. To measure *protection* (would a bug be caught)
|
|
29
|
+
add `--protection`. It comes in three tiers, cheapest first.
|
|
30
|
+
|
|
31
|
+
## Tier 1 — could a bug be caught here? (static, instant)
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
rigor coverage --protection [paths]
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Tier 1 classifies every **dispatch site** (a method call with a
|
|
38
|
+
receiver) by whether the receiver has a concrete type — a site
|
|
39
|
+
where Rigor's rules *can* catch a wrong method or argument. It is
|
|
40
|
+
one analysis pass, fast enough to run interactively and in CI, and
|
|
41
|
+
a **sound upper bound**: a concrete receiver is necessary for a
|
|
42
|
+
diagnostic to fire, but not sufficient.
|
|
43
|
+
|
|
44
|
+
The report leads with the protected ratio, then a ranked **"add a
|
|
45
|
+
type here"** list — the methods most often called on a `Dynamic`
|
|
46
|
+
receiver, where a type annotation would buy the most catching
|
|
47
|
+
power. `--threshold=RATIO` turns it into a CI gate (exit `1`
|
|
48
|
+
below the ratio) and `--format=json` carries the structured
|
|
49
|
+
fields.
|
|
50
|
+
|
|
51
|
+
This is the everyday number. When you want the truth behind it,
|
|
52
|
+
move to Tier 2.
|
|
53
|
+
|
|
54
|
+
## Tier 2 — would a bug *actually* be caught? (mutation)
|
|
55
|
+
|
|
56
|
+
```sh
|
|
57
|
+
rigor coverage --protection --mutation [paths]
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Tier 1 says a site *can* be protected; Tier 2 proves whether it
|
|
61
|
+
*is*. It introduces type-visible breakages at each site — dropping
|
|
62
|
+
a call-argument to `nil`, swapping its type, renaming a call to a
|
|
63
|
+
missing method — re-analyses the mutated code, and reports how many
|
|
64
|
+
Rigor **catches** (the kill rate). A breakage Rigor misses is a
|
|
65
|
+
real "add a type here" site, surfaced with no guesswork.
|
|
66
|
+
|
|
67
|
+
It runs many analyses, so it defaults to the **git-changed** `.rb`
|
|
68
|
+
files (pass explicit paths to widen — whole-project is minutes) and
|
|
69
|
+
is an opt-in CI deep-dive, not an interactive check. The framing is
|
|
70
|
+
always *effectiveness / where to add a type*, never "your code is
|
|
71
|
+
broken": a surviving breakage at a `Dynamic` site is a place
|
|
72
|
+
the type net does not reach.
|
|
73
|
+
|
|
74
|
+
What *does* reach it is your tests.
|
|
75
|
+
|
|
76
|
+
## The fused view — types **and** tests (`--with-tests`)
|
|
77
|
+
|
|
78
|
+
```sh
|
|
79
|
+
rigor coverage --protection --mutation --with-tests \
|
|
80
|
+
--test-command "bundle exec rspec" [paths]
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
This is the heart of the feature. For every breakage the type
|
|
84
|
+
checker does **not** catch, Rigor runs your test suite and asks
|
|
85
|
+
whether a **test** catches it. Each dispatch site lands in one of
|
|
86
|
+
three buckets:
|
|
87
|
+
|
|
88
|
+
| Classification | Meaning |
|
|
89
|
+
| --- | --- |
|
|
90
|
+
| **type-protected** | the type checker would reject the bug |
|
|
91
|
+
| **test-protected** | the types miss it, but a test goes red |
|
|
92
|
+
| **unprotected** | neither — a real, unguarded dispatch site |
|
|
93
|
+
|
|
94
|
+
The report names the **cheaper missing axis** at every gap: a
|
|
95
|
+
`Dynamic`-receiver hole says *add a type*; a typed-but-untested
|
|
96
|
+
hole says *add a test*. A site is reported unprotected only when
|
|
97
|
+
**both** halves miss, which is where the real risk lives, and what
|
|
98
|
+
no types-only or tests-only tool can show you.
|
|
99
|
+
|
|
100
|
+
Cost stays proportional to the hole. A breakage the type checker
|
|
101
|
+
already kills **never reaches the suite** (a gradual short-circuit),
|
|
102
|
+
so the expensive test runs are spent only where the static net has
|
|
103
|
+
a gap. The honest headline becomes *"of the bugs my types let
|
|
104
|
+
through, how many do my tests catch?"*
|
|
105
|
+
|
|
106
|
+
`--format=json` carries `mode` (`protection-fused`), `type_killed`,
|
|
107
|
+
`test_killed`, `unprotected`, `protected_ratio`, per-file rows, and
|
|
108
|
+
`add_protection_here`; `--threshold` gates on the fused ratio.
|
|
109
|
+
|
|
110
|
+
### The test-command hook
|
|
111
|
+
|
|
112
|
+
`--test-command=CMD` is how Rigor runs your suite (default
|
|
113
|
+
`bundle exec rake`). Two things to know:
|
|
114
|
+
|
|
115
|
+
- **The suite must pass on clean code first**, or "a breakage
|
|
116
|
+
survived" would be meaningless — the run aborts with a clear
|
|
117
|
+
message. Point the command at a plain pass/fail runner; a
|
|
118
|
+
coverage floor that exits non-zero on an otherwise-green
|
|
119
|
+
single-file run will trip this.
|
|
120
|
+
- **The command runs without a shell** (it is split into an argv
|
|
121
|
+
and executed directly), and Rigor strips its own Bundler
|
|
122
|
+
environment so a `bundle exec` command resolves *your* project's
|
|
123
|
+
bundle. So no `env` wrapper is needed — but shell constructs,
|
|
124
|
+
including an inline `BUNDLE_GEMFILE=… ` prefix, are not
|
|
125
|
+
interpreted. For a non-default Gemfile, use
|
|
126
|
+
`bundle config set --local gemfile PATH` or wrap the command in
|
|
127
|
+
`bash -c '…'`.
|
|
128
|
+
|
|
129
|
+
## `--include-dynamic` — covering your untyped code
|
|
130
|
+
|
|
131
|
+
By default the fused view only mutates sites Rigor can type-check.
|
|
132
|
+
But on dynamic Ruby that is the minority — the receiver of most
|
|
133
|
+
calls is `Dynamic`, and there a **test is the only possible
|
|
134
|
+
protection**. `--include-dynamic` mutates those sites too:
|
|
135
|
+
|
|
136
|
+
```sh
|
|
137
|
+
rigor coverage --protection --mutation --with-tests --include-dynamic \
|
|
138
|
+
--test-command "bundle exec rspec" --limit 40 [paths]
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
This completes the map to *every* dispatch site, not just the
|
|
142
|
+
typed ones, and is where the fused view earns its keep — it shows
|
|
143
|
+
which of your untyped code is held up by tests and which is held up
|
|
144
|
+
by nothing. Because every such site is, by definition, something
|
|
145
|
+
the type checker cannot catch, it runs the suite far more, so it is
|
|
146
|
+
an explicit opt-in.
|
|
147
|
+
|
|
148
|
+
`--limit=N` (with `--seed=N`) caps the measurement to a
|
|
149
|
+
deterministic sample of `N` mutations per file, bounding the cost
|
|
150
|
+
on large files; per-file ratios then become estimates, noted on
|
|
151
|
+
stderr so `--format=json` stdout stays clean.
|
|
152
|
+
|
|
153
|
+
## Reading the report — the "add a type **or** a test here" list
|
|
154
|
+
|
|
155
|
+
The unprotected sites are the payload. In practice they fall into
|
|
156
|
+
a few recognisable kinds, each with a natural fix:
|
|
157
|
+
|
|
158
|
+
- **An untested method body** — a helper the suite never exercises.
|
|
159
|
+
*Add a test*, or widen the test command to cover it.
|
|
160
|
+
- **An unreached branch** — an error path (`raise` in a `rescue`),
|
|
161
|
+
a version-dispatch arm. *Add a test* for that branch, if it
|
|
162
|
+
matters.
|
|
163
|
+
- **A `Dynamic`-receiver collaborator** — a call on an external
|
|
164
|
+
gem object, a framework facade, a duck-typed parameter, or a
|
|
165
|
+
metaprogramming DSL. *Add a type* — often a one-liner from
|
|
166
|
+
[`rigor sig-gen`](02-cli-reference.md#rigor-sig-gen) — so the
|
|
167
|
+
static net starts catching it.
|
|
168
|
+
|
|
169
|
+
A note on completeness: when `--test-command` is scoped to a
|
|
170
|
+
*subset* of your tests, the report over-reports `unprotected` (a
|
|
171
|
+
breakage a *different* test would catch shows as a gap). For an
|
|
172
|
+
accurate map, run the command over all tests that exercise the
|
|
173
|
+
files you are measuring — trading time for completeness.
|
|
174
|
+
|
|
175
|
+
## Cost and scope
|
|
176
|
+
|
|
177
|
+
The fused tiers run real analyses and real test suites, so treat
|
|
178
|
+
them as a deep-dive, not a per-keystroke check:
|
|
179
|
+
|
|
180
|
+
- **Scope tight.** The changed-files default (no paths given) keeps
|
|
181
|
+
a run proportional to a diff; pass explicit paths to widen
|
|
182
|
+
deliberately.
|
|
183
|
+
- **Keep the suite fast.** Cost is `(breakages measured) ×
|
|
184
|
+
(suite runtime)`. A fast, well-scoped test command is the biggest
|
|
185
|
+
lever.
|
|
186
|
+
- **Cap with `--limit`** on `--include-dynamic` or large files.
|
|
187
|
+
|
|
188
|
+
## In CI
|
|
189
|
+
|
|
190
|
+
Tier 1 (`--protection`) is cheap enough to gate every run; the
|
|
191
|
+
mutation and fused tiers are better as a scheduled or
|
|
192
|
+
label-triggered deep-dive. All tiers honour `--threshold` (a fused-
|
|
193
|
+
or effectiveness-ratio gate) and `--format=json`, so wiring them
|
|
194
|
+
into a pipeline reuses the same machinery as `rigor check`. See
|
|
195
|
+
[Running Rigor in CI](11-ci.md).
|
|
196
|
+
|
|
197
|
+
## See also
|
|
198
|
+
|
|
199
|
+
- [CLI command reference](02-cli-reference.md#rigor-coverage) —
|
|
200
|
+
the full flag list for `rigor coverage`.
|
|
201
|
+
- [Inspecting inferred types](05-inspecting-types.md) — when a
|
|
202
|
+
receiver is `Dynamic` and you want to know why.
|
|
203
|
+
- [Provided skills](08-skills.md) — the agent skills that turn an
|
|
204
|
+
"add a type here" list into annotations.
|