rigortype 0.3.5 → 0.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +5 -5
- data/data/gem_overlay/activesupport/core_ext.rbs +33 -0
- data/docs/handbook/02-everyday-types.md +1 -1
- data/docs/handbook/04-tuples-and-shapes.md +1 -1
- data/docs/handbook/08-understanding-errors.md +1 -1
- data/docs/handbook/09-plugins.md +2 -2
- data/docs/handbook/10-sorbet.md +1 -1
- data/docs/handbook/README.md +2 -2
- data/docs/handbook/appendix-go.md +1 -1
- data/docs/handbook/appendix-java-csharp.md +2 -2
- data/docs/handbook/appendix-mypy.md +1 -1
- data/docs/handbook/appendix-protocols-and-structural-typing.md +2 -2
- data/docs/handbook/appendix-rust.md +1 -1
- data/docs/handbook/appendix-type-theory.md +2 -2
- data/docs/handbook/appendix-typescript.md +4 -4
- data/docs/manual/02-cli-reference.md +13 -9
- data/docs/manual/03-configuration.md +1 -1
- data/docs/manual/04-diagnostics.md +12 -0
- data/docs/manual/08-skills.md +1 -1
- data/docs/manual/11-ci.md +1 -1
- data/docs/manual/12-caching.md +1 -1
- data/docs/manual/15-type-protection-coverage.md +1 -1
- data/docs/manual/18-removing-dead-code.md +13 -4
- data/docs/manual/19-effect-labels.md +19 -12
- data/docs/manual/README.md +2 -2
- data/docs/manual/plugins/rigor-actionmailer.md +4 -4
- data/docs/manual/plugins/rigor-activejob.md +3 -3
- data/docs/manual/plugins/rigor-activerecord.md +4 -4
- data/docs/manual/plugins/rigor-rails-i18n.md +5 -5
- data/docs/manual/plugins/rigor-rspec.md +6 -2
- data/lib/rigor/analysis/diagnostic.rb +17 -6
- data/lib/rigor/analysis/effects_cache_probe.rb +132 -0
- data/lib/rigor/analysis/reachability/graph.rb +30 -11
- data/lib/rigor/analysis/reachability/plugin_roots.rb +11 -8
- data/lib/rigor/analysis/reachability/scan.rb +20 -4
- data/lib/rigor/analysis/reachability/scan_cache.rb +130 -0
- data/lib/rigor/analysis/run_cache_key.rb +12 -0
- data/lib/rigor/analysis/runner/effect_envelope_pass.rb +14 -8
- data/lib/rigor/analysis/runner.rb +151 -28
- data/lib/rigor/cache/file_digest.rb +20 -2
- data/lib/rigor/cli/check_command.rb +67 -48
- data/lib/rigor/cli/coverage_command.rb +5 -6
- data/lib/rigor/cli/doc_links.rb +100 -0
- data/lib/rigor/cli/docs_command.rb +32 -2
- data/lib/rigor/cli/effects_command.rb +27 -2
- data/lib/rigor/cli/effects_diff_renderer.rb +82 -12
- data/lib/rigor/cli/effects_explain_renderer.rb +6 -3
- data/lib/rigor/cli/effects_snapshot_command.rb +52 -7
- data/lib/rigor/cli/unused_command.rb +65 -16
- data/lib/rigor/cli.rb +21 -8
- data/lib/rigor/effects/definition_lines.rb +100 -0
- data/lib/rigor/effects/envelope_check.rb +18 -1
- data/lib/rigor/effects/liskov_check.rb +17 -8
- data/lib/rigor/effects/signature_sources.rb +13 -2
- data/lib/rigor/effects/snapshot.rb +53 -21
- data/lib/rigor/effects/snapshot_diff.rb +26 -3
- data/lib/rigor/inference/synthetic_method_scanner.rb +7 -0
- data/lib/rigor/rbs_extended/envelope_scanner.rb +9 -0
- data/lib/rigor/version.rb +1 -1
- data/skills/rigor-ci-setup/SKILL.md +2 -2
- data/skills/rigor-editor-setup/SKILL.md +2 -2
- data/skills/rigor-mcp-setup/SKILL.md +2 -2
- data/skills/rigor-monkeypatch-resolve/SKILL.md +1 -1
- data/skills/rigor-plugin-review/SKILL.md +3 -3
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b2709c0d68fc616109c3300013f08fda3702484c6ca01ff67b4ccf9d0f354ad0
|
|
4
|
+
data.tar.gz: 77486c048ef9cd94a725b5ebb09ae4e161003b37f67919c769845ed36f6c60bf
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 15a406b49bcd62e9efc2c1c143903a85157dbaf165ccca5defdf4abd69949d0c0d0107afd55469e5a0b27fa667e3c59a0ec58de854af503cad0a9e3952568a6e
|
|
7
|
+
data.tar.gz: fa429f47f4ed13b53fa07c7078510524bd6d3b304f7ffa48916b3cfab0e954b42167bda011b413946448dbdaeb63e78dfeaf444ec86a68cf928cefcf3fd70813
|
data/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Rigor
|
|
2
2
|
|
|
3
3
|
[](https://badge.fury.io/rb/rigortype)
|
|
4
|
-
[](
|
|
4
|
+
[](LICENSE)
|
|
5
5
|
[](https://deepwiki.com/rigortype/rigor)
|
|
6
6
|
|
|
7
7
|
**Type-aware bug finding for Ruby — no annotations required, and a
|
|
@@ -47,7 +47,7 @@ s.lenght
|
|
|
47
47
|
|
|
48
48
|
```shell-session
|
|
49
49
|
$ rigor check demo.rb
|
|
50
|
-
demo.rb:7:3: error: undefined method `lenght' for "hello-world"
|
|
50
|
+
demo.rb:7:3: error: undefined method `lenght' for "hello-world" [call.undefined-method]
|
|
51
51
|
```
|
|
52
52
|
|
|
53
53
|
Note what the error says: not ``undefined method `lenght' for String``
|
|
@@ -185,9 +185,9 @@ plugins:
|
|
|
185
185
|
|
|
186
186
|
The full catalogue is in [`plugins/README.md`](plugins/README.md); the
|
|
187
187
|
`rigor-project-init` Skill picks the right set for you. To teach Rigor
|
|
188
|
-
your own DSL, the [`rigor-plugin-author`](skills/rigor-plugin-author
|
|
188
|
+
your own DSL, the [`rigor-plugin-author`](skills/rigor-plugin-author)
|
|
189
189
|
Skill authors a plugin step-by-step, and
|
|
190
|
-
[`rigor-baseline-reduce`](skills/rigor-baseline-reduce
|
|
190
|
+
[`rigor-baseline-reduce`](skills/rigor-baseline-reduce) drives the
|
|
191
191
|
baseline down once you are running.
|
|
192
192
|
|
|
193
193
|
## Going deeper
|
|
@@ -231,7 +231,7 @@ rigor docs --list # list every bundled page
|
|
|
231
231
|
|
|
232
232
|
## Status
|
|
233
233
|
|
|
234
|
-
Current release: **`v0.3.
|
|
234
|
+
Current release: **`v0.3.6`** (2026-08-30) — on the
|
|
235
235
|
`0.3.x` line, continuing the evaluation era opened by `v0.2.0`, the first
|
|
236
236
|
publicly-announced (general / evaluation) release. The line publishes
|
|
237
237
|
an enumerated [compatibility surface](docs/compatibility.md) as a
|
|
@@ -65,6 +65,9 @@ class Object
|
|
|
65
65
|
# `acts_like?(:string)` / `acts_like?(:date)` / `acts_like?(:time)`
|
|
66
66
|
# is ActiveSupport's "duck-typing helper" predicate.
|
|
67
67
|
def acts_like?: (Symbol | String) -> bool
|
|
68
|
+
|
|
69
|
+
# `core_ext/object/inclusion` — `x.in?([a, b])` / `x.in?(1..10)`.
|
|
70
|
+
def in?: (untyped) -> bool
|
|
68
71
|
end
|
|
69
72
|
|
|
70
73
|
# `nil.blank?` / `nil.present?` / `nil.try` are the most frequent
|
|
@@ -108,6 +111,9 @@ class String
|
|
|
108
111
|
def pluralize: (?Integer count, ?Symbol locale) -> String
|
|
109
112
|
def singularize: (?Symbol locale) -> String
|
|
110
113
|
def humanize: (?capitalize: bool, ?keep_id_suffix: bool) -> String
|
|
114
|
+
def titlecase: () -> String
|
|
115
|
+
def dasherize: () -> String
|
|
116
|
+
def upcase_first: () -> String
|
|
111
117
|
|
|
112
118
|
# `core_ext/string/filters`
|
|
113
119
|
def squish: () -> String
|
|
@@ -115,6 +121,8 @@ class String
|
|
|
115
121
|
def truncate: (Integer truncate_at, ?omission: String, ?separator: String | Regexp | nil) -> String
|
|
116
122
|
def truncate_bytes: (Integer truncate_at, ?omission: String) -> String
|
|
117
123
|
def truncate_words: (Integer words_count, ?omission: String, ?separator: String | Regexp | nil) -> String
|
|
124
|
+
def remove: (*Regexp | String patterns) -> String
|
|
125
|
+
def remove!: (*Regexp | String patterns) -> String
|
|
118
126
|
|
|
119
127
|
# `core_ext/string/output_safety` — `html_safe` returns an
|
|
120
128
|
# `ActiveSupport::SafeBuffer` (a String subclass). The closest
|
|
@@ -282,6 +290,8 @@ class Time
|
|
|
282
290
|
# Re-declaring it raised `RBS::DuplicatedMethodDefinitionError` and took the WHOLE `Time` definition
|
|
283
291
|
# down with it (every `Time` call degraded to `Dynamic[top]`). This overlay only ADDS what
|
|
284
292
|
# ActiveSupport adds.
|
|
293
|
+
def advance: (untyped options) -> Time
|
|
294
|
+
def all_day: () -> Range[Time]
|
|
285
295
|
def acts_like_time?: () -> true
|
|
286
296
|
end
|
|
287
297
|
|
|
@@ -323,6 +333,17 @@ class Date
|
|
|
323
333
|
def at_beginning_of_day: () -> Time
|
|
324
334
|
def end_of_day: () -> Time
|
|
325
335
|
def at_end_of_day: () -> Time
|
|
336
|
+
def advance: (untyped options) -> Date
|
|
337
|
+
def all_day: () -> Range[Time]
|
|
338
|
+
|
|
339
|
+
# `Date#to_time` is an OVERLOAD CONTINUATION (`| ...`), not a plain declaration: stdlib `date`
|
|
340
|
+
# already types `to_time: () -> Time`, and re-declaring it raises
|
|
341
|
+
# `RBS::DuplicatedMethodDefinitionError`, which collapses `Date` to `Dynamic[top]` for every project
|
|
342
|
+
# this overlay reaches — the failure the `Time#utc?` note above records, and the one #437 hit on the
|
|
343
|
+
# plugin. The row cannot simply be omitted either: ActiveSupport genuinely widens the arity, so
|
|
344
|
+
# without it `date.to_time(:utc)` — correct Rails code — draws an arity diagnostic (#449). `| ...`
|
|
345
|
+
# appends this overload ahead of the stdlib one, so both arities resolve.
|
|
346
|
+
def to_time: (?Symbol form) -> Time | ...
|
|
326
347
|
end
|
|
327
348
|
|
|
328
349
|
# ---------------------------------------------------------------
|
|
@@ -475,3 +496,15 @@ class DateTime
|
|
|
475
496
|
def end_of_minute: () -> DateTime
|
|
476
497
|
def acts_like_time?: () -> true
|
|
477
498
|
end
|
|
499
|
+
|
|
500
|
+
# ---------------------------------------------------------------
|
|
501
|
+
# ERB::Util — ActionView extends it with `html_escape_once`
|
|
502
|
+
# ---------------------------------------------------------------
|
|
503
|
+
|
|
504
|
+
# `ERB` is a CLASS in Ruby / RBS (not a module), so reopen it as a class — a `module ERB` wrapper
|
|
505
|
+
# collides with upstream `class ERB` once the `erb` stdlib is in scope. `ERB::Util` itself is a module.
|
|
506
|
+
class ERB
|
|
507
|
+
module Util
|
|
508
|
+
def self.html_escape_once: (untyped) -> String
|
|
509
|
+
end
|
|
510
|
+
end
|
|
@@ -113,7 +113,7 @@ String, Symbol, Array, and Hash. The list is not in this
|
|
|
113
113
|
handbook (it would fill several pages); see
|
|
114
114
|
[`docs/types.md`](../types.md) and the per-class catalogues
|
|
115
115
|
under
|
|
116
|
-
[`data/builtins/ruby_core/`](../../data/builtins/ruby_core
|
|
116
|
+
[`data/builtins/ruby_core/`](../../data/builtins/ruby_core).
|
|
117
117
|
|
|
118
118
|
When folding is **not** safe (because a method has side
|
|
119
119
|
effects, depends on the environment, or is not in a
|
|
@@ -275,7 +275,7 @@ the underlying RBS sig advertises.
|
|
|
275
275
|
|
|
276
276
|
If you prefer the TS spellings (`Pick<T, K>` etc.) in
|
|
277
277
|
directives, opt into the
|
|
278
|
-
[`rigor-typescript-utility-types`](../../plugins/rigor-typescript-utility-types
|
|
278
|
+
[`rigor-typescript-utility-types`](../../plugins/rigor-typescript-utility-types)
|
|
279
279
|
plugin. The plugin registers a `Plugin::TypeNodeResolver` that
|
|
280
280
|
translates each TS name onto the canonical projection:
|
|
281
281
|
|
|
@@ -236,6 +236,6 @@ deprecations, …). Most projects will never write one; the
|
|
|
236
236
|
chapter exists so you know the option is there.
|
|
237
237
|
[Chapter 10 — Coexisting with Sorbet](10-sorbet.md) is for
|
|
238
238
|
projects arriving from a Sorbet codebase: the
|
|
239
|
-
[`rigor-sorbet`](../../plugins/rigor-sorbet
|
|
239
|
+
[`rigor-sorbet`](../../plugins/rigor-sorbet) adapter reads
|
|
240
240
|
`sig { ... }` blocks, RBI files, and `T.let` / `T.cast` /
|
|
241
241
|
`T.must` / `T.unsafe` assertions as type sources.
|
data/docs/handbook/09-plugins.md
CHANGED
|
@@ -122,7 +122,7 @@ Reach for a plugin only when:
|
|
|
122
122
|
|
|
123
123
|
If those are true, [`examples/README.md`](../../examples/README.md)
|
|
124
124
|
is your starting point. The
|
|
125
|
-
[`rigor-deprecations`](../../examples/rigor-deprecations
|
|
125
|
+
[`rigor-deprecations`](../../examples/rigor-deprecations)
|
|
126
126
|
example is the smallest fully-shaped plugin — manifest +
|
|
127
127
|
single per-file walk + a couple of diagnostic emissions —
|
|
128
128
|
and is the recommended template for "I want to author my
|
|
@@ -146,7 +146,7 @@ From here:
|
|
|
146
146
|
to deeper material in
|
|
147
147
|
[`docs/type-specification/`](../type-specification/README.md),
|
|
148
148
|
[`docs/internal-spec/`](../internal-spec/README.md), and
|
|
149
|
-
[`docs/adr/`](../adr
|
|
149
|
+
[`docs/adr/`](../adr).
|
|
150
150
|
- The [`CHANGELOG.md`](../../CHANGELOG.md) is the per-release
|
|
151
151
|
truth for what shipped when.
|
|
152
152
|
|
data/docs/handbook/10-sorbet.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Coexisting with Sorbet
|
|
2
2
|
|
|
3
3
|
If your project already uses [Sorbet](https://sorbet.org/),
|
|
4
|
-
the [`rigor-sorbet`](../../plugins/rigor-sorbet
|
|
4
|
+
the [`rigor-sorbet`](../../plugins/rigor-sorbet) plugin
|
|
5
5
|
lets Rigor read your existing `sig` blocks, RBI files, and
|
|
6
6
|
`T.let` / `T.cast` / `T.must` / `T.unsafe` assertions as type
|
|
7
7
|
sources. You do not have to rewrite anything in RBS to start
|
data/docs/handbook/README.md
CHANGED
|
@@ -66,7 +66,7 @@ up the flag, key, or command that *acts* on it.
|
|
|
66
66
|
landing page.
|
|
67
67
|
10. [**Coexisting with Sorbet**](10-sorbet.md) — for users
|
|
68
68
|
arriving from a Sorbet-using project: the
|
|
69
|
-
[`rigor-sorbet`](../../plugins/rigor-sorbet
|
|
69
|
+
[`rigor-sorbet`](../../plugins/rigor-sorbet) adapter
|
|
70
70
|
reads `sig { ... }` blocks, RBI files, and
|
|
71
71
|
`T.let` / `T.cast` / `T.must` / `T.unsafe` assertions
|
|
72
72
|
as type sources without rewriting in RBS.
|
|
@@ -255,7 +255,7 @@ ADRs:
|
|
|
255
255
|
- [`docs/internal-spec/`](../internal-spec/README.md) —
|
|
256
256
|
analyzer-internal contracts (engine surface, type-object
|
|
257
257
|
public API).
|
|
258
|
-
- [`docs/adr/`](../adr
|
|
258
|
+
- [`docs/adr/`](../adr) — architecture decision records.
|
|
259
259
|
|
|
260
260
|
## Non-goals
|
|
261
261
|
|
|
@@ -105,7 +105,7 @@ type switches. Rigor has direct analogues.
|
|
|
105
105
|
| `if x != nil` | `if x` (strips `nil`), or `unless x.nil?` |
|
|
106
106
|
| `v, ok := x.(string)` (comma-ok assertion) | `x.is_a?(String)` narrowing in an `if` |
|
|
107
107
|
| `switch v := x.(type) { case string: … }` | `case x; in String => v` |
|
|
108
|
-
| `x.(T)` (assertion, panics on fail) | (no panicking assertion) — `is_a?` guard, or `T.cast` via [`rigor-sorbet`](../../plugins/rigor-sorbet
|
|
108
|
+
| `x.(T)` (assertion, panics on fail) | (no panicking assertion) — `is_a?` guard, or `T.cast` via [`rigor-sorbet`](../../plugins/rigor-sorbet) |
|
|
109
109
|
| user func returning `bool` | `%a{rigor:v1:predicate-if-true x is Foo}` directive |
|
|
110
110
|
|
|
111
111
|
Go's type switch is *not* exhaustive — you can omit cases and
|
|
@@ -58,7 +58,7 @@ advisor that only speaks when it is sure.
|
|
|
58
58
|
| `Set<T>` | `HashSet<T>` / `ISet<T>` | `Set[T]` | |
|
|
59
59
|
| `record Point(int x, int y)` | `record Point(int X, int Y)` | `Point = Data.define(:x, :y)` | See [Records ↔ Data.define](#records--datadefine). |
|
|
60
60
|
| `Optional<T>` | `T?` (nullable reference type) | `T?` (i.e. `T \| nil`) | Java models it as a *container*; C# as a *type modifier*. See [Nullability](#nullability). |
|
|
61
|
-
| `enum Color { RED, GREEN }` | `enum Color { Red, Green }` | `Constant<:red> \| Constant<:green>` (Symbol union) | Ruby has no native enum; the [`rigor-mangrove`](../../plugins
|
|
61
|
+
| `enum Color { RED, GREEN }` | `enum Color { Red, Green }` | `Constant<:red> \| Constant<:green>` (Symbol union) | Ruby has no native enum; the [`rigor-mangrove`](../../plugins) plugin types richer enum DSLs. |
|
|
62
62
|
| `sealed interface Shape permits …` | `abstract` base + sealed hierarchy | union of the subtypes | See [Sealed types & exhaustiveness](#sealed-types-and-exhaustiveness). |
|
|
63
63
|
| `<T>` (generic) | `<T>` (generic) | RBS `[T]` type parameter | |
|
|
64
64
|
| `? extends T` (use-site) | `out T` (declaration-site) | covariant type parameter | See [Generics & variance](#generics-and-variance). |
|
|
@@ -124,7 +124,7 @@ behaviour matches.
|
|
|
124
124
|
| `x instanceof String` | `x is string` | `x.is_a?(String)` |
|
|
125
125
|
| `x instanceof String s` (binding) | `x is string s` (binding) | `case x; in String => s` |
|
|
126
126
|
| `switch (x) { case Foo f -> … }` | `switch (x) { case Foo f => … }` | `case x; in Foo => f` |
|
|
127
|
-
| `(Foo) x` (cast) | `(Foo)x` (cast) | (no in-source cast) — `is_a?` guard, or `T.cast` via [`rigor-sorbet`](../../plugins/rigor-sorbet
|
|
127
|
+
| `(Foo) x` (cast) | `(Foo)x` (cast) | (no in-source cast) — `is_a?` guard, or `T.cast` via [`rigor-sorbet`](../../plugins/rigor-sorbet) |
|
|
128
128
|
| `Objects.requireNonNull(x)` | `x!` (null-forgiving) | (no in-source assertion) — `unless x.nil?`, or `T.must` via `rigor-sorbet` |
|
|
129
129
|
| user method returning `boolean` | user method returning `bool` | `%a{rigor:v1:predicate-if-true x is Foo}` directive on the predicate |
|
|
130
130
|
|
|
@@ -296,7 +296,7 @@ draws the distinction in full.
|
|
|
296
296
|
Pyright's "type alias narrowing" and mypy's overload stacks
|
|
297
297
|
cover some cases; Rigor's plugin contract gives you full
|
|
298
298
|
Ruby code at the dispatch point. The
|
|
299
|
-
[`rigor-lisp-eval`](../../examples/rigor-lisp-eval
|
|
299
|
+
[`rigor-lisp-eval`](../../examples/rigor-lisp-eval) example
|
|
300
300
|
is the canonical demo — `Lisp.eval([:+, 1, 2])` returns
|
|
301
301
|
`Integer`, `Lisp.eval([:<, 1, 2])` returns `bool`.
|
|
302
302
|
|
|
@@ -228,8 +228,8 @@ author):
|
|
|
228
228
|
- The `missing-protocol-method` / `protocol-return-mismatch`
|
|
229
229
|
diagnostics are **plugin diagnostics**, emitted under the
|
|
230
230
|
plugin's `plugin.<id>.` provenance — not core Rigor rules. The
|
|
231
|
-
worked references are [`examples/rigor-web/`](../../examples/rigor-web
|
|
232
|
-
(the minimal tutorial) and [`plugins/rigor-hanami/`](../../plugins/rigor-hanami
|
|
231
|
+
worked references are [`examples/rigor-web/`](../../examples/rigor-web)
|
|
232
|
+
(the minimal tutorial) and [`plugins/rigor-hanami/`](../../plugins/rigor-hanami)
|
|
233
233
|
(production Hanami 2 actions).
|
|
234
234
|
|
|
235
235
|
## Interface vs protocol contract
|
|
@@ -91,7 +91,7 @@ The reflex to drop is `unwrap()`. In Rust you reach for
|
|
|
91
91
|
`.unwrap()` / `.expect()` when you *know* it is `Some`. Rigor
|
|
92
92
|
has no in-source assertion that lies to the checker; the
|
|
93
93
|
equivalents are a `nil?` guard (checked, not asserted) or
|
|
94
|
-
`T.must` via the [`rigor-sorbet`](../../plugins/rigor-sorbet
|
|
94
|
+
`T.must` via the [`rigor-sorbet`](../../plugins/rigor-sorbet)
|
|
95
95
|
plugin (see [Chapter 10](10-sorbet.md)).
|
|
96
96
|
|
|
97
97
|
**`Result<T, E>` ↔ exceptions.** Here the models diverge. Ruby
|
|
@@ -709,7 +709,7 @@ Rigor maps onto this as:
|
|
|
709
709
|
| Dynamic type `?` | **`Dynamic[T]`** — a carrier that *wraps* a "best-guess" type `T` while marking the value as not-statically-verified. `Dynamic[top]` is the maximally-dynamic form. |
|
|
710
710
|
| Consistency `~` | The `maybe` arm of the trinary certainty — `Dynamic[T] ~ U` holds whenever `T ~ U` does. |
|
|
711
711
|
| Static/dynamic boundary | Per-method, per-file, per-plugin contribution — Rigor records *why* a value became `Dynamic[T]` in its dynamic-origin algebra. |
|
|
712
|
-
| Casts | No in-source cast operator. The opt-in [`rigor-sorbet`](../../plugins/rigor-sorbet
|
|
712
|
+
| Casts | No in-source cast operator. The opt-in [`rigor-sorbet`](../../plugins/rigor-sorbet) plugin reads `T.let` / `T.cast` / `T.must` as cast forms; `RBS::Extended` `assert_type` directives serve the same role from `.rbs`. |
|
|
713
713
|
|
|
714
714
|
Two Rigor-specific extensions matter:
|
|
715
715
|
|
|
@@ -794,7 +794,7 @@ against:
|
|
|
794
794
|
### What Rigor explicitly does NOT do
|
|
795
795
|
|
|
796
796
|
- **Runtime contract insertion at the static / dynamic boundary.**
|
|
797
|
-
The opt-in [`rigor-sorbet`](../../plugins/rigor-sorbet
|
|
797
|
+
The opt-in [`rigor-sorbet`](../../plugins/rigor-sorbet) plugin
|
|
798
798
|
reads Sorbet's `T.let` / `T.cast` / `T.must` as cast forms, but
|
|
799
799
|
the contract *enforcement* is `sorbet-runtime`'s job, not
|
|
800
800
|
Rigor's. Rigor's static analysis uses the cast as a hint, not
|
|
@@ -51,7 +51,7 @@ inference cannot see further).
|
|
|
51
51
|
| `{ name: string; age: number }` | `HashShape{name: String, age: Integer}` | Same per-key model; Ruby uses Symbol keys idiomatically. |
|
|
52
52
|
| `Array<T>` / `T[]` | `Array[T]` | Same. |
|
|
53
53
|
| `Record<K, V>` | `Hash[K, V]` | Same. |
|
|
54
|
-
| `Readonly<T>` | `readonly_of[T]` (via opt-in [`rigor-typescript-utility-types`](../../plugins/rigor-typescript-utility-types
|
|
54
|
+
| `Readonly<T>` | `readonly_of[T]` (via opt-in [`rigor-typescript-utility-types`](../../plugins/rigor-typescript-utility-types) plugin) | View-level read-only marker on every entry of a `HashShape`. Does NOT prove the underlying object is frozen — ADR-13 § "Readonly". |
|
|
55
55
|
| `Partial<T>` / `Required<T>` | `partial_of[T]` / `required_of[T]` (same plugin) | Flips every entry's required-ness on a `HashShape`. `Partial` does NOT widen value types to `nil` — Rigor's `HashShape` distinguishes "key absent" from "key present with nil value" (ADR-13 WD on required-ness flips). |
|
|
56
56
|
| `Pick<T, K>` / `Omit<T, K>` | `pick_of[T, K]` / `omit_of[T, K]` (same plugin) | Restrict / remove `HashShape` entries by literal-key union; Tuple receivers project by integer index. Non-shape carriers degrade conservatively and surface `dynamic.shape.lossy-projection`. |
|
|
57
57
|
| Conditional types `T extends U ? A : B` | (none in core; plugin contributions) | A plugin can vary return type by argument shape. |
|
|
@@ -159,7 +159,7 @@ inferred call-site instantiation as routinely as TypeScript.
|
|
|
159
159
|
| `Array<T>` | `Array[T]` |
|
|
160
160
|
| `Map<K, V>` | `Hash[K, V]` |
|
|
161
161
|
| `Promise<T>` | (no analogue — Ruby has no built-in Promise) |
|
|
162
|
-
| `Pick<T, K>` / `Omit<T, K>` / `Partial<T>` / `Required<T>` / `Readonly<T>` | Opt-in [`rigor-typescript-utility-types`](../../plugins/rigor-typescript-utility-types
|
|
162
|
+
| `Pick<T, K>` / `Omit<T, K>` / `Partial<T>` / `Required<T>` / `Readonly<T>` | Opt-in [`rigor-typescript-utility-types`](../../plugins/rigor-typescript-utility-types) plugin maps each onto `pick_of` / `omit_of` / `partial_of` / `required_of` / `readonly_of` over `HashShape` (and `pick_of` / `omit_of` over `Tuple`). |
|
|
163
163
|
| Conditional types | (no analogue — would need a plugin) |
|
|
164
164
|
|
|
165
165
|
Rigor reads RBS generics through its dispatcher and instantiates
|
|
@@ -214,7 +214,7 @@ Be honest about what you give up:
|
|
|
214
214
|
variation, not type-level expressions.
|
|
215
215
|
- **Mapped types.** `Pick`, `Omit`, `Partial`, `Required`, and
|
|
216
216
|
`Readonly` ship as opt-in plugin-supplied vocabulary via
|
|
217
|
-
[`rigor-typescript-utility-types`](../../plugins/rigor-typescript-utility-types
|
|
217
|
+
[`rigor-typescript-utility-types`](../../plugins/rigor-typescript-utility-types),
|
|
218
218
|
which maps them onto the Rigor-canonical `pick_of` / `omit_of`
|
|
219
219
|
/ `partial_of` / `required_of` / `readonly_of` shape-projection
|
|
220
220
|
type functions on `HashShape` (and `pick_of` / `omit_of` on
|
|
@@ -294,7 +294,7 @@ def pick: [K, V] (Hash[K, V] obj, Array[K] keys) -> Hash[K, V]
|
|
|
294
294
|
|
|
295
295
|
The RBS sig stays generic. If you want `Pick<T, K>`'s exact-
|
|
296
296
|
key-set tracking back, opt into the
|
|
297
|
-
[`rigor-typescript-utility-types`](../../plugins/rigor-typescript-utility-types
|
|
297
|
+
[`rigor-typescript-utility-types`](../../plugins/rigor-typescript-utility-types)
|
|
298
298
|
plugin and annotate the return type with the `Pick` spelling:
|
|
299
299
|
|
|
300
300
|
```rbs
|
|
@@ -38,7 +38,7 @@ the `paths:` list from the configuration file.
|
|
|
38
38
|
| `--incremental` | Re-analyse only the files changed since the last run plus the files that depend on them, serving the rest from a cross-process disk snapshot (ADR-46). Diagnostics are identical to a full run; a config / gem / version change (or a file added or removed) transparently forces a full re-analysis. See [Caching](12-caching.md). |
|
|
39
39
|
| `--verify-incremental` | Acceptance gate: run the incremental analyzer against a full `--no-cache` run and assert the diagnostics are byte-identical, then exit (0 on match, 1 with the differing diagnostics on mismatch). Used in CI to guarantee `--incremental` never serves a stale result. |
|
|
40
40
|
| `--clear-cache` | Delete the cache directory before running. |
|
|
41
|
-
| `--cache-stats` | Print the on-disk cache inventory when finished. |
|
|
41
|
+
| `--cache-stats` | Print the on-disk cache inventory when finished — on stdout under `--format text`, on stderr under every other format, so machine-readable output stays a parseable document. `--clear-cache`'s and `--verify-incremental`'s notes follow the same rule. |
|
|
42
42
|
| `--[no-]stats` | Print a run summary (files, classes, memory, wall time) to stderr. Default on. |
|
|
43
43
|
| `--coverage` | Add a type-precision coverage block to the output (`coverage` object under `--format json`; a one-line summary in text mode). Off by default — it is a second precision pass over the analyzed files, the same scan [`rigor coverage`](#rigor-coverage) runs, so it is opt-in. |
|
|
44
44
|
| `--workers=N` | Dispatch analysis across `N` parallel worker processes (fork-based pool today; ADR-15). Default `0` (sequential). Applies to `--incremental` re-checks as well as full runs. |
|
|
@@ -304,8 +304,8 @@ supposed to fan out — the fan-out is the blast radius.
|
|
|
304
304
|
|
|
305
305
|
```yaml
|
|
306
306
|
# .rigor-effects.yml — generated by `rigor effects update`. Commit it; review its diff.
|
|
307
|
-
schema:
|
|
308
|
-
rigor: "0.3.
|
|
307
|
+
schema: 2
|
|
308
|
+
rigor: "0.3.5"
|
|
309
309
|
vocabulary: 1
|
|
310
310
|
config_digest: "9ec82bfc…"
|
|
311
311
|
methods:
|
|
@@ -314,7 +314,7 @@ methods:
|
|
|
314
314
|
"Reports::Nightly#perform":
|
|
315
315
|
effects: ["io.db.read"]
|
|
316
316
|
exhaustive: false
|
|
317
|
-
unresolved:
|
|
317
|
+
unresolved: 1
|
|
318
318
|
reach:
|
|
319
319
|
"OrdersController#create":
|
|
320
320
|
effects: ["io.db.read", "io.net.http", "job.enqueue"]
|
|
@@ -376,14 +376,17 @@ spelled out:
|
|
|
376
376
|
Effect drift against .rigor-effects.yml:
|
|
377
377
|
|
|
378
378
|
methods:
|
|
379
|
-
PaymentGateway#charge + io.net.http
|
|
379
|
+
PaymentGateway#charge + io.net.http (app/services/payment_gateway.rb:18)
|
|
380
380
|
|
|
381
381
|
reach:
|
|
382
|
-
OrdersController#create + io.net.http
|
|
382
|
+
OrdersController#create + io.net.http (app/controllers/orders_controller.rb:7)
|
|
383
383
|
|
|
384
|
-
Run `rigor effects
|
|
384
|
+
Run `rigor effects explain` to see what caused this, and `rigor effects update` to accept it.
|
|
385
385
|
```
|
|
386
386
|
|
|
387
|
+
Each row names where the method is defined, so the reviewer reads the report
|
|
388
|
+
rather than searching for the method.
|
|
389
|
+
|
|
387
390
|
The author runs `rigor effects explain` to see the route —
|
|
388
391
|
|
|
389
392
|
```
|
|
@@ -556,7 +559,7 @@ hand-adjudicated corpus target only **7% of the rows were genuinely
|
|
|
556
559
|
unused**; the rest were reachable by means static analysis cannot
|
|
557
560
|
see. That is why this is a separate command and never a `rigor check`
|
|
558
561
|
diagnostic — see
|
|
559
|
-
[ADR-102](
|
|
562
|
+
[ADR-102](../adr/102-unused-code-reachability-report.md).
|
|
560
563
|
|
|
561
564
|
Reachability is computed from **roots**, not by counting references,
|
|
562
565
|
so a cluster of classes that only reference each other is still
|
|
@@ -1092,9 +1095,10 @@ operational knobs read the environment instead.
|
|
|
1092
1095
|
| `RIGOR_CI_DETECT=0` | Turn off CI auto-detection — the same as `--no-ci-detect`. See [Running Rigor in CI § auto-detection](11-ci.md). |
|
|
1093
1096
|
| `RIGOR_RACTOR_WORKERS=N` | Worker count for parallel analysis. Sits between the CLI flag and the config key in precedence: `--workers=N` > `RIGOR_RACTOR_WORKERS` > `parallel.workers:` > `0` (sequential). |
|
|
1094
1097
|
| `RIGOR_POOL_BACKEND=ractor` | Opt back into the (off-by-default) Ractor worker pool instead of the active fork-based pool ([ADR-15](../adr/15-ractor-concurrency.md)). Only relevant with a non-zero worker count; the fork pool is the supported backend. |
|
|
1098
|
+
| `RIGOR_LSP_POOL_MIN_BATCH=N` | Fewest buffers an [`rigor lsp`](#rigor-lsp) batch must carry before analysis is dispatched across the worker pool rather than run in-process (default `16`). Lower it if your project's per-file analysis is expensive enough that pooling pays off sooner. |
|
|
1095
1099
|
| `RIGOR_PLUGIN_ISOLATION=none\|process\|ruby_box` | How a plugin's direct calls into its target library are isolated. Default `process`. See [Using plugins § Isolation strategy](07-plugins.md). `RIGOR_BOX` is a legacy alias for `ruby_box`. |
|
|
1096
1100
|
| `RIGOR_STRICT_VALIDATION=1` | Force full-content cache validation for one run (the same as `cache.validation: digest`, and winning over it) — re-hash every file's content instead of trusting its stat metadata. Use it if a filesystem's timestamps or inode numbers cannot be trusted. See [Caching § How a file is checked for changes](12-caching.md#how-a-file-is-checked-for-changes). |
|
|
1097
|
-
| `RIGOR_DISABLE_YJIT=1` | Opt out of Rigor's deferred YJIT enablement. Rigor turns YJIT on partway through
|
|
1101
|
+
| `RIGOR_DISABLE_YJIT=1` | Opt out of Rigor's deferred YJIT enablement. Rigor turns YJIT on partway through any long run so short runs never pay the JIT warm-up; this variable leaves it off entirely. Diagnostics and allocations are identical either way — the effect is wall-time only. |
|
|
1098
1102
|
| `RIGOR_YJIT_DEADLINE=<seconds>` | Advanced: tune how long a run must last before deferred YJIT enables (default `5.0`). Lower it if your runs are long and you want the JIT sooner; raise it to protect short runs. Ignored when `RIGOR_DISABLE_YJIT=1` is set or YJIT is unavailable. |
|
|
1099
1103
|
|
|
1100
1104
|
Three further variables (`RIGOR_BUDGET_TRACE`,
|
|
@@ -251,7 +251,7 @@ stanza it broke:
|
|
|
251
251
|
```
|
|
252
252
|
app/presenters/user_presenter.rb:14:1: warning: Method Presenters::User#render performs io.fs.read
|
|
253
253
|
(File.read), but is declared effect: [] at .rigor.yml effects.envelopes[0], so io.fs.read exceeds
|
|
254
|
-
the envelope.
|
|
254
|
+
the envelope. [effect.envelope-exceeded]
|
|
255
255
|
```
|
|
256
256
|
|
|
257
257
|
When one method is a deliberate exception, write the narrower envelope on it in RBS — nearest wins,
|
|
@@ -19,6 +19,18 @@ Every rule has a two-segment `family.rule` identifier:
|
|
|
19
19
|
| `assert` | `assert_type` checks. |
|
|
20
20
|
| `dump` | `dump_type` notices. |
|
|
21
21
|
|
|
22
|
+
Every diagnostic carries its identifier in brackets at the end of the
|
|
23
|
+
line, so the ID you need for `# rigor:disable`, for `disable:` and for
|
|
24
|
+
`severity_profile:` is the one you are already looking at:
|
|
25
|
+
|
|
26
|
+
```text
|
|
27
|
+
app/user.rb:11:3: error: undefined method `lenght' for "hello" [call.undefined-method]
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
A diagnostic no rule produced — a parse error, a path error, an
|
|
31
|
+
internal analyzer error — has nothing to suppress and carries no
|
|
32
|
+
bracket.
|
|
33
|
+
|
|
22
34
|
`rigor explain <rule>` prints the full catalogue entry for any
|
|
23
35
|
built-in rule ID; `rigor explain` with no argument lists them all.
|
|
24
36
|
|
data/docs/manual/08-skills.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Rigor bundles a set of **Agent Skills** — structured workflows an AI
|
|
4
4
|
coding agent (Claude Code and compatible tools) can run on your behalf.
|
|
5
|
-
They live in [`skills/`](../../skills
|
|
5
|
+
They live in [`skills/`](../../skills) and are auto-discovered when an
|
|
6
6
|
agent works inside a project that has Rigor available.
|
|
7
7
|
|
|
8
8
|
Skills are optional. Everything they do, you can do by hand with the
|
data/docs/manual/11-ci.md
CHANGED
|
@@ -73,7 +73,7 @@ platform reads to annotate the diff directly, instead of leaving them in
|
|
|
73
73
|
the job log ([ADR-51](../adr/51-ci-diagnostic-output-formats.md)). These
|
|
74
74
|
layer on the generic `--format json` stream; they add no new diagnostics,
|
|
75
75
|
only a platform-native rendering. Ready-to-copy template files live under
|
|
76
|
-
[`ci-templates/`](ci-templates
|
|
76
|
+
[`ci-templates/`](ci-templates).
|
|
77
77
|
|
|
78
78
|
### GitHub — inline annotations (the default)
|
|
79
79
|
|
data/docs/manual/12-caching.md
CHANGED
|
@@ -137,7 +137,7 @@ CI detection that `auto` relies on.
|
|
|
137
137
|
| --- | --- |
|
|
138
138
|
| `rigor check --no-cache` | Run without reading or writing the persistent cache. |
|
|
139
139
|
| `rigor check --clear-cache` | Delete the cache directory, then run. |
|
|
140
|
-
| `rigor check --cache-stats` | Print the on-disk cache inventory when the run finishes. |
|
|
140
|
+
| `rigor check --cache-stats` | Print the on-disk cache inventory when the run finishes (on stderr under every `--format` but `text`, so the document on stdout stays parseable). |
|
|
141
141
|
| `rigor check --incremental` | Re-analyse only what changed; serve the rest from the incremental snapshot (see below). |
|
|
142
142
|
|
|
143
143
|
There is no config key to disable caching permanently — the
|
|
@@ -333,7 +333,7 @@ ones a type actually catches.
|
|
|
333
333
|
> [Configuration](03-configuration.md)). A project whose gems live in
|
|
334
334
|
> the active Ruby's default gem home — the common `rbenv` / `mise`
|
|
335
335
|
> case with no `--path` set — is invisible to the isolated analyzer by
|
|
336
|
-
> design ([ADR-27](
|
|
336
|
+
> design ([ADR-27](../adr/27-tool-distribution-model.md)):
|
|
337
337
|
> point Rigor at it with `bundler.bundle_path:`. Until you do, these
|
|
338
338
|
> holes keep the generic `engine_gap` cause instead of `add_rbs` —
|
|
339
339
|
> the label is missing, never wrong.
|
|
@@ -140,7 +140,7 @@ Four sections, needing four different decisions:
|
|
|
140
140
|
|
|
141
141
|
| Section | What it means | What to do |
|
|
142
142
|
| --- | --- | --- |
|
|
143
|
-
| **Reachable only from test code** | Live test, no production caller | Work these first |
|
|
143
|
+
| **Reachable only from test code** | Live test, no production caller Rigor can see or suspect | Work these first |
|
|
144
144
|
| **Candidates** | Nothing reachable names it | Adjudicate — most are still live |
|
|
145
145
|
| **Cannot decide** | Something can name it at runtime | Read the reason; do not delete from here |
|
|
146
146
|
| **Namespace-only** | A module wrapping live code | Excluded from candidates; count only |
|
|
@@ -209,14 +209,23 @@ skip most of a list quickly:
|
|
|
209
209
|
|
|
210
210
|
### Cannot decide: read the reason, do not delete
|
|
211
211
|
|
|
212
|
-
These were demoted
|
|
213
|
-
|
|
212
|
+
These were demoted because something can name the class at runtime —
|
|
213
|
+
out of `candidates`, or out of **reachable only from test code**. Each
|
|
214
|
+
row says what:
|
|
214
215
|
|
|
215
216
|
```
|
|
216
217
|
1 Handlers::Alpha lib/handlers.rb:2
|
|
217
218
|
constantize on an interpolated string (lib/dispatch.rb:14)
|
|
218
219
|
```
|
|
219
220
|
|
|
221
|
+
A row demoted out of the test-only section is the second kind, and it
|
|
222
|
+
is the one worth knowing about: a class your specs reference and a
|
|
223
|
+
data file also names — a job in `config/recurring.yml`, a class named
|
|
224
|
+
from a YAML setting — is not a dead production path, because the
|
|
225
|
+
configuration may well be what drives it. The test-only section makes
|
|
226
|
+
a claim about production, so a row Rigor holds evidence against
|
|
227
|
+
belongs here instead, with the file named.
|
|
228
|
+
|
|
220
229
|
`"Foo".constantize` names `Foo` exactly, so it counts as an ordinary
|
|
221
230
|
reference and never reaches this section. `"Foo::#{key}".constantize`
|
|
222
231
|
can only bound the namespace, so everything under `Foo` is demoted. A
|
|
@@ -322,5 +331,5 @@ Running it as a **reporting** job is fine, and is the intended way to
|
|
|
322
331
|
notice the drift described above: run it on a schedule, publish the
|
|
323
332
|
JSON, and let a person read the trend.
|
|
324
333
|
|
|
325
|
-
[adr-102]:
|
|
334
|
+
[adr-102]: ../adr/102-unused-code-reachability-report.md
|
|
326
335
|
[issue-363]: https://github.com/rigortype/rigor/issues/363
|
|
@@ -313,8 +313,8 @@ digest of your `effects:` block, so an upgrade or a policy edit shows up as a
|
|
|
313
313
|
|
|
314
314
|
```yaml
|
|
315
315
|
# .rigor-effects.yml — generated by `rigor effects update`. Commit it; review its diff.
|
|
316
|
-
schema:
|
|
317
|
-
rigor: "0.3.
|
|
316
|
+
schema: 2
|
|
317
|
+
rigor: "0.3.5"
|
|
318
318
|
vocabulary: 1
|
|
319
319
|
config_digest: "44136fa355b3678a1146ad16f7e8649e94fb4fc21fe77e8310c060f61caaff8a"
|
|
320
320
|
methods:
|
|
@@ -324,10 +324,12 @@ methods:
|
|
|
324
324
|
|
|
325
325
|
Two notes on reading the file:
|
|
326
326
|
|
|
327
|
-
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
327
|
+
- **`unresolved:` is a count, not a list.** It says how many calls the analyzer
|
|
328
|
+
could not follow, which is why `exhaustive:` is false. The causes themselves
|
|
329
|
+
are not recorded — they are inference-quality metadata that churns on Rigor
|
|
330
|
+
upgrades and on unrelated edits — so ask for them when you need them:
|
|
331
|
+
`rigor effects explain` names them, including for an `exhaustive → not` row.
|
|
332
|
+
The lines a reviewer reads are `effects:` and `declared:`.
|
|
331
333
|
- Trivial and synthesised entries are left out, as in the report. `--full`
|
|
332
334
|
records everything, and produces a much larger and much noisier file.
|
|
333
335
|
|
|
@@ -398,11 +400,11 @@ CI runs `rigor effects check` and fails, exit 1:
|
|
|
398
400
|
Effect drift against .rigor-effects.yml:
|
|
399
401
|
|
|
400
402
|
methods:
|
|
401
|
-
Change#init_path + io.fs.write
|
|
402
|
-
Change#init_path + nondet.time
|
|
403
|
-
Change#init_path exhaustive → not
|
|
403
|
+
Change#init_path + io.fs.write (app/models/change.rb:41)
|
|
404
|
+
Change#init_path + nondet.time (app/models/change.rb:41)
|
|
405
|
+
Change#init_path exhaustive → not (app/models/change.rb:41)
|
|
404
406
|
|
|
405
|
-
Run `rigor effects
|
|
407
|
+
Run `rigor effects explain` to see what caused this, and `rigor effects update` to accept it.
|
|
406
408
|
```
|
|
407
409
|
|
|
408
410
|
`+ label` and `- label` are the proven lane; `≤+` / `≤-` the declared one;
|
|
@@ -410,6 +412,11 @@ Run `rigor effects update` and commit the result if this change is intended.
|
|
|
410
412
|
someone introduced a call Rigor cannot follow; `+symbol` / `-symbol` are methods
|
|
411
413
|
that appeared or vanished, and a rename is one of each.
|
|
412
414
|
|
|
415
|
+
The parenthetical is where the method is defined — the file, and the `def`'s own
|
|
416
|
+
line when the file has one. A method defined by a reopening spans several files
|
|
417
|
+
and the row names them all; a `-symbol` row carries no position, because a method
|
|
418
|
+
this run no longer sees is one it cannot locate.
|
|
419
|
+
|
|
413
420
|
Before regenerating, ask why:
|
|
414
421
|
|
|
415
422
|
```sh
|
|
@@ -518,7 +525,7 @@ A method that exceeds its bound gets one diagnostic per (method, label) pair, at
|
|
|
518
525
|
its `def`, naming the route:
|
|
519
526
|
|
|
520
527
|
```
|
|
521
|
-
app/helpers/application_helper.rb:59:1: warning: Method ApplicationHelper#link_to_principal performs io.fs.read (Dir.glob via IconsHelper#principal_icon → IconsHelper#sprite_icon → IconsHelper#sprite_source → Redmine::Themes::Helper#current_theme → Redmine::Themes.theme → Redmine::Themes.themes → Redmine::Themes.scan_themes), but is declared effect: [] at .rigor.yml effects.envelopes[0], so io.fs.read exceeds the envelope.
|
|
528
|
+
app/helpers/application_helper.rb:59:1: warning: Method ApplicationHelper#link_to_principal performs io.fs.read (Dir.glob via IconsHelper#principal_icon → IconsHelper#sprite_icon → IconsHelper#sprite_source → Redmine::Themes::Helper#current_theme → Redmine::Themes.theme → Redmine::Themes.themes → Redmine::Themes.scan_themes), but is declared effect: [] at .rigor.yml effects.envelopes[0], so io.fs.read exceeds the envelope. [effect.envelope-exceeded]
|
|
522
529
|
```
|
|
523
530
|
|
|
524
531
|
**Budget for a big first number.** That one stanza, on Redmine, is **343
|
|
@@ -608,7 +615,7 @@ app/serializers/rest/v1/instance_serializer.rb:89:1: warning: Method
|
|
|
608
615
|
REST::V1::InstanceSerializer#invites_enabled performs mutate.self
|
|
609
616
|
(receiver-mutation via UserRole.everyone → UserRole.create! → UserRole#set_position),
|
|
610
617
|
but is declared effect: [] at .rigor.yml effects.envelopes[0], so mutate.self
|
|
611
|
-
exceeds the envelope.
|
|
618
|
+
exceeds the envelope. [effect.envelope-exceeded]
|
|
612
619
|
```
|
|
613
620
|
|
|
614
621
|
Rigor walked *through* the database write and reported the ivar assignment beyond
|
data/docs/manual/README.md
CHANGED
|
@@ -80,7 +80,7 @@ flag, key, or command that *acts* on it.
|
|
|
80
80
|
Cline, …) via `rigor mcp`.
|
|
81
81
|
11. [Running Rigor in CI](11-ci.md) — a clean CI job, inline
|
|
82
82
|
PR/MR diagnostics (SARIF / GitHub Actions / GitLab Code
|
|
83
|
-
Quality), copy-paste [templates](ci-templates
|
|
83
|
+
Quality), copy-paste [templates](ci-templates), and
|
|
84
84
|
version pinning.
|
|
85
85
|
12. [Caching](12-caching.md) — where the cache lives, what
|
|
86
86
|
invalidates it, and how to clear it.
|
|
@@ -94,4 +94,4 @@ flag, key, or command that *acts* on it.
|
|
|
94
94
|
- [`docs/types.md`](../types.md) — one-page type-system guide.
|
|
95
95
|
- [`docs/type-specification/`](../type-specification/README.md)
|
|
96
96
|
— the normative spec corpus.
|
|
97
|
-
- [`docs/adr/`](../adr
|
|
97
|
+
- [`docs/adr/`](../adr) — architecture decision records.
|
|
@@ -17,10 +17,10 @@ plugins:
|
|
|
17
17
|
## What it checks
|
|
18
18
|
|
|
19
19
|
```text
|
|
20
|
-
demo.rb:7:1:
|
|
21
|
-
errors_demo.rb:
|
|
22
|
-
errors_demo.rb:
|
|
23
|
-
app/mailers/user_mailer.rb:
|
|
20
|
+
demo.rb:7:1: info: `UserMailer.welcome` matches mailer action (arity 1..2) [plugin.actionmailer.mailer-call]
|
|
21
|
+
errors_demo.rb:10:1: error: `UserMailer.welcome` expects 1..2 argument(s), got 0 [plugin.actionmailer.wrong-arity]
|
|
22
|
+
errors_demo.rb:18:1: error: `UserMailer.does_not_exist` is not a defined mailer action (known actions: digest, reset_password, welcome) [plugin.actionmailer.unknown-action]
|
|
23
|
+
app/mailers/user_mailer.rb:26:7: warning: `UserMailer#digest` has no view template under `app/views/user_mailer/` [plugin.actionmailer.missing-view]
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
1. **Action existence** — `Mailer.unknown_action(...)` →
|
|
@@ -18,9 +18,9 @@ Given a job whose `#perform` takes one required and one optional
|
|
|
18
18
|
argument (arity `1..2`):
|
|
19
19
|
|
|
20
20
|
```text
|
|
21
|
-
demo.rb:
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
demo.rb:8:1: info: `WelcomeEmailJob.perform_later` matches `#perform` (arity 1..2) [plugin.activejob.job-call]
|
|
22
|
+
errors_demo.rb:10:1: error: `WelcomeEmailJob.perform_later` expects 1..2 argument(s), got 0 [plugin.activejob.wrong-arity]
|
|
23
|
+
errors_demo.rb:14:1: error: `WelcomeEmailJob.perform_later` expects 1..2 argument(s), got 3 [plugin.activejob.wrong-arity]
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
A `*rest` parameter yields an unbounded upper bound (`arity 0+`).
|