rigortype 0.2.1 → 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 +41 -14
- 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 +1 -1
- data/lib/rigor/cli/docs_command.rb +248 -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/inference/method_dispatcher/constant_folding.rb +124 -32
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +37 -6
- data/lib/rigor/inference/scope_indexer.rb +87 -89
- data/lib/rigor/plugin/isolation.rb +5 -5
- data/lib/rigor/plugin/loader.rb +4 -2
- 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 +90 -1
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
# RBS::Extended annotations
|
|
2
|
+
|
|
3
|
+
Plain RBS can say a method returns a `String`. It cannot say it
|
|
4
|
+
returns a *non-empty* string, that a predicate narrows its
|
|
5
|
+
argument on the true branch, or that a class is meant to satisfy
|
|
6
|
+
a structural interface as a checked contract. Rigor reads that
|
|
7
|
+
extra information from **`RBS::Extended` annotations** — ordinary
|
|
8
|
+
RBS `%a{...}` annotations under a reserved `rigor:v1:` key — so
|
|
9
|
+
you can sharpen a signature without leaving RBS and without
|
|
10
|
+
breaking any other RBS tool, which preserves or ignores
|
|
11
|
+
the annotation.
|
|
12
|
+
|
|
13
|
+
You write these in your `*.rbs` files, on the method or class
|
|
14
|
+
declaration they refine:
|
|
15
|
+
|
|
16
|
+
```rbs
|
|
17
|
+
%a{rigor:v1:return: non-empty-string}
|
|
18
|
+
def read_name: () -> String
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The plain `() -> String` stays the compatibility contract; the
|
|
22
|
+
annotation tells Rigor the return is a non-empty string.
|
|
23
|
+
This page is the *operational* reference — the directives you can
|
|
24
|
+
write and their syntax. For the normative rules (conflict
|
|
25
|
+
handling, merging, provenance) see
|
|
26
|
+
[`docs/type-specification/rbs-extended.md`](../type-specification/rbs-extended.md);
|
|
27
|
+
for the type-model walkthrough see
|
|
28
|
+
[handbook chapter 7](../handbook/07-rbs-and-extended.md).
|
|
29
|
+
|
|
30
|
+
## Per-method directives
|
|
31
|
+
|
|
32
|
+
Each directive is one `%a{rigor:v1:<directive> …}` annotation
|
|
33
|
+
written immediately above the `def` it applies to. Multiple
|
|
34
|
+
directives may stack on one method; they compose independently of
|
|
35
|
+
order.
|
|
36
|
+
|
|
37
|
+
| Directive | Effect |
|
|
38
|
+
| --- | --- |
|
|
39
|
+
| `rigor:v1:return: T` | Override the RBS-declared return type with `T` at every call site. |
|
|
40
|
+
| `rigor:v1:param: name [is] T` | Tighten parameter `name` to `T` — both at overload selection / argument checks *and* inside the method body during inference. The `is` glue word is optional. |
|
|
41
|
+
| `rigor:v1:predicate-if-true target is T` | Refine `target` to `T` on the **true** branch when the call is used as a condition. |
|
|
42
|
+
| `rigor:v1:predicate-if-false target is T` | Refine `target` to `T` on the **false** branch. |
|
|
43
|
+
| `rigor:v1:assert target is T` | Refine `target` after the method returns normally. |
|
|
44
|
+
| `rigor:v1:assert-if-true target is T` | Refine `target` when the method returns a truthy value. |
|
|
45
|
+
| `rigor:v1:assert-if-false target is T` | Refine `target` when the method returns `false` or `nil`. |
|
|
46
|
+
|
|
47
|
+
`target` is an RBS *parameter name* from the method's own
|
|
48
|
+
signature, or the literal `self`. To refer to an argument, the
|
|
49
|
+
RBS method type must name it (`(untyped value)`, not `(untyped)`).
|
|
50
|
+
|
|
51
|
+
### Predicates — narrowing through a guard
|
|
52
|
+
|
|
53
|
+
A predicate teaches Rigor to narrow a variable across the
|
|
54
|
+
branches of a method that tests it — the equivalent of
|
|
55
|
+
TypeScript's type guards or Python's `TypeGuard` / `TypeIs`. A
|
|
56
|
+
true-branch fact alone is enough for `TypeGuard`-style narrowing;
|
|
57
|
+
supplying both branches gives `TypeIs`-style narrowing.
|
|
58
|
+
|
|
59
|
+
```rbs
|
|
60
|
+
%a{rigor:v1:predicate-if-true value is String}
|
|
61
|
+
%a{rigor:v1:predicate-if-false value is ~String}
|
|
62
|
+
def string?: (untyped value) -> bool
|
|
63
|
+
|
|
64
|
+
%a{rigor:v1:predicate-if-true self is LoggedInUser}
|
|
65
|
+
def logged_in?: () -> bool
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
After `if string?(x)`, Rigor types `x` as `String` in the `then`
|
|
69
|
+
branch; in the `else` branch the `~String` negative fact removes
|
|
70
|
+
`String` from its type.
|
|
71
|
+
|
|
72
|
+
### Assertions — narrowing after a call
|
|
73
|
+
|
|
74
|
+
An assertion narrows a variable *after* the call returns, the way
|
|
75
|
+
PHPStan models `assert`-style helpers. Use `assert` for a method
|
|
76
|
+
that raises unless the fact holds, and `assert-if-true` /
|
|
77
|
+
`assert-if-false` for a method whose return value carries the
|
|
78
|
+
fact.
|
|
79
|
+
|
|
80
|
+
```rbs
|
|
81
|
+
%a{rigor:v1:assert value is String}
|
|
82
|
+
def assert_string!: (untyped value) -> void
|
|
83
|
+
|
|
84
|
+
%a{rigor:v1:assert-if-true value is String}
|
|
85
|
+
def valid_string?: (untyped value) -> bool
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
After `assert_string!(x)` returns, `x` is `String` for the rest
|
|
89
|
+
of the scope.
|
|
90
|
+
|
|
91
|
+
## The payload type grammar
|
|
92
|
+
|
|
93
|
+
The right-hand side of `return:`, `param:`, `assert*`, and
|
|
94
|
+
`predicate-if-*` accepts either:
|
|
95
|
+
|
|
96
|
+
- an **RBS class name** — `String`, `::Foo::Bar`; or
|
|
97
|
+
- a **refinement payload** — a kebab-case name from the
|
|
98
|
+
imported-built-in catalogue
|
|
99
|
+
([`imported-built-in-types.md`](../type-specification/imported-built-in-types.md)),
|
|
100
|
+
such as `non-empty-string` or `positive-int`.
|
|
101
|
+
|
|
102
|
+
Refinement payloads support the parameterised forms
|
|
103
|
+
`non-empty-array[Integer]`, `non-empty-hash[Symbol, Integer]`,
|
|
104
|
+
and the bounded-integer form `int<min, max>`. Type-argument
|
|
105
|
+
positions also accept Symbol / String literal tokens and unions
|
|
106
|
+
of them — `pick_of[T, :name | :email]`,
|
|
107
|
+
`Pick[T, "name" | "email"]` — each lifted to a `Constant<value>`.
|
|
108
|
+
|
|
109
|
+
Negation with `~T` is allowed on **class-name** payloads (it is
|
|
110
|
+
how the false branch of a predicate is usually written); it is
|
|
111
|
+
**not** yet accepted on refinement-form payloads. For an explicit
|
|
112
|
+
user-authored difference type, prefer `T - U` (see
|
|
113
|
+
[type-operators.md](../type-specification/type-operators.md)).
|
|
114
|
+
|
|
115
|
+
## `conforms-to` — a checked structural contract
|
|
116
|
+
|
|
117
|
+
Rigor checks structural compatibility implicitly wherever a value
|
|
118
|
+
flows into a position that needs an interface. The `conforms-to`
|
|
119
|
+
directive makes that contract *explicit and always-checked* on a
|
|
120
|
+
class, regardless of whether any call site currently exercises it
|
|
121
|
+
— useful when a library wants its structural contract to be a
|
|
122
|
+
design assertion:
|
|
123
|
+
|
|
124
|
+
```rbs
|
|
125
|
+
%a{rigor:v1:conforms-to _RewindableStream}
|
|
126
|
+
class MyBuffer
|
|
127
|
+
end
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
If the class is missing a method the interface requires, Rigor
|
|
131
|
+
fires
|
|
132
|
+
[`rbs_extended.unsatisfied-conformance`](04-diagnostics.md#rule-rbs_extended-unsatisfied-conformance);
|
|
133
|
+
a satisfied directive is silent. Multiple `conforms-to`
|
|
134
|
+
directives on one class combine like an intersection of
|
|
135
|
+
interfaces. The directive is purely additive — a class that
|
|
136
|
+
already satisfies the interface type-checks with or without it.
|
|
137
|
+
|
|
138
|
+
## Higher-kinded type directives
|
|
139
|
+
|
|
140
|
+
Two declaration-level directives register and define the
|
|
141
|
+
defunctionalised type constructors behind Rigor's lightweight HKT
|
|
142
|
+
mechanism ([ADR-20](../adr/20-lightweight-hkt.md)). Unlike the
|
|
143
|
+
per-method directives they attach to a `class` / `module` and
|
|
144
|
+
take **space-separated `key=value` pairs** (RBS's annotation
|
|
145
|
+
grammar does not accept nested punctuation):
|
|
146
|
+
|
|
147
|
+
| Directive | Effect |
|
|
148
|
+
| --- | --- |
|
|
149
|
+
| `rigor:v1:hkt_register: uri=<uri> arity=<int> variance=<v1>,… bound=<class\|untyped>` | Register a type-constructor URI with its arity, per-position variance, and erasure bound. |
|
|
150
|
+
| `rigor:v1:hkt_define: uri=<uri> params=<P1>,… body=<body-text>` | Bind the URI to a type-function body; `body=` consumes the rest of the payload and is parsed into a union tree. |
|
|
151
|
+
|
|
152
|
+
```rbs
|
|
153
|
+
%a{rigor:v1:hkt_register: uri=json::value arity=1 variance=out bound=untyped}
|
|
154
|
+
%a{rigor:v1:hkt_define: uri=json::value params=K
|
|
155
|
+
body=nil | true | false | Integer | Float | String |
|
|
156
|
+
Array[App[json::value, K]] | Hash[K, App[json::value, K]]}
|
|
157
|
+
module JsonOverlay
|
|
158
|
+
end
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
This is an advanced authoring surface; the worked walkthrough is
|
|
162
|
+
[handbook chapter 12](../handbook/12-lightweight-hkt.md).
|
|
163
|
+
|
|
164
|
+
## Authoring rules
|
|
165
|
+
|
|
166
|
+
- The plain RBS signature is always the compatibility contract;
|
|
167
|
+
annotations only refine or explain it.
|
|
168
|
+
- Always use the explicit, versioned `rigor:v1:` prefix. An
|
|
169
|
+
unversioned `rigor:` directive is invalid.
|
|
170
|
+
- Multiple annotations on one node are interpreted independently
|
|
171
|
+
of source order; exact duplicates are idempotent.
|
|
172
|
+
- A directive that **conflicts** with the RBS signature, or two
|
|
173
|
+
contradictory directives on the same target and flow edge, are
|
|
174
|
+
reported as diagnostics — Rigor never silently picks a winner.
|
|
175
|
+
- Annotations under unrelated keys belong to other tools; Rigor
|
|
176
|
+
preserves them untouched. Conversely, exported plain RBS
|
|
177
|
+
([RBS erasure](../type-specification/rbs-erasure.md)) drops
|
|
178
|
+
Rigor-only annotations unless you ask to keep them.
|
|
179
|
+
|
|
180
|
+
## See also
|
|
181
|
+
|
|
182
|
+
- [`docs/type-specification/rbs-extended.md`](../type-specification/rbs-extended.md)
|
|
183
|
+
— the normative grammar and merging rules.
|
|
184
|
+
- [`imported-built-in-types.md`](../type-specification/imported-built-in-types.md)
|
|
185
|
+
— the reserved refinement-name catalogue.
|
|
186
|
+
- [handbook chapter 7](../handbook/07-rbs-and-extended.md) — the
|
|
187
|
+
type-model walkthrough.
|
|
188
|
+
- [Inspecting inferred types](05-inspecting-types.md) — the
|
|
189
|
+
`assert_type` / `dump_type` source helpers, the Ruby-side
|
|
190
|
+
counterpart to these RBS-side annotations.
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# Driving project improvement with `rigor-next-steps`
|
|
2
|
+
|
|
3
|
+
`rigor-next-steps` is the **single entry point** for "what should we
|
|
4
|
+
do next with Rigor on this project?". You hand it to an AI coding
|
|
5
|
+
agent once; from then on it figures out where the project is on the
|
|
6
|
+
Rigor adoption curve and drives it forward — installing Rigor if
|
|
7
|
+
missing, onboarding the project if it has no config, then routing to
|
|
8
|
+
the right next skill until the project is set up, guarded, and as
|
|
9
|
+
type-protected as you want it.
|
|
10
|
+
|
|
11
|
+
This chapter is the operator's view of that journey. Each step maps to
|
|
12
|
+
a command you can also run by hand — the skill just drives the whole
|
|
13
|
+
loop for you. The bundled skills it routes to are catalogued in
|
|
14
|
+
[Provided skills](08-skills.md); this chapter is the *workflow* that
|
|
15
|
+
ties them together.
|
|
16
|
+
|
|
17
|
+
## The loop, in one picture
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
rigor-next-steps
|
|
21
|
+
├─ resolve `rigor` (install via docs/install.md if missing)
|
|
22
|
+
├─ no config? → rigor-project-init (onboard)
|
|
23
|
+
└─ otherwise → rigor skill describe → the recommended next skill
|
|
24
|
+
↺ re-run after each step
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The engine of the loop is **`rigor skill describe`**. It is cheap and
|
|
28
|
+
side-effect-free — a *presence-only* probe (it stats your config,
|
|
29
|
+
baseline, `sig/`, lockfiles, CI, and editor/MCP configs; it never runs
|
|
30
|
+
`rigor check`) — so an agent can run it freely at any point. Its
|
|
31
|
+
guidance is generated live by your installed Rigor, so it never goes
|
|
32
|
+
stale.
|
|
33
|
+
|
|
34
|
+
## What `describe` tells you
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
rigor skill describe # or the alias: rigor describe
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
# Rigor — next steps for this project
|
|
42
|
+
#
|
|
43
|
+
# Generated live by rigortype 0.2.x; this guidance always
|
|
44
|
+
# reflects your installed version and the project's current state.
|
|
45
|
+
|
|
46
|
+
## Project state
|
|
47
|
+
- Config file: .rigor.dist.yml
|
|
48
|
+
- Baseline: none
|
|
49
|
+
- Project sig/: present
|
|
50
|
+
- Community RBS: collection installed
|
|
51
|
+
- CI integration: CI present, Rigor not wired
|
|
52
|
+
- Editor LSP: .vscode present, Rigor LSP not wired
|
|
53
|
+
- MCP server: not detected
|
|
54
|
+
|
|
55
|
+
## Recommended next step
|
|
56
|
+
→ rigor-ci-setup — Rigor is configured but not wired into CI — lock in the regression guard.
|
|
57
|
+
Load it: rigor skill rigor-ci-setup
|
|
58
|
+
|
|
59
|
+
## All skills you can run next
|
|
60
|
+
…the full catalogue, each with its current one-line description…
|
|
61
|
+
|
|
62
|
+
## For the agent
|
|
63
|
+
…how to act on the recommendation, and how to refine it from `rigor check` findings…
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Run it, follow the **Recommended next step**, complete that skill, then
|
|
67
|
+
run it again. The recommendation advances as the project's state
|
|
68
|
+
changes (e.g. `project-init → rbs-setup → ci-setup → …`).
|
|
69
|
+
|
|
70
|
+
## The journey, step by step
|
|
71
|
+
|
|
72
|
+
The recommendation walks a sensible adoption order. For each stage,
|
|
73
|
+
the equivalent hand-commands are in the linked chapter.
|
|
74
|
+
|
|
75
|
+
| Stage | Recommended skill | What it does | By hand |
|
|
76
|
+
| --- | --- | --- | --- |
|
|
77
|
+
| **Onboard** | `rigor-project-init` | Detect the stack, pick plugins, write `.rigor.dist.yml`, optionally snapshot a baseline. | [Provided skills](08-skills.md), [Configuration](03-configuration.md) |
|
|
78
|
+
| **Community RBS** | `rigor-rbs-setup` | `rbs collection install` so RBS-less gems stop typing as `Dynamic`. | [Configuration](03-configuration.md) (`rbs_collection`) |
|
|
79
|
+
| **Rails plugins** | `rigor-plugin-tune` | If Rails is locked but no Rails plugins are enabled, wire them so ActiveRecord / routes / i18n calls resolve. | [Using plugins](07-plugins.md) |
|
|
80
|
+
| **See findings** | — | `rigor check` for bugs; `rigor coverage --protection` for "add a type here". | [CLI reference](02-cli-reference.md), [Type-protection coverage](15-type-protection-coverage.md) |
|
|
81
|
+
| **Raise protection** | `rigor-protection-uplift` | Close type-protection holes — sig-gen first, minimal hand-RBS, under a double gate. | [Type-protection coverage](15-type-protection-coverage.md) |
|
|
82
|
+
| **Pay down debt** | `rigor-baseline-reduce` | Work an existing baseline down rule by rule. | [Baselines](06-baseline.md) |
|
|
83
|
+
| **Guard it** | `rigor-ci-setup` | Run Rigor in CI with inline PR/MR diagnostics. | [Running Rigor in CI](11-ci.md) |
|
|
84
|
+
| **Editor / agent** | `rigor-editor-setup` / `rigor-mcp-setup` | Wire `rigor lsp` into your editor / `rigor mcp` into your AI agent. | [Editor integration](09-editor-integration.md), [MCP server](10-mcp-server.md) |
|
|
85
|
+
| **Teach a DSL** | `rigor-monkeypatch-resolve` / `rigor-plugin-author` | Resolve your own monkey-patches via `pre_eval:`, or author a plugin. | [Configuration](03-configuration.md), [Provided skills](08-skills.md) |
|
|
86
|
+
| **Upgrade / validate** | `rigor-upgrade` / `rigor-doctor` | Adopt a new Rigor version cleanly; validate the setup is healthy. | [Baselines](06-baseline.md), [Troubleshooting](13-troubleshooting.md) |
|
|
87
|
+
|
|
88
|
+
## A worked walk-through
|
|
89
|
+
|
|
90
|
+
A typical Rails app, freshly installed Rigor, no config:
|
|
91
|
+
|
|
92
|
+
1. **`describe`** → *"no Rigor configuration yet — start here"* → run
|
|
93
|
+
`rigor-project-init`. It writes `.rigor.dist.yml` (`target_ruby`,
|
|
94
|
+
`paths:`, the Rails plugins) and snapshots a baseline if the first
|
|
95
|
+
`rigor check` reports many diagnostics.
|
|
96
|
+
2. **`check` finds real bugs.** On a type-conscious Rails app the
|
|
97
|
+
plugins resolve hundreds of framework calls *and* surface genuine,
|
|
98
|
+
RBS-invisible bugs — strong-params keys that are not columns
|
|
99
|
+
(`permit :start_date_jst` where the column is `start_date`), missing
|
|
100
|
+
or doubled i18n keys. These are the onboarding payoff.
|
|
101
|
+
3. **`coverage --protection` shows where types help.** A plain library
|
|
102
|
+
slice might report e.g. `17.5%` protected, with an "add a type here"
|
|
103
|
+
list pinpointing the untyped receivers.
|
|
104
|
+
4. **`rigor-protection-uplift` closes the cheap holes.** sig-gen first,
|
|
105
|
+
then a *minimal true* hand-RBS for the residual — e.g. a seven-line
|
|
106
|
+
RBS for an external gem with no signatures can lift a slice's
|
|
107
|
+
protection from `13%` to `26%` with **zero new diagnostics** (the
|
|
108
|
+
skill verifies both: protection up *and* `rigor check` stays clean).
|
|
109
|
+
5. **Acknowledge mode + a baseline** snapshot today's diagnostics so
|
|
110
|
+
any *new* one is a visible regression. Inject a typo'd i18n key and
|
|
111
|
+
`rigor check` flags it immediately.
|
|
112
|
+
6. **`describe` advances** to `rigor-ci-setup`, then editor / MCP — and
|
|
113
|
+
the loop continues whenever you want the next move.
|
|
114
|
+
|
|
115
|
+
## Refining the recommendation from `check`
|
|
116
|
+
|
|
117
|
+
`describe`'s headline is presence-only — it never runs `rigor check`,
|
|
118
|
+
which keeps it fast. But the *best* next step often depends on what
|
|
119
|
+
`check` would find, so the **"For the agent"** section tells the agent
|
|
120
|
+
to refine the choice from a `rigor check` it runs (or already ran):
|
|
121
|
+
|
|
122
|
+
- errors present, no baseline yet → `rigor-baseline-reduce`
|
|
123
|
+
- a `call.unresolved-toplevel` / `call.undefined-method` cluster on
|
|
124
|
+
your own monkey-patches → `rigor-monkeypatch-resolve`
|
|
125
|
+
- framework calls typing as `Dynamic` with no matching plugins enabled
|
|
126
|
+
→ `rigor-plugin-tune`
|
|
127
|
+
- `RBS classes available: 0` or a `configuration-error` → `rigor-doctor`
|
|
128
|
+
|
|
129
|
+
If you are following the loop by hand, the same rules apply: run
|
|
130
|
+
`rigor check`, and let its output pick the most useful next skill.
|
|
131
|
+
|
|
132
|
+
## Practical notes
|
|
133
|
+
|
|
134
|
+
- **`target_ruby` floor.** Rigor's bundled parser (Prism) supports a
|
|
135
|
+
recent Ruby floor; if you set `target_ruby` below it, `rigor check`
|
|
136
|
+
tells you the minimum and where to read the right value (your
|
|
137
|
+
`Gemfile.lock` `RUBY VERSION` or `.ruby-version`). `rigor-project-init`
|
|
138
|
+
picks a compatible value for you.
|
|
139
|
+
- **List the individual Rails plugins, not the umbrella.** Put
|
|
140
|
+
`rigor-activerecord`, `rigor-actionpack`, … in `plugins:` — not
|
|
141
|
+
`rigor-rails` (a convenience meta-gem, which is not a single plugin).
|
|
142
|
+
If you try the umbrella, the load error lists the individual plugins
|
|
143
|
+
to use.
|
|
144
|
+
- **`RBS classes available: 0` is a broken setup, not a clean run.** If
|
|
145
|
+
`rigor check` prints that warning, the RBS environment failed to build
|
|
146
|
+
(usually a duplicate declaration in `signature_paths:`) — fix it
|
|
147
|
+
before trusting the (near-empty) results. `rigor-doctor` helps locate
|
|
148
|
+
it.
|
|
149
|
+
- **A missing path is skipped, not fatal.** `rigor check app lib` on a
|
|
150
|
+
project with no `lib/` analyses `app` and warns about `lib` — it does
|
|
151
|
+
not abort.
|
|
152
|
+
|
|
153
|
+
## See also
|
|
154
|
+
|
|
155
|
+
- [Provided skills](08-skills.md) — the per-skill reference for every
|
|
156
|
+
destination this loop routes to.
|
|
157
|
+
- [Installing Rigor](01-installation.md) — the install step
|
|
158
|
+
`rigor-next-steps` runs first.
|
|
159
|
+
- [Type-protection coverage](15-type-protection-coverage.md) — the
|
|
160
|
+
measurement `rigor-protection-uplift` acts on.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# The Rigor User Manual
|
|
2
|
+
|
|
3
|
+
How to install, run, configure, and operate Rigor. Where the
|
|
4
|
+
[handbook](../handbook/README.md) teaches the *type model* —
|
|
5
|
+
what carriers Rigor infers and why — this manual is the
|
|
6
|
+
*operational* reference: the command line, the configuration
|
|
7
|
+
file, the diagnostic catalogue, and the workflows for adopting
|
|
8
|
+
Rigor on a real project.
|
|
9
|
+
|
|
10
|
+
The two are companions. Reach for the handbook to understand
|
|
11
|
+
what a diagnostic *means*; reach for the manual to look up the
|
|
12
|
+
flag, key, or command that *acts* on it.
|
|
13
|
+
|
|
14
|
+
## Contents
|
|
15
|
+
|
|
16
|
+
### Getting started
|
|
17
|
+
|
|
18
|
+
1. [Installing Rigor](01-installation.md) — `mise`, `asdf`,
|
|
19
|
+
`gem install`, Nix, and the dev-container guidance. Rigor
|
|
20
|
+
is a tool, not a project dependency.
|
|
21
|
+
17. [Driving project improvement with `rigor-next-steps`](17-driving-improvement.md) —
|
|
22
|
+
the single-entry-point loop: onboard, see what Rigor finds,
|
|
23
|
+
raise type-protection, guard it in CI — driven by
|
|
24
|
+
`rigor skill describe`.
|
|
25
|
+
14. [Rigor for Rails — step-by-step setup with mise](14-rails-quickstart.md) —
|
|
26
|
+
install Ruby 4.0 + Rigor via `mise`, activate the Rails
|
|
27
|
+
plugin set, run `rigor check`, and share the config with
|
|
28
|
+
your team in about ten minutes.
|
|
29
|
+
|
|
30
|
+
### Reference
|
|
31
|
+
|
|
32
|
+
2. [CLI command reference](02-cli-reference.md) — every
|
|
33
|
+
subcommand (`check`, `annotate`, `type-of`, `sig-gen`,
|
|
34
|
+
`baseline`, `triage`, `lsp`, …), its flags, and its exit
|
|
35
|
+
codes.
|
|
36
|
+
3. [Configuration](03-configuration.md) — the `.rigor.yml`
|
|
37
|
+
key reference, config discovery, and `includes:` layering.
|
|
38
|
+
4. [Diagnostics](04-diagnostics.md) — the rule-ID catalogue,
|
|
39
|
+
severity profiles, and `# rigor:disable` suppression.
|
|
40
|
+
5. [Inspecting inferred types](05-inspecting-types.md) — the
|
|
41
|
+
`assert_type` / `dump_type` source helpers and the
|
|
42
|
+
`rigor annotate` / `rigor type-of` commands.
|
|
43
|
+
16. [RBS::Extended annotations](16-rbs-extended-annotations.md) —
|
|
44
|
+
the `%a{rigor:v1:…}` annotations you write in `*.rbs` files:
|
|
45
|
+
`return:` / `param:` overrides, predicate and assertion
|
|
46
|
+
narrowing, `conforms-to`, and the HKT directives.
|
|
47
|
+
|
|
48
|
+
### Adopting Rigor on a project
|
|
49
|
+
|
|
50
|
+
6. [Baselines](06-baseline.md) — `.rigor-baseline.yml`, the
|
|
51
|
+
`rigor baseline` subcommands, and `rigor triage`.
|
|
52
|
+
7. [Using plugins](07-plugins.md) — activating framework and
|
|
53
|
+
gem plugins through the `plugins:` config key. Per-plugin
|
|
54
|
+
user docs live under [Plugin reference](plugins/README.md).
|
|
55
|
+
8. [Provided skills](08-skills.md) — the bundled Agent Skills
|
|
56
|
+
the `rigor-next-steps` loop routes to: onboarding, RBS /
|
|
57
|
+
plugin setup, protection uplift, baseline reduction, CI /
|
|
58
|
+
editor / MCP wiring, and plugin authoring.
|
|
59
|
+
15. [Type-protection coverage](15-type-protection-coverage.md) —
|
|
60
|
+
measuring whether a bug would be *caught*, fusing your types
|
|
61
|
+
and your tests into one safety net (`rigor coverage
|
|
62
|
+
--protection [--mutation --with-tests --include-dynamic]`).
|
|
63
|
+
|
|
64
|
+
### Integration and operations
|
|
65
|
+
|
|
66
|
+
9. [Editor integration](09-editor-integration.md) — wiring
|
|
67
|
+
`rigor lsp` into Neovim, VS Code, Helix, and Emacs.
|
|
68
|
+
10. [MCP server](10-mcp-server.md) — exposing Rigor's
|
|
69
|
+
analysis tools to AI coding agents (Claude Code, Cursor,
|
|
70
|
+
Cline, …) via `rigor mcp`.
|
|
71
|
+
11. [Running Rigor in CI](11-ci.md) — a clean CI job, inline
|
|
72
|
+
PR/MR diagnostics (SARIF / GitHub Actions / GitLab Code
|
|
73
|
+
Quality), copy-paste [templates](ci-templates/), and
|
|
74
|
+
version pinning.
|
|
75
|
+
12. [Caching](12-caching.md) — where the cache lives, what
|
|
76
|
+
invalidates it, and how to clear it.
|
|
77
|
+
13. [Troubleshooting](13-troubleshooting.md) — common
|
|
78
|
+
problems and their fixes.
|
|
79
|
+
|
|
80
|
+
## See also
|
|
81
|
+
|
|
82
|
+
- [The Rigor Handbook](../handbook/README.md) — the type-model
|
|
83
|
+
walkthrough.
|
|
84
|
+
- [`docs/types.md`](../types.md) — one-page type-system guide.
|
|
85
|
+
- [`docs/type-specification/`](../type-specification/README.md)
|
|
86
|
+
— the normative spec corpus.
|
|
87
|
+
- [`docs/adr/`](../adr/) — architecture decision records.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# CI setup templates
|
|
2
|
+
|
|
3
|
+
Copy-paste CI configuration for running Rigor in your project's pipeline.
|
|
4
|
+
Each runs Rigor in its **own isolated job** on Ruby 4.0 (see
|
|
5
|
+
[chapter 11, "Running Rigor in CI"](../11-ci.md) for why isolation is
|
|
6
|
+
required) and surfaces diagnostics inline on the pull / merge request via a
|
|
7
|
+
CI-native output format ([ADR-51](../../adr/51-ci-diagnostic-output-formats.md)).
|
|
8
|
+
|
|
9
|
+
`ruby/setup-ruby` provides **prebuilt** Ruby 4.0.x binaries for
|
|
10
|
+
`ubuntu-latest` — setup is fast (~0.3 s), not a compile-from-source
|
|
11
|
+
wait. Verified 2026-06-13: `setup-ruby` resolves `ruby-version: "4.0"`
|
|
12
|
+
to Ruby 4.0.5 from the runner tool cache.
|
|
13
|
+
|
|
14
|
+
| File | Copy it to | What it does |
|
|
15
|
+
| --- | --- | --- |
|
|
16
|
+
| [`github-actions-annotations.yml`](github-actions-annotations.yml) | `.github/workflows/rigor.yml` | **The default.** Workflow commands → inline PR annotations. No upload step, no permissions, works on every repo. |
|
|
17
|
+
| [`github-actions-sarif.yml`](github-actions-sarif.yml) | `.github/workflows/rigor.yml` | SARIF 2.1.0 → GitHub code scanning (Security tab + PR alerts). Needs code scanning — public repo, or private with GitHub Advanced Security. |
|
|
18
|
+
| [`github-actions-reviewdog.yml`](github-actions-reviewdog.yml) | `.github/workflows/rigor.yml` | reviewdog → inline PR **review comments**. Needs `pull-requests: write`. |
|
|
19
|
+
| [`gitlab-ci.yml`](gitlab-ci.yml) | `.gitlab-ci.yml` (or `include:` it) | GitLab Code Quality report → the merge-request widget. |
|
|
20
|
+
|
|
21
|
+
Pick **one** GitHub template. **Default to annotations** — it is the only
|
|
22
|
+
one that works on every repository with zero setup. Use SARIF when code
|
|
23
|
+
scanning is available (public repo, or private with GitHub Advanced
|
|
24
|
+
Security) and you want the Security tab; use reviewdog for threaded review
|
|
25
|
+
comments (it works the same way against GitLab, Gerrit, Bitbucket, and Gitea
|
|
26
|
+
— see the [`rigor-ci-setup`](../../../skills/rigor-ci-setup/SKILL.md) skill).
|
|
27
|
+
All run Rigor the same way — only the output format and publish step differ.
|
|
28
|
+
|
|
29
|
+
## Other runners (generic recipe)
|
|
30
|
+
|
|
31
|
+
On any CI system, the four steps are: provision Ruby 4.0, install
|
|
32
|
+
`rigortype`, run `rigor check`, and (optionally) publish the report.
|
|
33
|
+
|
|
34
|
+
```sh
|
|
35
|
+
# 1. Ruby 4.0 must be the active Ruby (rbenv/asdf/mise/container image).
|
|
36
|
+
# 2. Install Rigor — kept out of your project's Gemfile (see ADR-27).
|
|
37
|
+
gem install rigortype
|
|
38
|
+
# 3. Run it. Pick the format your platform renders, or plain text for logs.
|
|
39
|
+
rigor check # human-readable, exit 1 on errors
|
|
40
|
+
rigor check --format sarif > rigor.sarif # SARIF 2.1.0
|
|
41
|
+
rigor check --format gitlab > codequality.json # GitLab Code Quality
|
|
42
|
+
rigor check --format checkstyle > checkstyle.xml # reviewdog / Jenkins
|
|
43
|
+
rigor check --format junit > junit.xml # test-report CIs
|
|
44
|
+
rigor check --format json > rigor.json # generic machine stream
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The exit code is `0` when there are no errors and `1` otherwise, so the
|
|
48
|
+
step gates the pipeline regardless of format. Redirect the report to a file
|
|
49
|
+
with `>`; if your platform fails the job on a non-zero exit before the
|
|
50
|
+
publish step runs, mark the check step "continue on error" and publish
|
|
51
|
+
unconditionally (the GitHub SARIF template shows the pattern).
|
|
52
|
+
|
|
53
|
+
## Pinning Rigor's version
|
|
54
|
+
|
|
55
|
+
These templates install the latest `rigortype` at run time. To pin it — and
|
|
56
|
+
keep CI reproducible — see [chapter 11 § "Pinning Rigor's version"](../11-ci.md#pinning-rigors-version)
|
|
57
|
+
(a CI-only `.github/rigor/Gemfile` that Dependabot can update, or a pinned
|
|
58
|
+
`gem install rigortype -v "X.Y.Z"`).
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# Plugin reference
|
|
2
|
+
|
|
3
|
+
User-facing documentation for each bundled Rigor plugin — what
|
|
4
|
+
it checks, its configuration keys, what it infers, and its
|
|
5
|
+
limitations. For *activating* plugins in general, see
|
|
6
|
+
[Using plugins](../07-plugins.md); to *write* one, see the
|
|
7
|
+
[examples/](../../../examples/README.md) walkthroughs and the
|
|
8
|
+
[`rigor-plugin-author` skill](../08-skills.md).
|
|
9
|
+
|
|
10
|
+
All plugins ship bundled in `rigortype` — no separate install.
|
|
11
|
+
The full catalogue, with a one-line scope for every plugin, is
|
|
12
|
+
[plugins/README.md](../../../plugins/README.md).
|
|
13
|
+
|
|
14
|
+
## Available pages
|
|
15
|
+
|
|
16
|
+
- [rigor-activerecord](rigor-activerecord.md) — ActiveRecord
|
|
17
|
+
finder / relation typing and schema-checked columns.
|
|
18
|
+
- [rigor-rails-routes](rigor-rails-routes.md) — `*_path` / `*_url`
|
|
19
|
+
helper validation against a parsed `config/routes.rb`.
|
|
20
|
+
- [rigor-rails-i18n](rigor-rails-i18n.md) — `t(...)` / `I18n.t(...)`
|
|
21
|
+
key, per-locale coverage, and interpolation validation.
|
|
22
|
+
- [rigor-actionpack](rigor-actionpack.md) — controller route
|
|
23
|
+
helpers, filter chains, render targets, strong-params keys.
|
|
24
|
+
- [rigor-activestorage](rigor-activestorage.md) — `has_*_attached`
|
|
25
|
+
attachment-accessor typing on AR models.
|
|
26
|
+
- [rigor-activejob](rigor-activejob.md) — `Job.perform_*` argument
|
|
27
|
+
arity against the discovered `#perform`.
|
|
28
|
+
- [rigor-actionmailer](rigor-actionmailer.md) — mailer action
|
|
29
|
+
existence / arity and missing-view-template detection.
|
|
30
|
+
- [rigor-factorybot](rigor-factorybot.md) — factory + attribute
|
|
31
|
+
(+ AR column) validation for `FactoryBot.create` / `build` / ….
|
|
32
|
+
- [rigor-rails](rigor-rails.md) — convenience grouping of the seven
|
|
33
|
+
Tier 1+2 Rails plugins (not a checker itself).
|
|
34
|
+
- [rigor-dry-types](rigor-dry-types.md) — `Types::*` alias
|
|
35
|
+
resolution; the dry-rb foundation (no diagnostics of its own).
|
|
36
|
+
- [rigor-dry-struct](rigor-dry-struct.md) — synthesises
|
|
37
|
+
`Dry::Struct` `attribute` readers (precise with dry-types).
|
|
38
|
+
- [rigor-dry-schema](rigor-dry-schema.md) — recognises dry-schema
|
|
39
|
+
declarations; publishes the typed-key table (fact-only).
|
|
40
|
+
- [rigor-dry-validation](rigor-dry-validation.md) — recognises
|
|
41
|
+
`Dry::Validation::Contract` subclasses; result-API RBS overlay.
|
|
42
|
+
- [rigor-sinatra](rigor-sinatra.md) — narrows the route-block
|
|
43
|
+
`self` so `params` / `redirect` / `halt` / … resolve.
|
|
44
|
+
- [rigor-rspec](rigor-rspec.md) — RSpec `let` / `subject`
|
|
45
|
+
duplicate and self-reference checks.
|
|
46
|
+
- [rigor-sorbet](rigor-sorbet.md) — read an existing Sorbet
|
|
47
|
+
codebase (`sig` blocks, RBI, `T.*` assertions) as a type
|
|
48
|
+
source (full guide: [handbook ch. 10](../../handbook/10-sorbet.md)).
|
|
49
|
+
- [rigor-devise](rigor-devise.md) — synthesises the methods a
|
|
50
|
+
`devise :strategy` declaration mixes into a model (no diagnostics).
|
|
51
|
+
- [rigor-statesman](rigor-statesman.md) — validates `transition_to(:state)`
|
|
52
|
+
against the states declared in a `state_machine` block.
|
|
53
|
+
- [rigor-mangrove](rigor-mangrove.md) — sharpens Mangrove
|
|
54
|
+
`Result` / `Option` unwrap types and synthesises `Enum` variants.
|
|
55
|
+
- [rigor-pundit](rigor-pundit.md) — policy-class existence and
|
|
56
|
+
`authorize(record, :action)` predicate validation.
|
|
57
|
+
- [rigor-sidekiq](rigor-sidekiq.md) — Sidekiq `Worker.perform_*`
|
|
58
|
+
argument arity against the discovered `#perform`.
|
|
59
|
+
- [rigor-actioncable](rigor-actioncable.md) — `broadcast_to` channel
|
|
60
|
+
existence and `ActionCable.server.broadcast` stream-name validation.
|
|
61
|
+
- [rigor-minitest](rigor-minitest.md) — local-variable narrowing
|
|
62
|
+
through Minitest / Test::Unit assertions and spec matchers.
|
|
63
|
+
- [rigor-graphql](rigor-graphql.md) — GraphQL-Ruby type / enum / input
|
|
64
|
+
/ mutation table publication (cross-plugin facts, no diagnostics).
|
|
65
|
+
- [rigor-rspec-rails](rigor-rspec-rails.md) — `have_http_status`
|
|
66
|
+
argument validation (out-of-range codes, unknown status symbols).
|
|
67
|
+
- [rigor-shoulda-matchers](rigor-shoulda-matchers.md) — shoulda matcher
|
|
68
|
+
column / association validation against the AR model index.
|
|
69
|
+
- [rigor-hanami](rigor-hanami.md) — Hanami::Action `#handle` protocol
|
|
70
|
+
enforcement + request/response parameter typing (ADR-28).
|
|
71
|
+
- [rigor-typescript-utility-types](rigor-typescript-utility-types.md) —
|
|
72
|
+
`Pick` / `Omit` / `Partial` / … mapped onto Rigor shape projections.
|
|
73
|
+
- [rigor-rbs-inline](rigor-rbs-inline.md) — ingests `# @rbs` inline
|
|
74
|
+
comments as enforced RBS contracts (ADR-32).
|
|
75
|
+
- [rigor-activesupport-core-ext](rigor-activesupport-core-ext.md) —
|
|
76
|
+
opt-in RBS bundle for ActiveSupport core_ext (the biggest Rails FP source).
|
|
77
|
+
|
|
78
|
+
The browser **playground** (`rigor playground`) is infrastructure, not
|
|
79
|
+
a checker plugin — it has no page here; see the
|
|
80
|
+
[CLI reference](../02-cli-reference.md) and
|
|
81
|
+
[ADR-29](../../adr/29-browser-playground.md).
|
|
82
|
+
|
|
83
|
+
_Every bundled checker plugin has a page above; each plugin's in-tree
|
|
84
|
+
[`README.md`](../../../plugins/README.md) now covers its internals
|
|
85
|
+
(layout, architecture, the contract surfaces it exercises) and links
|
|
86
|
+
back up to its page here._
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# rigor-actioncable
|
|
2
|
+
|
|
3
|
+
Validates ActionCable broadcast call sites against a statically
|
|
4
|
+
discovered channel index: `<X>Channel.broadcast_to(record, data)`
|
|
5
|
+
checks that the channel class exists, and
|
|
6
|
+
`ActionCable.server.broadcast("stream_name", data)` checks that the
|
|
7
|
+
literal stream name was registered with `stream_from` in some channel.
|
|
8
|
+
It reads source only — no `actioncable` runtime dependency.
|
|
9
|
+
|
|
10
|
+
It ships bundled in `rigortype`. Activate it under `plugins:`:
|
|
11
|
+
|
|
12
|
+
```yaml
|
|
13
|
+
plugins:
|
|
14
|
+
- rigor-actioncable
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## What it checks
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
# app/channels/chat_channel.rb
|
|
21
|
+
class ChatChannel < ApplicationCable::Channel
|
|
22
|
+
def subscribed
|
|
23
|
+
stream_from "chat_room_5"
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
ChatChannel.broadcast_to(room, message: "hi") # info: channel exists
|
|
28
|
+
ActionCable.server.broadcast("chat_room_5", body: "hi") # info: registered stream
|
|
29
|
+
ChartChannel.broadcast_to(room, message: "hi") # error: no channel (did you mean ChatChannel?)
|
|
30
|
+
ActionCable.server.broadcast("chat_room_42", body: "hi") # warning: no such stream registration
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
| Rule | Severity | Fires when |
|
|
34
|
+
| --- | --- | --- |
|
|
35
|
+
| `plugin.actioncable.broadcast-target` | info | `<X>Channel.broadcast_to(...)` matched a discovered channel |
|
|
36
|
+
| `plugin.actioncable.broadcast-stream` | info | `ActionCable.server.broadcast("...", ...)` matched a registered `stream_from` literal |
|
|
37
|
+
| `plugin.actioncable.unknown-channel` | error | the receiver ends in `Channel` but is not in the index (with a did-you-mean) |
|
|
38
|
+
| `plugin.actioncable.unknown-stream` | warning | the literal stream name matched no `stream_from` registration (with a did-you-mean) |
|
|
39
|
+
| `plugin.actioncable.load-error` | warning | channel discovery failed (parse/read error) — once per file |
|
|
40
|
+
|
|
41
|
+
The `unknown-stream` check is **suppressed** when any discovered
|
|
42
|
+
channel registers a dynamic stream (`stream_from interpolated_string`
|
|
43
|
+
or `stream_for record`) — the absence of a literal match doesn't prove
|
|
44
|
+
the stream is invalid. Non-`Channel` receivers and non-literal stream
|
|
45
|
+
arguments pass through silently.
|
|
46
|
+
|
|
47
|
+
## Configuration
|
|
48
|
+
|
|
49
|
+
```yaml
|
|
50
|
+
plugins:
|
|
51
|
+
- gem: rigor-actioncable
|
|
52
|
+
config:
|
|
53
|
+
channel_search_paths: ["app/channels"] # default
|
|
54
|
+
channel_base_classes: ["ApplicationCable::Channel", "ActionCable::Channel::Base"] # default
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Limitations
|
|
58
|
+
|
|
59
|
+
- **Direct-superclass match only.** An indirect chain (`AdminChannel <
|
|
60
|
+
BaseChannel < ApplicationCable::Channel`) needs `BaseChannel` listed
|
|
61
|
+
in `channel_base_classes`.
|
|
62
|
+
- **Action methods are indexed, not validated.** Channel actions are
|
|
63
|
+
invoked client-side via `subscription.perform("action", data)`; Rigor
|
|
64
|
+
does not analyse JavaScript, so the action index is informational.
|
|
65
|
+
- **`broadcast_to` arity is not checked** — it accepts any record + any
|
|
66
|
+
data hash.
|
|
67
|
+
- **Indirect `stream_from`** (registered inside a helper method rather
|
|
68
|
+
than directly in the channel body) is out of scope.
|
|
69
|
+
- **Bare `broadcast(...)`** without an explicit `ActionCable.server`
|
|
70
|
+
receiver is skipped to avoid false positives on unrelated methods.
|
|
71
|
+
|
|
72
|
+
## Plugin internals
|
|
73
|
+
|
|
74
|
+
The channel discoverer / index and the contract surfaces this plugin
|
|
75
|
+
exercises are in the
|
|
76
|
+
[plugin's README](../../../plugins/rigor-actioncable/README.md). To
|
|
77
|
+
write a plugin, see [`examples/`](../../../examples/README.md) and the
|
|
78
|
+
[`rigor-plugin-author`](../08-skills.md) skill.
|