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,152 @@
|
|
|
1
|
+
# Configuration
|
|
2
|
+
|
|
3
|
+
Rigor reads a single YAML configuration file from the project
|
|
4
|
+
root. `rigor init` writes a starter one.
|
|
5
|
+
|
|
6
|
+
## Discovery and precedence
|
|
7
|
+
|
|
8
|
+
With no `--config` flag, Rigor looks for, in order:
|
|
9
|
+
|
|
10
|
+
1. `.rigor.yml`
|
|
11
|
+
2. `.rigor.dist.yml`
|
|
12
|
+
|
|
13
|
+
The **first file found wins** — the two are not merged. The
|
|
14
|
+
convention is to commit `.rigor.dist.yml` as the shared
|
|
15
|
+
project config and let an individual developer drop an
|
|
16
|
+
(un-tracked) `.rigor.yml` to override it locally.
|
|
17
|
+
|
|
18
|
+
To *layer* configs rather than replace, a config file can name
|
|
19
|
+
a base with `includes:` (recursive). `--config=PATH` bypasses
|
|
20
|
+
discovery entirely.
|
|
21
|
+
|
|
22
|
+
All relative paths in a config file resolve against that
|
|
23
|
+
file's own directory.
|
|
24
|
+
|
|
25
|
+
## A minimal config
|
|
26
|
+
|
|
27
|
+
```yaml
|
|
28
|
+
target_ruby: "4.0"
|
|
29
|
+
paths:
|
|
30
|
+
- lib
|
|
31
|
+
plugins: []
|
|
32
|
+
cache:
|
|
33
|
+
path: .rigor/cache
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Key reference
|
|
37
|
+
|
|
38
|
+
### Sources and target
|
|
39
|
+
|
|
40
|
+
| Key | Type | Default | Meaning |
|
|
41
|
+
| --- | --- | --- | --- |
|
|
42
|
+
| `target_ruby` | String | `"4.0"` | The Ruby version *your* project runs — `"X.Y"`, `"X.Y.Z"`, or `"latest"`. Independent of the Ruby Rigor itself runs on. |
|
|
43
|
+
| `paths` | Array | `["lib"]` | Directories or files to analyse. |
|
|
44
|
+
| `exclude` | Array | `[]` | Glob patterns to skip. `vendor/bundle`, `.bundle`, and `node_modules` are always excluded. |
|
|
45
|
+
| `includes` | Array | `[]` | Other config files to layer underneath this one. |
|
|
46
|
+
| `fold_platform_specific_paths` | Boolean | `false` | Resolve Ruby-version-conditional load paths when discovering sources. |
|
|
47
|
+
|
|
48
|
+
### Type sources
|
|
49
|
+
|
|
50
|
+
| Key | Type | Default | Meaning |
|
|
51
|
+
| --- | --- | --- | --- |
|
|
52
|
+
| `libraries` | Array | `[]` | Standard-library / gem names whose bundled RBS to load. |
|
|
53
|
+
| `signature_paths` | Array | `nil` | Extra directories of `.rbs` files. Relative entries resolve against the config file's directory. |
|
|
54
|
+
| `pre_eval` | Array | `[]` | Files (or globs) walked before per-file analysis, to register project monkey-patches. |
|
|
55
|
+
| `plugins` | Array | `[]` | Plugins to activate — see [Using plugins](07-plugins.md). |
|
|
56
|
+
|
|
57
|
+
### Config validation warnings
|
|
58
|
+
|
|
59
|
+
`rigor check` warns on STDERR when a configured value silently resolves to
|
|
60
|
+
nothing — the class of mistake where a typo loads zero signatures (or
|
|
61
|
+
leaves a suppression inert) and the only symptom is downstream and
|
|
62
|
+
confusing. A missing RBS path, for instance, turns every call into the
|
|
63
|
+
types it was meant to describe into a high-confidence
|
|
64
|
+
`call.undefined-method`, so a one-character mistake can look like hundreds
|
|
65
|
+
of real type errors. The audit covers:
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
rigor: signature_paths: "/path/to/sig" does not exist (no signatures loaded from it)
|
|
69
|
+
rigor: signature_paths: "/path/to/sig" matched 0 signature files
|
|
70
|
+
rigor: libraries: "csb" is not an available RBS library (no signatures loaded from it)
|
|
71
|
+
rigor: disable: "call.undefined-methdo" is not a recognized rule id; the suppression has no effect
|
|
72
|
+
rigor: severity_overrides: "flow.bogus" is not a recognized rule id; the override has no effect
|
|
73
|
+
rigor: bundler.lockfile: "./missing/Gemfile.lock" does not exist
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
These are warnings, not errors — partial or optional bundles and
|
|
77
|
+
forward-looking config are valid setups. The audit only fires on explicit,
|
|
78
|
+
working-setup-safe signals: an unset default (auto-detected `<root>/sig`,
|
|
79
|
+
auto-detected bundle) is never warned about, and a `disable:` /
|
|
80
|
+
`severity_overrides:` token under a *plugin* family (`rspec.…`,
|
|
81
|
+
`rbs_extended.…`) is left alone, since its rule id cannot be enumerated
|
|
82
|
+
statically and may resolve at run time. The same findings appear in the
|
|
83
|
+
`--format=json` payload under `config_warnings` (each tagged with a
|
|
84
|
+
`kind`), so CI can assert on them.
|
|
85
|
+
|
|
86
|
+
### Diagnostics
|
|
87
|
+
|
|
88
|
+
| Key | Type | Default | Meaning |
|
|
89
|
+
| --- | --- | --- | --- |
|
|
90
|
+
| `disable` | Array | `[]` | Rule IDs or families to suppress project-wide. |
|
|
91
|
+
| `severity_profile` | String | `"balanced"` | `lenient`, `balanced`, or `strict` — see [Diagnostics](04-diagnostics.md). |
|
|
92
|
+
| `severity_overrides` | Hash | `{}` | Per-rule / per-family severity, e.g. `{ call: warning, flow.always-truthy-condition: off }`. |
|
|
93
|
+
| `baseline` | String / `false` | `nil` | Path to a `.rigor-baseline.yml`, or `false` to disable an inherited one. See [Baselines](06-baseline.md). |
|
|
94
|
+
| `bleeding_edge` | Boolean / Array / Hash | `false` | Adopt the next major's queued diagnostic disciplines early ([ADR-50](../adr/50-release-engineering-and-stability-strategy.md) § WD2). `false` adopts none; `true` adopts the whole overlay; a list of feature ids adopts only those; `{ all: true, except: [ids] }` adopts all but the named. Orthogonal to `severity_profile`. Override it for a single run with [`rigor check --bleeding-edge[=ids]`](02-cli-reference.md#rigor-check) / `--no-bleeding-edge`. Inspect with [`rigor show-bleedingedge`](02-cli-reference.md#rigor-show-bleedingedge). The overlay is empty in this release, so every form is currently a no-op. |
|
|
95
|
+
|
|
96
|
+
### Dependency RBS discovery
|
|
97
|
+
|
|
98
|
+
| Key | Type | Default | Meaning |
|
|
99
|
+
| --- | --- | --- | --- |
|
|
100
|
+
| `bundler.auto_detect` | Boolean | `true` | Auto-detect the Bundler install path and lockfile. |
|
|
101
|
+
| `bundler.bundle_path` | String | `nil` | Explicit Bundler install root. |
|
|
102
|
+
| `bundler.lockfile` | String | `nil` | Explicit `Gemfile.lock` path. |
|
|
103
|
+
|
|
104
|
+
`bundler.auto_detect` looks for the Bundler install root in project-local
|
|
105
|
+
locations first — the `path` recorded in `<project>/.bundle/config`, then a
|
|
106
|
+
`<project>/vendor/bundle/` directory — and falls back to a user-global
|
|
107
|
+
`bundle config set --global path …` (`~/.bundle/config`) when the project
|
|
108
|
+
has no in-tree bundle.
|
|
109
|
+
|
|
110
|
+
It deliberately does **not** read `BUNDLE_PATH` from rigor's own
|
|
111
|
+
environment, and it cannot reach gems installed at the *default* shared
|
|
112
|
+
location (the active Ruby's `GEM_HOME`, when no `path` is configured):
|
|
113
|
+
rigor runs in its own isolated Ruby and reads your project as data
|
|
114
|
+
([ADR-27](../adr/27-tool-distribution-model.md)), so it does not know the
|
|
115
|
+
project Ruby's gem home without running your toolchain. If `rigor check`'s
|
|
116
|
+
`--stats` shows gems whose RBS it could not find, point it at the bundle
|
|
117
|
+
explicitly with `bundler.bundle_path:`, or supply signatures another way:
|
|
118
|
+
`rbs collection install` (auto-discovered) or `dependencies.source_inference:`.
|
|
119
|
+
| `rbs_collection.auto_detect` | Boolean | `true` | Auto-discover `rbs_collection.lock.yaml`. |
|
|
120
|
+
| `rbs_collection.lockfile` | String | `nil` | Explicit `rbs_collection.lock.yaml` path. |
|
|
121
|
+
| `dependencies.source_inference` | Array | `[]` | Per-gem source-inference modes (ADR-10). |
|
|
122
|
+
| `dependencies.budget_per_gem` | Integer | `5000` | Per-gem source-walk cap, counted in **method definitions** (not time): the walker stops harvesting a gem's catalog once it reaches this many `def`s, then emits `dynamic.dependency-source.budget-exceeded` and degrades the rest to `Dynamic[top]`. Range 1250–20000. |
|
|
123
|
+
| `dependencies.budget_overrun_strategy` | String | `"walker_cap"` | What happens to calls on a gem that hit `budget_per_gem` (ADR-10 § 5b). `walker_cap` (default) lets methods past the cap fall through to the engine's normal user-class resolution. `dependency_silence` instead resolves any call on a class from a budget-exceeded gem to `Dynamic[top]`, silencing `call.undefined-method` on that gem's unrecorded surface at the cost of weaker static checking there. |
|
|
124
|
+
|
|
125
|
+
### Execution
|
|
126
|
+
|
|
127
|
+
| Key | Type | Default | Meaning |
|
|
128
|
+
| --- | --- | --- | --- |
|
|
129
|
+
| `cache.path` | String | `.rigor/cache` | Persistent cache directory. See [Caching](12-caching.md). |
|
|
130
|
+
| `cache.max_bytes` | Integer or `null` | `268435456` (256 MB) | LRU eviction cap for the cache directory; `null` disables eviction. See [Caching § Size and eviction](12-caching.md#size-and-eviction). |
|
|
131
|
+
| `parallel.workers` | Integer | `0` | Parallel worker processes for per-file analysis (fork-based pool today; ADR-15); `0` is sequential. CLI `--workers` and `RIGOR_RACTOR_WORKERS` take precedence. |
|
|
132
|
+
| `plugins_io.network` | String | `"disabled"` | Plugin network policy — `disabled` or `allowlist`. |
|
|
133
|
+
| `plugins_io.allowed_paths` | Array | `[]` | Filesystem paths plugins may read. |
|
|
134
|
+
| `plugins_io.allowed_url_hosts` | Array | `[]` | URL hosts plugins may fetch from when `network: allowlist`. |
|
|
135
|
+
|
|
136
|
+
## A worked example
|
|
137
|
+
|
|
138
|
+
```yaml
|
|
139
|
+
target_ruby: "3.4"
|
|
140
|
+
paths:
|
|
141
|
+
- lib
|
|
142
|
+
- app
|
|
143
|
+
exclude:
|
|
144
|
+
- "**/*_pb.rb"
|
|
145
|
+
plugins:
|
|
146
|
+
- rigor-activerecord
|
|
147
|
+
- rigor-rspec
|
|
148
|
+
severity_profile: balanced
|
|
149
|
+
severity_overrides:
|
|
150
|
+
flow.dead-assignment: warning
|
|
151
|
+
baseline: .rigor-baseline.yml
|
|
152
|
+
```
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# Diagnostics
|
|
2
|
+
|
|
3
|
+
When `rigor check` finds a problem it reports a **diagnostic**:
|
|
4
|
+
a file, a line and column, a severity, a rule ID, and a
|
|
5
|
+
message. This page is the reference for the rule catalogue,
|
|
6
|
+
the severity model, and suppression. For the *reasoning*
|
|
7
|
+
behind each rule, see
|
|
8
|
+
[handbook chapter 8](../handbook/08-understanding-errors.md).
|
|
9
|
+
|
|
10
|
+
## Rule IDs
|
|
11
|
+
|
|
12
|
+
Every rule has a two-segment `family.rule` identifier:
|
|
13
|
+
|
|
14
|
+
| Family | Covers |
|
|
15
|
+
| --- | --- |
|
|
16
|
+
| `call` | Call sites — undefined methods, arity, argument types, nil receivers. |
|
|
17
|
+
| `flow` | Control-flow proofs — always-raises, dead branches, constant conditions. |
|
|
18
|
+
| `def` | Method definitions — return types, ivar writes, visibility. |
|
|
19
|
+
| `assert` | `assert_type` checks. |
|
|
20
|
+
| `dump` | `dump_type` notices. |
|
|
21
|
+
|
|
22
|
+
`rigor explain <rule>` prints the full catalogue entry for any
|
|
23
|
+
ID; `rigor explain` with no argument lists them all.
|
|
24
|
+
|
|
25
|
+
### Catalogue
|
|
26
|
+
|
|
27
|
+
Each rule has a stable per-rule anchor on this page
|
|
28
|
+
(`#rule-<family>-<name>`, dots written as dashes) — the
|
|
29
|
+
`documentation_url` field in `--format json` and `rigor explain`'s
|
|
30
|
+
`Documentation:` line both point here. The `Evidence` column is
|
|
31
|
+
Rigor's confidence that a firing is a true positive (see
|
|
32
|
+
[Evidence tier](#evidence-tier) below).
|
|
33
|
+
|
|
34
|
+
| Rule | Fires when | Evidence |
|
|
35
|
+
| --- | --- | --- |
|
|
36
|
+
| <a id="rule-call-undefined-method"></a>`call.undefined-method` | The method is not defined on the receiver's statically known class. | high |
|
|
37
|
+
| <a id="rule-call-self-undefined-method"></a>`call.self-undefined-method` | An implicit-self call (no receiver) resolves to no method on a confidently-closed standalone class. Ships `:off`; opt in via `severity_overrides`. | low |
|
|
38
|
+
| <a id="rule-call-wrong-arity"></a>`call.wrong-arity` | The positional-argument count matches no signature. | high |
|
|
39
|
+
| <a id="rule-call-argument-type-mismatch"></a>`call.argument-type-mismatch` | An argument's type provably violates the parameter contract. | high |
|
|
40
|
+
| <a id="rule-call-possible-nil-receiver"></a>`call.possible-nil-receiver` | The receiver is `T \| nil` and the method is not defined on `NilClass`. | high |
|
|
41
|
+
| <a id="rule-call-unresolved-toplevel"></a>`call.unresolved-toplevel` | A top-level implicit-self call resolves against no same-file `def`, `pre_eval:` patch, or `Kernel` / `Object` method. | low |
|
|
42
|
+
| <a id="rule-flow-always-raises"></a>`flow.always-raises` | The expression provably raises on every reachable path. | high |
|
|
43
|
+
| <a id="rule-flow-unreachable-branch"></a>`flow.unreachable-branch` | An `if` / `unless` / ternary branch is statically dead. | high |
|
|
44
|
+
| <a id="rule-flow-always-truthy-condition"></a>`flow.always-truthy-condition` | A condition is provably always truthy or always falsey. | medium |
|
|
45
|
+
| <a id="rule-flow-dead-assignment"></a>`flow.dead-assignment` | A local is written but never read in the same method. | medium |
|
|
46
|
+
| <a id="rule-flow-unreachable-clause"></a>`flow.unreachable-clause` | A `case`/`when` or `case`/`in` clause is statically dead — its subject type is disjoint with the pattern, or a prior clause already exhausted the subject. | medium |
|
|
47
|
+
| <a id="rule-def-return-type-mismatch"></a>`def.return-type-mismatch` | The method body's result violates its declared RBS return type. | medium |
|
|
48
|
+
| <a id="rule-def-ivar-write-mismatch"></a>`def.ivar-write-mismatch` | An instance variable is written with a type disagreeing with its first write. | high |
|
|
49
|
+
| <a id="rule-def-method-visibility-mismatch"></a>`def.method-visibility-mismatch` | An explicit-receiver call reaches a private method. | high |
|
|
50
|
+
| <a id="rule-def-override-visibility-reduced"></a>`def.override-visibility-reduced` | An override reduces the visibility it inherits from a project-defined ancestor. | high |
|
|
51
|
+
| <a id="rule-def-override-return-widened"></a>`def.override-return-widened` | An override's declared return type widens the inherited return (covariance). | high |
|
|
52
|
+
| <a id="rule-def-override-param-narrowed"></a>`def.override-param-narrowed` | An override narrows an inherited parameter type (contravariance). | high |
|
|
53
|
+
| <a id="rule-rbs_extended-unsatisfied-conformance"></a>`rbs_extended.unsatisfied-conformance` | A class declares `%a{rigor:v1:conforms-to _Interface}` in its RBS but is missing a method the interface requires. Presence-based: only definitively-absent required methods fire. | — |
|
|
54
|
+
| <a id="rule-assert-type-mismatch"></a>`assert.type-mismatch` | An `assert_type` expectation does not match the inferred type. | high |
|
|
55
|
+
| <a id="rule-dump-type"></a>`dump.type` | A `dump_type` call — informational, prints the inferred type. | — |
|
|
56
|
+
|
|
57
|
+
Plugins may contribute further families and rules; `rigor
|
|
58
|
+
explain` lists whatever the active configuration loads.
|
|
59
|
+
|
|
60
|
+
## Evidence tier
|
|
61
|
+
|
|
62
|
+
Every rule in the catalogue above carries an **evidence tier** —
|
|
63
|
+
Rigor's own confidence that a firing is a *true positive*, derived
|
|
64
|
+
from the rule's firing gates. It is orthogonal to severity (impact) and to
|
|
65
|
+
the severity profile: the tier never changes whether a diagnostic
|
|
66
|
+
surfaces, it only routes attention.
|
|
67
|
+
|
|
68
|
+
| Tier | Meaning |
|
|
69
|
+
| --- | --- |
|
|
70
|
+
| `high` | Fires only on a concrete, statically-known type with no metaprogramming escape. Rigor's false-positive discipline has already filtered the uncertain cases, so a firing is almost always a real problem — a consumer can act on it (or a downstream classifier can trust it) without cross-checking another tool. |
|
|
71
|
+
| `medium` | Rests on a flow- or inference-level proof that inherits a documented false-positive envelope (loop / mutation / RBS-strictness modelling gaps, narrowed by the rule's *does not fire when* list). Usually right, but not literal-provable. |
|
|
72
|
+
| `low` | A resolution- or coverage-gap signal: a firing frequently reflects context the analyzer cannot see (an unanalyzed file, a metaprogramming patch) rather than a definite bug. Treat as "review this" — e.g. route `call.unresolved-toplevel` to a `pre_eval:` decision. |
|
|
73
|
+
|
|
74
|
+
Informational rules (`dump.type`) carry no tier. The per-rule tier
|
|
75
|
+
is the single source of truth in the rule catalogue — read it with
|
|
76
|
+
`rigor explain <rule>` or `rigor explain --format json`, and it is
|
|
77
|
+
echoed on each diagnostic in `rigor check --format json` (below).
|
|
78
|
+
|
|
79
|
+
## Severity profiles
|
|
80
|
+
|
|
81
|
+
Each rule emits with an authored severity, then a **profile**
|
|
82
|
+
re-stamps it for the run. Three profiles, set with the
|
|
83
|
+
`severity_profile:` config key:
|
|
84
|
+
|
|
85
|
+
| Profile | Stance |
|
|
86
|
+
| --- | --- |
|
|
87
|
+
| `lenient` | Only proven diagnostics are errors; uncertain ones drop to `warning` / `info`. For incremental adoption on legacy code. |
|
|
88
|
+
| `balanced` *(default)* | Most rules `error`; `dump.type` `info`; uncertain rules `warning`. |
|
|
89
|
+
| `strict` | Every rule is an `error`. CI-friendly. |
|
|
90
|
+
|
|
91
|
+
For finer control, `severity_overrides:` maps a rule ID or a
|
|
92
|
+
family to one of `error`, `warning`, `info`, or `off`:
|
|
93
|
+
|
|
94
|
+
```yaml
|
|
95
|
+
severity_profile: balanced
|
|
96
|
+
severity_overrides:
|
|
97
|
+
flow.always-truthy-condition: off
|
|
98
|
+
call: warning
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
A rule-specific override beats a family override.
|
|
102
|
+
|
|
103
|
+
## Machine-readable output (`--format json`)
|
|
104
|
+
|
|
105
|
+
`rigor check --format json` emits the diagnostics as a JSON
|
|
106
|
+
document for editors, CI, and AI agents. Each diagnostic is an
|
|
107
|
+
object with **stable, structured fields** — so a consumer filters
|
|
108
|
+
and groups on them directly and **never parses the human-readable
|
|
109
|
+
`message`** (the wording is presentation, not contract, and may be
|
|
110
|
+
reworded in a minor release):
|
|
111
|
+
|
|
112
|
+
| Field | Present | Meaning |
|
|
113
|
+
| --- | --- | --- |
|
|
114
|
+
| `path` / `line` / `column` | always | Location (1-based line and column). |
|
|
115
|
+
| `severity` | always | `error` / `warning` / `info`. |
|
|
116
|
+
| `rule` | always (`null` for parse / internal errors) | The `family.rule` ID. |
|
|
117
|
+
| `source_family` | always | `builtin`, `rbs_extended`, `generated.*`, or `plugin.<id>`. |
|
|
118
|
+
| `message` | always | Human-readable text — *presentation, not contract*. |
|
|
119
|
+
| `receiver_type` | when the rule has a receiver | The called receiver's displayed type (`String`, `Array[User]`, …). |
|
|
120
|
+
| `method_name` | when the rule has a method | The called / defined method name. |
|
|
121
|
+
| `project_definition_site` | `call.undefined-method` monkey-patch case | `path:line` where the project itself defines the method (ADR-17). |
|
|
122
|
+
| `evidence_tier` | built-in rules with a tier | `high` / `medium` / `low` — Rigor's confidence the firing is a true positive ([Evidence tier](#evidence-tier)). |
|
|
123
|
+
| `documentation_url` | built-in rules | A stable URL to the rule's entry in this catalogue. |
|
|
124
|
+
|
|
125
|
+
`evidence_tier` lets a consumer prioritise without re-deriving
|
|
126
|
+
confidence — e.g. surface only `high` firings in a strict CI gate,
|
|
127
|
+
or route `low` firings to a human review queue:
|
|
128
|
+
|
|
129
|
+
```sh
|
|
130
|
+
# only the high-confidence diagnostics
|
|
131
|
+
rigor check --format json \
|
|
132
|
+
| jq '[.diagnostics[] | select(.evidence_tier == "high")]'
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Coverage block (`--coverage`)
|
|
136
|
+
|
|
137
|
+
`rigor check --coverage` adds a top-level `coverage` object so a single
|
|
138
|
+
run reports both *what fired* and *how much of the analyzed surface
|
|
139
|
+
Rigor could type* — useful when a large diagnostic count raises the
|
|
140
|
+
question "did it analyze all my files, or only a few?". The block
|
|
141
|
+
mirrors the `summary` of `rigor check`'s sibling
|
|
142
|
+
[`rigor coverage`](02-cli-reference.md#rigor-coverage) (same
|
|
143
|
+
precision-tier vocabulary), plus `scan_files`:
|
|
144
|
+
|
|
145
|
+
```jsonc
|
|
146
|
+
"coverage": {
|
|
147
|
+
"scan_files": 203,
|
|
148
|
+
"parse_errors": 0,
|
|
149
|
+
"expressions_typed": 18394,
|
|
150
|
+
"precise_count": 9847,
|
|
151
|
+
"precise_ratio": 0.535,
|
|
152
|
+
"dynamic_opaque_count": 8547,
|
|
153
|
+
"dynamic_opaque_ratio": 0.465
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
It is **off by default** — computing it is a second precision pass over
|
|
158
|
+
the analyzed files — so the default check path's cost is unchanged. In
|
|
159
|
+
text mode `--coverage` prints a one-line summary instead. For the full
|
|
160
|
+
per-file / per-tier breakdown, run `rigor coverage` directly.
|
|
161
|
+
|
|
162
|
+
The `receiver_type` / `method_name` pair is populated by the
|
|
163
|
+
call-family rules and the method-level `def.*` rules. Group a run
|
|
164
|
+
by the called class and method with `jq`, no message parsing:
|
|
165
|
+
|
|
166
|
+
```sh
|
|
167
|
+
# every diagnostic that names a method, as {receiver, method, rule}
|
|
168
|
+
rigor check --format json \
|
|
169
|
+
| jq '[.diagnostics[] | select(.method_name) | {receiver: .receiver_type, method: .method_name, rule}]'
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
The `check` stream is **faithful per-site** — a literal receiver
|
|
173
|
+
reports its literal type (`"hi"`, `42`). For the **aggregated**
|
|
174
|
+
view — counts per class/method across the whole run, with literal
|
|
175
|
+
receivers folded to their class — use
|
|
176
|
+
[`rigor triage`](02-cli-reference.md)'s `selectors` section.
|
|
177
|
+
|
|
178
|
+
## Suppressing a diagnostic
|
|
179
|
+
|
|
180
|
+
Three layers, from narrowest to broadest.
|
|
181
|
+
|
|
182
|
+
**In-source, one line.** A trailing comment suppresses the
|
|
183
|
+
named rules on that line:
|
|
184
|
+
|
|
185
|
+
```ruby
|
|
186
|
+
config.merge(extra) # rigor:disable call.undefined-method
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
It accepts qualified IDs, family wildcards (`call`), a
|
|
190
|
+
comma- or space-separated list, or `all`.
|
|
191
|
+
|
|
192
|
+
**In-source, whole file.** `# rigor:disable-file <rules>`
|
|
193
|
+
anywhere in a file suppresses those rules for every line;
|
|
194
|
+
`# rigor:disable-file all` silences the file.
|
|
195
|
+
|
|
196
|
+
**Project-wide.** The `disable:` config key turns rules off
|
|
197
|
+
across the whole run:
|
|
198
|
+
|
|
199
|
+
```yaml
|
|
200
|
+
disable:
|
|
201
|
+
- flow.dead-assignment
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
For a *known backlog* you want to keep visible but not fail
|
|
205
|
+
on, prefer a [baseline](06-baseline.md) over a blanket
|
|
206
|
+
`disable:` — `disable:` also hides any new occurrences.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Inspecting inferred types
|
|
2
|
+
|
|
3
|
+
Rigor's analysis is invisible by default — it only speaks up to
|
|
4
|
+
report a diagnostic. When you want to *see* what type the
|
|
5
|
+
engine assigns an expression, there are four tools: two source
|
|
6
|
+
helpers and two CLI commands.
|
|
7
|
+
|
|
8
|
+
## `dump_type` — print a type from source
|
|
9
|
+
|
|
10
|
+
`dump_type(expr)` makes Rigor emit an `info`-severity
|
|
11
|
+
`dump.type` diagnostic showing the inferred type of `expr`. At
|
|
12
|
+
runtime it is a no-op that returns `expr` unchanged, so it is
|
|
13
|
+
safe to leave in or sprinkle freely while debugging.
|
|
14
|
+
|
|
15
|
+
```ruby
|
|
16
|
+
require "rigor/testing"
|
|
17
|
+
include Rigor::Testing
|
|
18
|
+
|
|
19
|
+
dump_type(1 + 2) # rigor reports: dump.type — Constant<3>
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Rigor recognises the call when it is written as `dump_type(…)`
|
|
23
|
+
after `include Rigor::Testing`, or fully qualified as
|
|
24
|
+
`Rigor::Testing.dump_type(…)` / `Rigor.dump_type(…)`.
|
|
25
|
+
|
|
26
|
+
## `assert_type` — pin a type in source
|
|
27
|
+
|
|
28
|
+
`assert_type("TypeString", expr)` compares `expr`'s inferred
|
|
29
|
+
type against the literal type string. On a mismatch Rigor
|
|
30
|
+
emits an `error`-severity `assert.type-mismatch` diagnostic; on
|
|
31
|
+
a match it stays silent. Like `dump_type`, it returns `expr`
|
|
32
|
+
unchanged at runtime.
|
|
33
|
+
|
|
34
|
+
```ruby
|
|
35
|
+
assert_type("Constant<3>", 1 + 2) # silent — matches
|
|
36
|
+
assert_type("Integer", 1 + 2) # assert.type-mismatch
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The type string is matched against the engine's short display
|
|
40
|
+
form. `assert_type` is how the handbook's examples stay honest,
|
|
41
|
+
and it doubles as a regression check you can keep in a
|
|
42
|
+
project's own test sources.
|
|
43
|
+
|
|
44
|
+
## `rigor annotate` — types in the margin
|
|
45
|
+
|
|
46
|
+
`rigor annotate FILE` reprints a whole file with every line
|
|
47
|
+
tagged by the type of the expression it evaluates to, as a
|
|
48
|
+
trailing `#=>` comment (the xmpfilter / seeing_is_believing
|
|
49
|
+
convention):
|
|
50
|
+
|
|
51
|
+
```ruby
|
|
52
|
+
two = 1 + 1 #=> 2
|
|
53
|
+
name = gets #=> String | nil
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
It is the fastest way to survey a file. The annotation is
|
|
57
|
+
idempotent — re-running replaces the previous `#=>` comment
|
|
58
|
+
(including hand-written ones, and the pre-v0.2.0
|
|
59
|
+
`#=> dump_type:` spelling) rather than stacking it. Output is
|
|
60
|
+
syntax-highlighted for a tty — through
|
|
61
|
+
[`bat`](https://github.com/sharkdp/bat) when it is found on
|
|
62
|
+
`PATH` (`--no-bat` opts out), otherwise via the built-in
|
|
63
|
+
colorizer; `--no-color` (and the `NO_COLOR` environment
|
|
64
|
+
variable) disable the colour.
|
|
65
|
+
|
|
66
|
+
## `rigor type-of` — one position
|
|
67
|
+
|
|
68
|
+
When you only need one expression's type — typically while
|
|
69
|
+
chasing down why a diagnostic did or did not fire — query a
|
|
70
|
+
single position:
|
|
71
|
+
|
|
72
|
+
```sh
|
|
73
|
+
rigor type-of lib/example.rb:12:8
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`--format=json` emits a machine-readable result for tooling.
|
|
77
|
+
This is the same query the editor integration answers on
|
|
78
|
+
hover.
|
|
79
|
+
|
|
80
|
+
## `rigor trace` — watch the inference happen
|
|
81
|
+
|
|
82
|
+
Where `annotate` and `type-of` show the *answer*, `rigor trace
|
|
83
|
+
FILE` shows the *derivation*: it re-runs the engine over the
|
|
84
|
+
file and replays the recorded inference events as a
|
|
85
|
+
step-through terminal animation — the moment a local enters the
|
|
86
|
+
scope, the moment two branch types merge into a union, the
|
|
87
|
+
moment a method call resolves (or fail-softens to
|
|
88
|
+
`Dynamic[top]`).
|
|
89
|
+
|
|
90
|
+
```sh
|
|
91
|
+
rigor trace lib/example.rb # step on key press
|
|
92
|
+
rigor trace --delay=0.5 lib/example.rb # autoplay
|
|
93
|
+
rigor trace --format=json lib/example.rb # raw event stream
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
`--verbose` adds an enter/result frame for every expression the
|
|
97
|
+
typer visits; the default keeps only the three teachable event
|
|
98
|
+
kinds. The JSON stream is stable enough to build course
|
|
99
|
+
material or figures from.
|
|
100
|
+
|
|
101
|
+
## Which to reach for
|
|
102
|
+
|
|
103
|
+
| You want… | Use |
|
|
104
|
+
| --- | --- |
|
|
105
|
+
| One expression's type, from the shell | `rigor type-of` |
|
|
106
|
+
| Every line of a file surveyed | `rigor annotate` |
|
|
107
|
+
| The derivation replayed step by step | `rigor trace` |
|
|
108
|
+
| A type printed mid-analysis, in context | `dump_type` |
|
|
109
|
+
| A type *asserted* and regression-checked | `assert_type` |
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# Baselines
|
|
2
|
+
|
|
3
|
+
A **baseline** records the diagnostics a project already has,
|
|
4
|
+
so `rigor check` can stay silent about them and surface only
|
|
5
|
+
what is *new*. It is the pragmatic on-ramp for adopting Rigor
|
|
6
|
+
on an existing codebase: you do not have to reach zero
|
|
7
|
+
diagnostics before the check becomes useful in CI.
|
|
8
|
+
|
|
9
|
+
## The baseline file
|
|
10
|
+
|
|
11
|
+
A baseline is a YAML file — `.rigor-baseline.yml` by
|
|
12
|
+
convention — listing buckets of known diagnostics:
|
|
13
|
+
|
|
14
|
+
```yaml
|
|
15
|
+
version: 1
|
|
16
|
+
ignored:
|
|
17
|
+
- file: app/models/user.rb
|
|
18
|
+
rule: call.undefined-method
|
|
19
|
+
count: 3
|
|
20
|
+
- file: app/lib/legacy.rb
|
|
21
|
+
rule: call.argument-type-mismatch
|
|
22
|
+
count: 1
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Each row is a **bucket** keyed by `(file, rule)`, with a
|
|
26
|
+
`count` of how many diagnostics of that rule the file is
|
|
27
|
+
allowed. An optional `message:` field (a regular-expression
|
|
28
|
+
source) narrows a bucket to call sites whose message matches.
|
|
29
|
+
|
|
30
|
+
You do not hand-write this file — `rigor baseline generate`
|
|
31
|
+
produces it.
|
|
32
|
+
|
|
33
|
+
## Turning a baseline on
|
|
34
|
+
|
|
35
|
+
A baseline file sitting in the project is **dormant** until
|
|
36
|
+
something activates it — presence alone does nothing. Activate
|
|
37
|
+
it with the config key:
|
|
38
|
+
|
|
39
|
+
```yaml
|
|
40
|
+
baseline: .rigor-baseline.yml
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
or per run with `rigor check --baseline=PATH`. `--no-baseline`
|
|
44
|
+
ignores a configured one for a single run, and `baseline:
|
|
45
|
+
false` in a config file disables a baseline inherited through
|
|
46
|
+
`includes:`.
|
|
47
|
+
|
|
48
|
+
## All-or-nothing per bucket
|
|
49
|
+
|
|
50
|
+
When a baseline is active, each bucket is matched whole:
|
|
51
|
+
|
|
52
|
+
- **current count ≤ recorded count** — every diagnostic in the
|
|
53
|
+
bucket is silenced.
|
|
54
|
+
- **current count > recorded count** — every diagnostic in the
|
|
55
|
+
bucket surfaces, not just the excess.
|
|
56
|
+
|
|
57
|
+
The reasoning: line numbers drift as a file is edited, so a
|
|
58
|
+
partial match cannot reliably point at *which* diagnostic is
|
|
59
|
+
the new one. Surfacing the whole bucket asks you to review
|
|
60
|
+
that rule × file together.
|
|
61
|
+
|
|
62
|
+
The baseline filter runs **last** — after `# rigor:disable`
|
|
63
|
+
comments and the severity profile. A baseline never resurrects
|
|
64
|
+
a diagnostic another layer has already suppressed.
|
|
65
|
+
|
|
66
|
+
## The `rigor baseline` commands
|
|
67
|
+
|
|
68
|
+
| Command | Use |
|
|
69
|
+
| --- | --- |
|
|
70
|
+
| `rigor baseline generate` | Write a fresh baseline from the current diagnostics. Refuses to clobber an existing file without `--force`. |
|
|
71
|
+
| `rigor baseline regenerate` | Rewrite unconditionally — run it after fixing diagnostics so the file shrinks. |
|
|
72
|
+
| `rigor baseline dump` | Print the baseline, filterable with `--rule` and `--file`. |
|
|
73
|
+
| `rigor baseline drift` | Show how buckets have moved — `--only=over` for buckets that grew, `reducible` for ones you have already improved past, `cleared` for empty ones. |
|
|
74
|
+
| `rigor baseline prune` | Drop buckets that match nothing any more. `--dry-run` previews. |
|
|
75
|
+
|
|
76
|
+
`generate` and `regenerate` take `--match-mode=rule` (the
|
|
77
|
+
default — one bucket per file × rule) or `--match-mode=message`
|
|
78
|
+
(a bucket per distinct message: more precise, more churn).
|
|
79
|
+
|
|
80
|
+
## Working a baseline down
|
|
81
|
+
|
|
82
|
+
`rigor triage` summarises a diagnostic stream — rule
|
|
83
|
+
distribution, class/method selectors, the files with the most
|
|
84
|
+
diagnostics, and heuristic hints about likely causes — so you can
|
|
85
|
+
decide what to tackle first:
|
|
86
|
+
|
|
87
|
+
```sh
|
|
88
|
+
rigor triage
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The `selectors` section (`rigor triage --format json | jq
|
|
92
|
+
'.selectors'`) is the best prioritisation signal: a class/method
|
|
93
|
+
with a high `count` spread across many `files` is a systemic cause
|
|
94
|
+
one fix or a `pre_eval:` entry clears in bulk, while a low-`count`
|
|
95
|
+
selector is a candidate genuine bug to fix at the site.
|
|
96
|
+
|
|
97
|
+
It is advisory and always exits `0`. The intended loop is
|
|
98
|
+
`triage` to prioritise → fix or suppress a rule → `rigor
|
|
99
|
+
baseline regenerate` to shrink the file. The
|
|
100
|
+
[`rigor-baseline-reduce` skill](08-skills.md) walks this loop
|
|
101
|
+
interactively.
|
|
102
|
+
|
|
103
|
+
To make CI fail when the baseline *grows*, add
|
|
104
|
+
`--baseline-strict` to `rigor check`.
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Using plugins
|
|
2
|
+
|
|
3
|
+
A plugin teaches Rigor about a framework, a gem, or an
|
|
4
|
+
application DSL that plain inference cannot see — Rails route
|
|
5
|
+
helpers, RSpec `let` bindings, dry-rb struct attributes, and
|
|
6
|
+
so on. This page is about *activating* plugins. Writing one is
|
|
7
|
+
covered by [`examples/`](../../examples/README.md) and the
|
|
8
|
+
[`rigor-plugin-author` skill](08-skills.md).
|
|
9
|
+
|
|
10
|
+
## Activating a plugin
|
|
11
|
+
|
|
12
|
+
List plugins under the `plugins:` key in your config file:
|
|
13
|
+
|
|
14
|
+
```yaml
|
|
15
|
+
plugins:
|
|
16
|
+
- rigor-activerecord
|
|
17
|
+
- rigor-rspec
|
|
18
|
+
- rigor-rails-routes
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Each name is a plugin that ships bundled inside the `rigortype`
|
|
22
|
+
gem — no separate installation is needed. Listing a plugin under
|
|
23
|
+
`plugins:` is enough to activate it. A plugin that needs
|
|
24
|
+
configuration takes the object form:
|
|
25
|
+
|
|
26
|
+
```yaml
|
|
27
|
+
plugins:
|
|
28
|
+
- gem: rigor-activerecord
|
|
29
|
+
config:
|
|
30
|
+
schema: db/schema.rb
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Available plugins
|
|
34
|
+
|
|
35
|
+
Rigor ships a catalogue of production plugins under
|
|
36
|
+
[`plugins/`](../../plugins/README.md). The set grows between
|
|
37
|
+
releases — consult that directory for the current list and
|
|
38
|
+
each plugin's options — but the families today are:
|
|
39
|
+
|
|
40
|
+
- **Rails** — `rigor-activerecord`, `rigor-actionpack`,
|
|
41
|
+
`rigor-rails-routes`, `rigor-rails-i18n`,
|
|
42
|
+
`rigor-actionmailer`, `rigor-activejob`,
|
|
43
|
+
`rigor-activestorage`, `rigor-actioncable`. The
|
|
44
|
+
`rigor-rails` meta-gem bundles the Rails set for Gemfile
|
|
45
|
+
convenience; you still enumerate the individual plugins you
|
|
46
|
+
want under `plugins:`.
|
|
47
|
+
- **Testing** — `rigor-rspec`, `rigor-rspec-rails`,
|
|
48
|
+
`rigor-minitest`, `rigor-shoulda-matchers`,
|
|
49
|
+
`rigor-factorybot`.
|
|
50
|
+
- **dry-rb** — `rigor-dry-types`, `rigor-dry-schema`,
|
|
51
|
+
`rigor-dry-struct`, `rigor-dry-validation`.
|
|
52
|
+
- **Other ecosystems** — `rigor-sinatra`, `rigor-hanami`,
|
|
53
|
+
`rigor-devise`, `rigor-pundit`, `rigor-sidekiq`,
|
|
54
|
+
`rigor-graphql`, `rigor-statesman`, `rigor-sorbet`,
|
|
55
|
+
`rigor-typescript-utility-types`,
|
|
56
|
+
`rigor-activesupport-core-ext`.
|
|
57
|
+
|
|
58
|
+
## `plugins/` versus `examples/`
|
|
59
|
+
|
|
60
|
+
[`plugins/`](../../plugins/README.md) holds production plugins
|
|
61
|
+
for real gems and frameworks — the ones you activate. The
|
|
62
|
+
[`examples/`](../../examples/README.md) tree holds *tutorial*
|
|
63
|
+
plugins over deliberately simplified DSLs; they are reading
|
|
64
|
+
material for plugin authors, not for activation in a real
|
|
65
|
+
project.
|
|
66
|
+
|
|
67
|
+
## Sandboxing
|
|
68
|
+
|
|
69
|
+
A plugin may want to read a file (a schema dump) or reach the
|
|
70
|
+
network. Those are gated by the `plugins_io:` config keys —
|
|
71
|
+
the network is `disabled` by default, and a plugin can read
|
|
72
|
+
only the paths you list. See
|
|
73
|
+
[Configuration](03-configuration.md).
|
|
74
|
+
|
|
75
|
+
### Isolation strategy
|
|
76
|
+
|
|
77
|
+
A few plugins call into their target library directly (for
|
|
78
|
+
example to ask ActiveSupport's real inflector how to pluralise a
|
|
79
|
+
class name). That call runs under an **isolation strategy**, set
|
|
80
|
+
with the `RIGOR_PLUGIN_ISOLATION` environment variable:
|
|
81
|
+
|
|
82
|
+
| Value | Behaviour |
|
|
83
|
+
| --- | --- |
|
|
84
|
+
| `process` (default) | Run the call in a forked, crash-contained worker, so the target library's monkey-patches and any crash never contaminate Rigor. Falls back to `none` where `fork` is unavailable (Windows / JRuby). |
|
|
85
|
+
| `none` | Load the library into Rigor's own process and call it directly. |
|
|
86
|
+
| `ruby_box` | Run inside an experimental `Ruby::Box` sandbox. This needs the `RUBY_BOX=1` start flag, so the `rigor` launcher re-execs itself with it set when you select this strategy. |
|
|
87
|
+
|
|
88
|
+
The legacy `RIGOR_BOX` environment variable is a back-compat
|
|
89
|
+
alias for `RIGOR_PLUGIN_ISOLATION=ruby_box`. The default
|
|
90
|
+
(`process`) is the right choice for almost everyone; the variable
|
|
91
|
+
exists for the rare platform where forking is unavailable or
|
|
92
|
+
where you want stronger containment.
|