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,332 @@
1
+ # Appendix — Coming from TypeScript
2
+
3
+ If your reflex when you see a static type checker is "ah, like
4
+ TypeScript," this appendix maps Rigor's vocabulary onto the
5
+ TypeScript concepts you already know. It is the shortest path
6
+ from "I get TypeScript" to "I get Rigor."
7
+
8
+ This page is not a tutorial. It is a translation table plus a
9
+ short discussion of the places where the two systems make
10
+ different choices, which are the places where your
11
+ TypeScript reflexes will mislead you.
12
+
13
+ ## The five-second pitch
14
+
15
+ | Question | TypeScript | Rigor |
16
+ | --- | --- | --- |
17
+ | Where do annotations live? | In source (`x: number`) | In `.rbs` files alongside `.rb` |
18
+ | Who writes them? | The author of the code | The author OR inference |
19
+ | What is the default? | `any` (TypeScript pre-strict) / `unknown` (strict) | Inferred precisely or `Dynamic[top]` |
20
+ | Identity of types | Structural | Nominal + structural facets |
21
+ | Cost of "I do not know yet" | A red squiggle until annotated | Silence — `Dynamic[top]` produces no diagnostic |
22
+ | When do diagnostics fire? | Whenever a type is unsound | Only when Rigor can **prove** the unsoundness |
23
+
24
+ The two systems share their goal — flag bugs before the program
25
+ runs — and disagree on the path to it. TypeScript prefers
26
+ soundness-first authoring (every value gets a checked type, the
27
+ checker complains until it does). Rigor prefers
28
+ no-false-positives inference (the checker stays silent on
29
+ anything it cannot prove and asks for `.rbs` only where
30
+ inference cannot see further).
31
+
32
+ ## Type vocabulary mapping
33
+
34
+ | TypeScript form | Rigor form | Notes |
35
+ | --- | --- | --- |
36
+ | `string` | `String` | Display drops `Nominal[]`. |
37
+ | `number` | `Integer` / `Float` / `Numeric` | TS conflates int and float; Rigor splits per Ruby's runtime. |
38
+ | `boolean` | `bool` (`Constant<true> \| Constant<false>`) | `bool` is structurally a union of two constants. |
39
+ | `null` | `nil` (`Constant<nil>`) | Ruby has only `nil`; TS distinguishes `null` and `undefined`. |
40
+ | `undefined` | (no analogue) | An unset Ruby local raises `NameError`, not "undefined". |
41
+ | `any` | `Dynamic[top]` | The "be silent here" carrier. |
42
+ | `unknown` | `Top` | Both refuse method dispatch until narrowed; `unknown` is closer to `Top` than to `Dynamic[top]`. |
43
+ | `never` | `Bot` | Empty type — no inhabitants. Used for unreachable branches and `T.absurd` (Sorbet) / `raise`-only bodies. |
44
+ | `void` | `void` | Same idea — caller must not consume the value. |
45
+ | `T \| U` | `T \| U` | Same shape; same display. |
46
+ | `T & U` | `Intersection[T, U]` | Less common in Rigor — refinements often replace it. |
47
+ | `"hello"` (literal type) | `Constant<"hello">` | Direct equivalent. Folding is more aggressive in Rigor. |
48
+ | `42` (literal type) | `Constant<42>` | Same. |
49
+ | `42 \| 43 \| 44` | `Constant<42> \| Constant<43> \| Constant<44>` | Same. |
50
+ | `[number, string]` (tuple) | `Tuple[Integer, String]` | Same per-position model. |
51
+ | `{ name: string; age: number }` | `HashShape{name: String, age: Integer}` | Same per-key model; Ruby uses Symbol keys idiomatically. |
52
+ | `Array<T>` / `T[]` | `Array[T]` | Same. |
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/) plugin) | View-level read-only marker on every entry of a `HashShape`. Does NOT prove the underlying object is frozen — ADR-13 § "Readonly". |
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
+ | `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
+ | Conditional types `T extends U ? A : B` | (none in core; plugin contributions) | A plugin can vary return type by argument shape. |
58
+ | `keyof T` | (none) | `HashShape` exposes its key set internally but not as a type operator. |
59
+ | `T['k']` | `T[k]` indexed access | Rigor supports literal indexed access on `HashShape` and `Tuple` (see the type spec). |
60
+ | Template literal types | `literal-string` carrier | "Provably built from literals" — see Chapter 2. |
61
+
62
+ ## Narrowing — the part that feels familiar
63
+
64
+ TypeScript's flow-sensitive narrowing has direct analogues in
65
+ Rigor. The vocabulary is different; the behaviour is the same.
66
+
67
+ | TypeScript | Rigor |
68
+ | --- | --- |
69
+ | `if (x)` | `if x` — strips `false` / `nil` from the truthy edge |
70
+ | `typeof x === "string"` | `x.is_a?(String)` |
71
+ | `x instanceof Foo` | `x.is_a?(Foo)` |
72
+ | `x === null` | `x.nil?` (and `x == nil`) |
73
+ | `if (x !== null && x !== undefined)` | `if x` (Ruby has only `nil`, no `undefined`) |
74
+ | Discriminated union `switch (x.kind)` | `case x; in {kind: :foo}` or `case x.kind; when :foo` |
75
+ | User-defined type guard `function isFoo(x): x is Foo` | `%a{rigor:v1:predicate-if-true x is Foo}` directive |
76
+ | `as` cast | (no equivalent in code) — Rigor has `T.cast` via `rigor-sorbet`, or `param:` directives |
77
+ | `x!` (non-null assertion) | (no equivalent in code) — `T.must` via `rigor-sorbet`, or `unless x.nil?` narrowing |
78
+ | `as const` | Constants fold automatically — no `as const` needed |
79
+
80
+ The biggest practical difference: in TypeScript, you reach for
81
+ `as Foo` whenever the checker disagrees with you. Rigor does
82
+ not have an in-source cast. The equivalents are:
83
+
84
+ 1. **Add a guard.** `unless x.nil?; x.upcase; end` is the
85
+ idiomatic move.
86
+ 2. **Tighten an `.rbs`.** Often the underlying issue is a
87
+ library sig that is too loose.
88
+ 3. **Use the `rigor-sorbet` plugin.** Adopt `T.let` /
89
+ `T.cast` / `T.must` if you want in-source assertions; see
90
+ Chapter 10.
91
+
92
+ ## Refinement carriers — the part that does not exist in TypeScript
93
+
94
+ TypeScript can express "string of length ≥ 1" only through
95
+ template literal types or branded types, and neither composes
96
+ well. Rigor has first-class refinement carriers — a string
97
+ that is provably non-empty, an integer that is provably
98
+ positive, an array that is provably non-empty.
99
+
100
+ | Rigor refinement | TypeScript closest | Comment |
101
+ | --- | --- | --- |
102
+ | `non-empty-string` | `\`${string}${string}\`` (template literal trick) or branded `NonEmptyString` | Awkward in TS; Rigor produces it from `unless s.empty?` automatically. |
103
+ | `positive-int` | branded `PositiveInt` | TS users tend to skip the brand — Rigor narrows from `n > 0`. |
104
+ | `int<1, 9>` | union of literal types `1 \| 2 \| 3 \| ... \| 9` | Rigor's range carrier handles arbitrary bounds without exploding. |
105
+ | `numeric-string` | (none useful) | TS has no equivalent; Rigor narrows from regex matches against numeric patterns. |
106
+ | `non-empty-array[T]` | `[T, ...T[]]` (tuple-with-rest) | TS has the encoding but few APIs use it; Rigor produces it from `unless arr.empty?`. |
107
+
108
+ If you have ever wished TypeScript had `non-empty-string` as a
109
+ keyword instead of a brand, you will appreciate this part of
110
+ Rigor.
111
+
112
+ ## "No annotations needed" in practice
113
+
114
+ Take the canonical TypeScript onboarding example:
115
+
116
+ ```typescript
117
+ function classify(n: number): "zero" | "positive" | "negative" {
118
+ if (n === 0) return "zero";
119
+ if (n > 0) return "positive";
120
+ return "negative";
121
+ }
122
+
123
+ const result = classify(7);
124
+ // TypeScript: result: "zero" | "positive" | "negative"
125
+ ```
126
+
127
+ The Rigor equivalent — no annotations:
128
+
129
+ ```ruby
130
+ def classify(n)
131
+ return :zero if n.zero?
132
+ return :positive if n.positive?
133
+ :negative
134
+ end
135
+
136
+ result = classify(7)
137
+ ```
138
+
139
+ Both checkers infer the same precise union. The TypeScript
140
+ version requires the parameter type and return type as
141
+ authored annotations; the Rigor version requires neither.
142
+
143
+ When you DO need to write a sig — at module boundaries, when
144
+ the body is too dynamic, when you want to enforce parameter
145
+ shapes — that goes into `sig/<file>.rbs`, not into the `.rb`
146
+ source. That separation is deliberate (see ADR-1 and ADR-5).
147
+
148
+ ## Generics
149
+
150
+ TypeScript's generics are central to its standard library;
151
+ Rigor's generics are RBS's, which are more conservative. RBS
152
+ supports class-level type parameters and method-level type
153
+ parameters with bounded constraints, but does not yet support
154
+ inferred call-site instantiation as routinely as TypeScript.
155
+
156
+ | TypeScript | Rigor (via RBS) |
157
+ | --- | --- |
158
+ | `function id<T>(x: T): T` | `def id: [T] (T) -> T` |
159
+ | `Array<T>` | `Array[T]` |
160
+ | `Map<K, V>` | `Hash[K, V]` |
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/) plugin maps each onto `pick_of` / `omit_of` / `partial_of` / `required_of` / `readonly_of` over `HashShape` (and `pick_of` / `omit_of` over `Tuple`). |
163
+ | Conditional types | (no analogue — would need a plugin) |
164
+
165
+ Rigor reads RBS generics through its dispatcher and instantiates
166
+ parameters at the call site when the receiver carries enough
167
+ information. The display is identical to RBS — `Array[Integer]`
168
+ shows as `Array[Integer]`.
169
+
170
+ ## Nullability
171
+
172
+ TypeScript's `strictNullChecks` makes `null` and `undefined`
173
+ their own types. You spell nullable as `T | null | undefined`.
174
+
175
+ Ruby has `nil` and only `nil`. The RBS shorthand is `T?`,
176
+ which expands to `T | nil`. Rigor's narrowing handles `nil`
177
+ exactly the way TypeScript handles `null`:
178
+
179
+ ```ruby
180
+ def length(s) # s: String? (RBS-declared)
181
+ return 0 if s.nil?
182
+ s.length # s: String — nil stripped by .nil? check
183
+ end
184
+ ```
185
+
186
+ The TypeScript equivalent reads identically:
187
+
188
+ ```typescript
189
+ function length(s: string | null): number {
190
+ if (s === null) return 0;
191
+ return s.length;
192
+ }
193
+ ```
194
+
195
+ ## Severity, suppression, and "strict mode"
196
+
197
+ | TypeScript | Rigor |
198
+ | --- | --- |
199
+ | `tsconfig.json` `strict: true` | `severity_profile: strict` |
200
+ | `tsconfig.json` `noImplicitAny` | (no analogue — Rigor never demands annotations) |
201
+ | `tsconfig.json` `strictNullChecks` | Always-on in Rigor |
202
+ | `// @ts-ignore` | `# rigor:disable <rule>` |
203
+ | `// @ts-expect-error` | (no analogue today) |
204
+ | `// @ts-nocheck` | `# rigor:disable-file all` |
205
+ | `tsc --noEmit` | `rigor check lib` |
206
+
207
+ ## What TypeScript has and Rigor does not
208
+
209
+ Be honest about what you give up:
210
+
211
+ - **Conditional types.** `T extends U ? A : B` has no core
212
+ Rigor analogue. A plugin can vary return type by argument
213
+ shape (see Chapter 9), but you write Ruby code for the
214
+ variation, not type-level expressions.
215
+ - **Mapped types.** `Pick`, `Omit`, `Partial`, `Required`, and
216
+ `Readonly` ship as opt-in plugin-supplied vocabulary via
217
+ [`rigor-typescript-utility-types`](../../plugins/rigor-typescript-utility-types/),
218
+ which maps them onto the Rigor-canonical `pick_of` / `omit_of`
219
+ / `partial_of` / `required_of` / `readonly_of` shape-projection
220
+ type functions on `HashShape` (and `pick_of` / `omit_of` on
221
+ `Tuple`). Template literal manipulation and other mapped-type
222
+ variants (`Uppercase<S>` / `Lowercase<S>` / `Capitalize<S>`)
223
+ remain outside Rigor's surface.
224
+ - **Type-level computation.** TypeScript's type system is
225
+ Turing-complete; Rigor's deliberately is not, so the
226
+ analyzer stays fast on real Ruby projects.
227
+ - **Inferred return type from method body in source.** `tsc`
228
+ infers return types from a function's body and exposes them
229
+ to callers. Rigor does the same for in-source `def`, but
230
+ RBS-declared methods bind their callers to the declared
231
+ return — a deliberate boundary-discipline choice (see ADR-5,
232
+ the robustness principle).
233
+ - **Editor IntelliSense parity.** TypeScript's tooling has 20
234
+ years of investment behind it. Rigor's editor integration is
235
+ younger: `rigor lsp` ships an in-process Language Server
236
+ (diagnostics, hover-to-type, outline, type-aware completion;
237
+ see the manual's editor-integration chapter), but its
238
+ tooling depth still trails TypeScript's.
239
+
240
+ ## What Rigor has and TypeScript does not
241
+
242
+ The other direction:
243
+
244
+ - **First-class refinements.** `non-empty-string`,
245
+ `positive-int`, `numeric-string`, etc. — values restricted
246
+ by predicate, narrowed automatically.
247
+ - **Constant folding through method calls.** `"foo".upcase` is
248
+ `Constant<"FOO">`, not just `string`. Rigor catalogues which
249
+ built-in methods are pure and folds through them.
250
+ - **No-false-positives stance.** Rigor stays silent on
251
+ `Dynamic[top]` receivers rather than complaining. You will
252
+ never see a Rigor diagnostic where the right answer is "well,
253
+ technically the checker cannot know."
254
+ - **No annotation tax.** You can run `rigor check` on a Ruby
255
+ project that has zero `.rbs` files and get useful diagnostics
256
+ from inference alone. Adding `.rbs` files is incremental;
257
+ every file you skip is `Dynamic[top]` at the boundary, not a
258
+ diagnostic.
259
+ - **Severity-aware adoption.** TypeScript's "all or nothing"
260
+ feel (you flip `strict` and a thousand errors appear) is
261
+ smoothed by Rigor's `lenient` / `balanced` / `strict`
262
+ profiles plus per-rule overrides plus baseline diffing.
263
+
264
+ ## A migration vignette
265
+
266
+ You are porting a TypeScript module to Ruby. The original
267
+ function:
268
+
269
+ ```typescript
270
+ function pick<K extends keyof T, T extends object>(obj: T, keys: K[]): Pick<T, K> {
271
+ const out = {} as Pick<T, K>;
272
+ for (const k of keys) {
273
+ if (k in obj) out[k] = obj[k];
274
+ }
275
+ return out;
276
+ }
277
+ ```
278
+
279
+ The Rigor approach:
280
+
281
+ ```ruby
282
+ # lib/utils.rb
283
+ def pick(obj, keys)
284
+ keys.each_with_object({}) do |k, out|
285
+ out[k] = obj[k] if obj.key?(k)
286
+ end
287
+ end
288
+ ```
289
+
290
+ ```rbs
291
+ # sig/utils.rbs
292
+ def pick: [K, V] (Hash[K, V] obj, Array[K] keys) -> Hash[K, V]
293
+ ```
294
+
295
+ The RBS sig stays generic. If you want `Pick<T, K>`'s exact-
296
+ key-set tracking back, opt into the
297
+ [`rigor-typescript-utility-types`](../../plugins/rigor-typescript-utility-types/)
298
+ plugin and annotate the return type with the `Pick` spelling:
299
+
300
+ ```rbs
301
+ # sig/utils.rbs
302
+ %a{rigor:v1:return: Pick[T, K]}
303
+ def pick: [K, V] (Hash[K, V] obj, Array[K] keys) -> Hash[K, V]
304
+ ```
305
+
306
+ The plugin's `TypeNodeResolver` translates `Pick[T, K]` into
307
+ the canonical `pick_of[T, K]` projection. Either way the call
308
+ site stays precise where it matters: a Hash literal at the
309
+ call site is a `HashShape` regardless of the signature, and
310
+ the per-key types survive through `obj.key?(k)` narrowing.
311
+
312
+ ## What's next
313
+
314
+ You probably do not need to read the rest of this appendix
315
+ section sequentially. Three useful pointers:
316
+
317
+ - [Chapter 2 — Everyday types](02-everyday-types.md) for the
318
+ carrier zoo if you have not seen the refinements before.
319
+ - [Chapter 7 — RBS and `RBS::Extended`](07-rbs-and-extended.md)
320
+ for the directive grammar (how you teach Rigor about a
321
+ custom type predicate).
322
+ - [Chapter 10 — Coexisting with Sorbet](10-sorbet.md) if your
323
+ project is in fact already using Sorbet — `T.let`, `T.cast`,
324
+ and `T.must` have direct equivalents and the migration is
325
+ smoother than starting from scratch.
326
+
327
+ If you want to compare against another tool, the sibling
328
+ appendix pages cover [PHPStan](appendix-phpstan.md),
329
+ [mypy](appendix-mypy.md), [Steep](appendix-steep.md),
330
+ [TypeProf](appendix-typeprof.md),
331
+ [Java / C#](appendix-java-csharp.md), [Rust](appendix-rust.md),
332
+ [Go](appendix-go.md), and [Elixir](appendix-elixir.md).
data/docs/install.md ADDED
@@ -0,0 +1,189 @@
1
+ # Install Rigor — instructions for an AI agent
2
+
3
+ These instructions are written for an AI coding agent. Follow each
4
+ step in order. Run the shell commands exactly as shown. If a step
5
+ fails, stop and report the error before continuing.
6
+
7
+ The goal is to install Rigor and hand off to `rigor skill describe`,
8
+ which reports the project's state and routes to the right next-step
9
+ skill (`rigor-project-init` for a project that has never run Rigor).
10
+ **Do not add Rigor to the project's `Gemfile`** — Rigor is a
11
+ standalone tool, not a library.
12
+
13
+ ---
14
+
15
+ ## Step 1 — Detect the environment
16
+
17
+ Run these four checks and note which tools are available:
18
+
19
+ ```sh
20
+ which mise # preferred — see Step 2A
21
+ which asdf # fallback — see Step 2B
22
+ ruby --version 2>/dev/null | head -1 # is Ruby 4.0 already on PATH?
23
+ which docker # last resort — see Step 2D
24
+ ```
25
+
26
+ Then proceed to the **first** matching case below.
27
+
28
+ ---
29
+
30
+ ## Step 2 — Install Ruby 4.0 and Rigor
31
+
32
+ ### Case A — mise is available (recommended)
33
+
34
+ **What is mise?**
35
+ [mise](https://mise.jdx.dev/) is a runtime and tool version manager
36
+ — think `rbenv` + `nvm` combined, plus a task runner. It installs
37
+ and pins language runtimes (Ruby, Node, Python, …) and tool gems
38
+ (like `rigortype`) per project, recording versions in a `mise.toml`
39
+ that can be committed alongside the code. Other contributors — and CI
40
+ — run `mise install` to restore the exact same versions with no
41
+ `Gemfile` involvement.
42
+
43
+ Run in the project root:
44
+
45
+ ```sh
46
+ mise use ruby@4.0
47
+ mise use gem:rigortype
48
+ ```
49
+
50
+ `mise use` installs the tools and writes their versions to `mise.toml`
51
+ in one step. Commit `mise.toml` so the version is shared.
52
+
53
+ Then verify:
54
+
55
+ ```sh
56
+ rigor --version
57
+ ```
58
+
59
+ If `rigor` is not found, mise may not be wired into your shell yet.
60
+ Run one of:
61
+
62
+ ```sh
63
+ # Interactive shells (add to ~/.zshrc / ~/.bashrc permanently):
64
+ eval "$(mise activate zsh)" # or bash / fish
65
+
66
+ # Or use the shims directory directly:
67
+ export PATH="$HOME/.local/share/mise/shims:$PATH"
68
+ ```
69
+
70
+ Then re-run `rigor --version`. If it still fails, run
71
+ `mise exec gem:rigortype -- rigor --version` as a one-off check.
72
+
73
+ ---
74
+
75
+ ### Case B — asdf is available
76
+
77
+ `asdf` follows the same model as mise but has no gem backend, so the
78
+ gem is installed with `gem install` after setting the Ruby version.
79
+
80
+ ```sh
81
+ asdf install ruby latest:4.0
82
+ asdf local ruby latest:4.0
83
+ gem install rigortype
84
+ ```
85
+
86
+ Verify:
87
+
88
+ ```sh
89
+ rigor --version
90
+ ```
91
+
92
+ Note: unlike mise, `gem install` here does not pin the version in a
93
+ project config file. Consider switching to mise for per-project
94
+ pinning; see <https://mise.jdx.dev/getting-started.html>.
95
+
96
+ ---
97
+
98
+ ### Case C — Ruby 4.0 is already on PATH
99
+
100
+ If `ruby --version` reports `ruby 4.0.*`, install the gem directly:
101
+
102
+ ```sh
103
+ gem install rigortype
104
+ ```
105
+
106
+ Verify:
107
+
108
+ ```sh
109
+ rigor --version
110
+ ```
111
+
112
+ No version is pinned per project with this approach; a version manager
113
+ (Case A or B) is better for shared codebases.
114
+
115
+ ---
116
+
117
+ ### Case D — mise is not installed yet (no ruby, no asdf)
118
+
119
+ Install mise first by following the official getting-started guide:
120
+ <https://mise.jdx.dev/getting-started.html>
121
+
122
+ The one-line script from that page is:
123
+
124
+ ```sh
125
+ curl https://mise.run | sh
126
+ ```
127
+
128
+ After mise is installed and activated in your shell, come back and
129
+ follow **Case A**.
130
+
131
+ ---
132
+
133
+ ### Last resort — Docker
134
+
135
+ If none of the above is workable in your environment, you can run
136
+ Rigor inside a Docker container:
137
+
138
+ ```sh
139
+ docker run --rm -v "$(pwd):/app" -w /app ghcr.io/rigortype/rigor:latest rigor check
140
+ ```
141
+
142
+ This approach adds overhead from the container filesystem boundary
143
+ and does not integrate well with editor LSP. Use it only when a
144
+ host-side Ruby 4.0 is genuinely unavailable (for example, Windows
145
+ without WSL). For all other environments, prefer Case A–D above.
146
+
147
+ ---
148
+
149
+ ## Step 3 — Verify the installation
150
+
151
+ ```sh
152
+ rigor --version
153
+ ```
154
+
155
+ A version string like `rigor 0.1.x` confirms a successful install.
156
+ If the command is not found, revisit Step 2 for your case.
157
+
158
+ ---
159
+
160
+ ## Step 4 — Ask Rigor what to do next
161
+
162
+ Now that `rigor` is on your PATH, let it pick the next step for this
163
+ project:
164
+
165
+ ```sh
166
+ rigor skill describe
167
+ ```
168
+
169
+ This reports the project's current state (config / baseline / `sig/` /
170
+ CI), recommends the next skill to run with a reason, and lists every
171
+ skill with its current description. Follow the `## Recommended next
172
+ step` it prints — for a project with no `.rigor.yml` yet that is
173
+ `rigor-project-init`:
174
+
175
+ ```sh
176
+ rigor skill rigor-project-init
177
+ ```
178
+
179
+ `rigor skill <name>` prints the SKILL definition — a header with
180
+ file paths followed by the skill body. Follow it from top to bottom.
181
+ The project-init skill detects your project's stack, proposes plugins,
182
+ writes `.rigor.dist.yml`, and snapshots a baseline if needed; once the
183
+ project is set up, re-run `rigor skill describe` for the step after
184
+ that.
185
+
186
+ If `rigor skill describe` is not recognised, your Rigor version predates
187
+ it. Run `rigor --version` and upgrade with `mise use gem:rigortype` (or
188
+ `gem update rigortype` for Case C/B); on an older version, run
189
+ `rigor skill rigor-project-init` directly.
data/docs/llms.txt ADDED
@@ -0,0 +1,72 @@
1
+ # Rigor — offline doc index
2
+
3
+ > Rigor is a Ruby static analyzer that reports real bugs — undefined methods on
4
+ > typed receivers, wrong argument counts, provable nil dereferences — from
5
+ > ordinary Ruby with no type annotations in source. Every type is inferred from
6
+ > the values your code actually produces.
7
+
8
+ This is the **offline** doc index served by an installed Rigor. You do not need
9
+ the network: read any page below with `rigor docs <name>`, and any bundled Agent
10
+ Skill with `rigor skill <name>`. (The canonical web copy is
11
+ <https://rigor.typedduck.fail/llms.txt>.)
12
+
13
+ ## Start here (AI coding agents)
14
+
15
+ - **Run the `rigor-next-steps` skill** — the single entry point for "what should
16
+ we do next with Rigor on this project?". It resolves the `rigor` command,
17
+ onboards an unconfigured project, then asks `rigor skill describe` for the next
18
+ step and routes to the matching skill. `rigor skill rigor-next-steps`.
19
+ - `rigor skill describe` (alias `rigor describe`) — probe the project's state and
20
+ recommend the next skill. The driving command behind `rigor-next-steps`.
21
+ - `rigor skill --list` — every bundled skill; `rigor skill <name>` — its body.
22
+ - The full workflow narrative: `rigor docs 17-driving-improvement`.
23
+
24
+ ## Manual (read with `rigor docs <name>`)
25
+
26
+ - `install` — install Rigor (mise / asdf / gem). The one doc that precedes a
27
+ local `rigor`, so an agent reads it from the web until Rigor is installed.
28
+ - `01-installation` — installation channels and the tool-not-a-library rule.
29
+ - `02-cli-reference` — every command and flag.
30
+ - `03-configuration` — `.rigor.yml` keys, discovery, `includes:` layering.
31
+ - `04-diagnostics` — the rule-ID catalogue and severity profiles.
32
+ - `05-inspecting-types` — `rigor annotate` / `rigor type-of`, `assert_type`.
33
+ - `06-baseline` — `.rigor-baseline.yml` and the `rigor baseline` subcommands.
34
+ - `07-plugins` — activating framework / gem plugins via `plugins:`.
35
+ - `08-skills` — the bundled Agent Skills (the catalogue this index links to).
36
+ - `09-editor-integration` — wiring `rigor lsp` into Neovim / VS Code / Helix / Emacs.
37
+ - `10-mcp-server` — exposing Rigor to AI agents via `rigor mcp`.
38
+ - `11-ci` — running Rigor in CI with inline PR/MR diagnostics.
39
+ - `12-caching` — where the cache lives and what invalidates it.
40
+ - `13-troubleshooting` — common problems and fixes.
41
+ - `14-rails-quickstart` — the fast path for Rails apps.
42
+ - `15-type-protection-coverage` — `rigor coverage --protection`.
43
+ - `16-rbs-extended-annotations` — the `%a{rigor:v1:…}` annotations.
44
+ - `17-driving-improvement` — the `rigor-next-steps`-driven improvement loop.
45
+
46
+ ## Handbook (read with `rigor docs <name>`)
47
+
48
+ The end-user walkthrough of the type model — concepts rather than operations.
49
+ List them with `rigor docs --list handbook`; a chapter whose short name also
50
+ exists in the manual (`plugins`) needs its qualified path (`handbook/09-plugins`).
51
+
52
+ - `01-getting-started` — first run, what Rigor infers with no annotations.
53
+ - `02-everyday-types` — the carrier zoo (literals, unions, nil, Dynamic).
54
+ - `03-narrowing` — control-flow narrowing and certainty.
55
+ - `04-tuples-and-shapes` — tuples, hash shapes, object shapes.
56
+ - `05-methods-and-blocks` — method signatures, blocks, yields.
57
+ - `06-classes` — classes, ivars, inheritance, structural interfaces.
58
+ - `07-rbs-and-extended` — RBS, inline RBS, `%a{rigor:v1:…}` annotations.
59
+ - `08-understanding-errors` — reading diagnostics and why each fires.
60
+ - `handbook/09-plugins` — what plugins add to inference.
61
+ - `10-sorbet` — interoperating with Sorbet signatures.
62
+ - `11-sig-gen` — generating RBS with `rigor sig-gen`.
63
+ - `12-lightweight-hkt` — the `App[F, A]` lightweight higher-kinded encoding.
64
+ - Appendices (`rigor docs --list handbook`) compare Rigor to TypeScript, mypy,
65
+ PHPStan, Steep, TypeProf, Go, Java/C#, Rust, Elixir, and type theory.
66
+
67
+ ## More (web only)
68
+
69
+ The contributor-facing corpus is not bundled in the gem; read it on the site:
70
+ the type specification, internal specification, architecture decisions (ADRs),
71
+ design notes, and development notes are linked from
72
+ <https://rigor.typedduck.fail/llms.txt>.