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.
Files changed (105) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +41 -14
  3. data/docs/handbook/01-getting-started.md +311 -0
  4. data/docs/handbook/02-everyday-types.md +337 -0
  5. data/docs/handbook/03-narrowing.md +359 -0
  6. data/docs/handbook/04-tuples-and-shapes.md +321 -0
  7. data/docs/handbook/05-methods-and-blocks.md +339 -0
  8. data/docs/handbook/06-classes.md +305 -0
  9. data/docs/handbook/07-rbs-and-extended.md +427 -0
  10. data/docs/handbook/08-understanding-errors.md +373 -0
  11. data/docs/handbook/09-plugins.md +241 -0
  12. data/docs/handbook/10-sorbet.md +347 -0
  13. data/docs/handbook/11-sig-gen.md +312 -0
  14. data/docs/handbook/12-lightweight-hkt.md +333 -0
  15. data/docs/handbook/README.md +275 -0
  16. data/docs/handbook/appendix-elixir.md +370 -0
  17. data/docs/handbook/appendix-go.md +399 -0
  18. data/docs/handbook/appendix-java-csharp.md +470 -0
  19. data/docs/handbook/appendix-liskov.md +580 -0
  20. data/docs/handbook/appendix-mypy.md +370 -0
  21. data/docs/handbook/appendix-phpstan.md +338 -0
  22. data/docs/handbook/appendix-protocols-and-structural-typing.md +292 -0
  23. data/docs/handbook/appendix-rust.md +446 -0
  24. data/docs/handbook/appendix-steep.md +336 -0
  25. data/docs/handbook/appendix-type-theory.md +1662 -0
  26. data/docs/handbook/appendix-typeprof.md +416 -0
  27. data/docs/handbook/appendix-typescript.md +332 -0
  28. data/docs/install.md +189 -0
  29. data/docs/llms.txt +72 -0
  30. data/docs/manual/01-installation.md +342 -0
  31. data/docs/manual/02-cli-reference.md +557 -0
  32. data/docs/manual/03-configuration.md +152 -0
  33. data/docs/manual/04-diagnostics.md +206 -0
  34. data/docs/manual/05-inspecting-types.md +109 -0
  35. data/docs/manual/06-baseline.md +104 -0
  36. data/docs/manual/07-plugins.md +92 -0
  37. data/docs/manual/08-skills.md +143 -0
  38. data/docs/manual/09-editor-integration.md +245 -0
  39. data/docs/manual/10-mcp-server.md +532 -0
  40. data/docs/manual/11-ci.md +274 -0
  41. data/docs/manual/12-caching.md +116 -0
  42. data/docs/manual/13-troubleshooting.md +120 -0
  43. data/docs/manual/14-rails-quickstart.md +332 -0
  44. data/docs/manual/15-type-protection-coverage.md +204 -0
  45. data/docs/manual/16-rbs-extended-annotations.md +190 -0
  46. data/docs/manual/17-driving-improvement.md +160 -0
  47. data/docs/manual/README.md +87 -0
  48. data/docs/manual/ci-templates/README.md +58 -0
  49. data/docs/manual/plugins/README.md +86 -0
  50. data/docs/manual/plugins/rigor-actioncable.md +78 -0
  51. data/docs/manual/plugins/rigor-actionmailer.md +74 -0
  52. data/docs/manual/plugins/rigor-actionpack.md +80 -0
  53. data/docs/manual/plugins/rigor-activejob.md +58 -0
  54. data/docs/manual/plugins/rigor-activerecord.md +102 -0
  55. data/docs/manual/plugins/rigor-activestorage.md +74 -0
  56. data/docs/manual/plugins/rigor-activesupport-core-ext.md +86 -0
  57. data/docs/manual/plugins/rigor-devise.md +70 -0
  58. data/docs/manual/plugins/rigor-dry-schema.md +56 -0
  59. data/docs/manual/plugins/rigor-dry-struct.md +60 -0
  60. data/docs/manual/plugins/rigor-dry-types.md +59 -0
  61. data/docs/manual/plugins/rigor-dry-validation.md +62 -0
  62. data/docs/manual/plugins/rigor-factorybot.md +76 -0
  63. data/docs/manual/plugins/rigor-graphql.md +89 -0
  64. data/docs/manual/plugins/rigor-hanami.md +83 -0
  65. data/docs/manual/plugins/rigor-mangrove.md +73 -0
  66. data/docs/manual/plugins/rigor-minitest.md +86 -0
  67. data/docs/manual/plugins/rigor-pundit.md +72 -0
  68. data/docs/manual/plugins/rigor-rails-i18n.md +92 -0
  69. data/docs/manual/plugins/rigor-rails-routes.md +94 -0
  70. data/docs/manual/plugins/rigor-rails.md +44 -0
  71. data/docs/manual/plugins/rigor-rbs-inline.md +83 -0
  72. data/docs/manual/plugins/rigor-rspec-rails.md +72 -0
  73. data/docs/manual/plugins/rigor-rspec.md +86 -0
  74. data/docs/manual/plugins/rigor-shoulda-matchers.md +78 -0
  75. data/docs/manual/plugins/rigor-sidekiq.md +78 -0
  76. data/docs/manual/plugins/rigor-sinatra.md +61 -0
  77. data/docs/manual/plugins/rigor-sorbet.md +63 -0
  78. data/docs/manual/plugins/rigor-statesman.md +75 -0
  79. data/docs/manual/plugins/rigor-typescript-utility-types.md +71 -0
  80. data/exe/rigor +1 -1
  81. data/lib/rigor/analysis/incremental_session.rb +4 -2
  82. data/lib/rigor/analysis/run_stats.rb +13 -1
  83. data/lib/rigor/analysis/runner.rb +54 -12
  84. data/lib/rigor/cli/check_command.rb +1 -1
  85. data/lib/rigor/cli/docs_command.rb +248 -0
  86. data/lib/rigor/cli/skill_command.rb +103 -41
  87. data/lib/rigor/cli/skill_describe.rb +346 -0
  88. data/lib/rigor/cli.rb +25 -3
  89. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +124 -32
  90. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +37 -6
  91. data/lib/rigor/inference/scope_indexer.rb +87 -89
  92. data/lib/rigor/plugin/isolation.rb +5 -5
  93. data/lib/rigor/plugin/loader.rb +4 -2
  94. data/lib/rigor/version.rb +1 -1
  95. data/skills/rigor-ask/SKILL.md +172 -0
  96. data/skills/rigor-doctor/SKILL.md +87 -0
  97. data/skills/rigor-editor-setup/SKILL.md +114 -0
  98. data/skills/rigor-mcp-setup/SKILL.md +117 -0
  99. data/skills/rigor-monkeypatch-resolve/SKILL.md +79 -0
  100. data/skills/rigor-next-steps/SKILL.md +113 -0
  101. data/skills/rigor-plugin-tune/SKILL.md +79 -0
  102. data/skills/rigor-protection-uplift/SKILL.md +133 -0
  103. data/skills/rigor-rbs-setup/SKILL.md +128 -0
  104. data/skills/rigor-upgrade/SKILL.md +79 -0
  105. 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.