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,416 @@
1
+ # Appendix — Coming from TypeProf
2
+
3
+ [TypeProf](https://github.com/ruby/typeprof) is Ruby's
4
+ official type *inference* tool — a type-level abstract
5
+ interpreter, maintained inside Ruby core, that reads
6
+ un-annotated `.rb` files and tells you the types it deduced.
7
+ If you have used TypeProf, the most important thing to know is
8
+ that **Rigor and TypeProf share TypeProf's headline promise**:
9
+ neither one asks you to write `.rbs` first. Where the Steep
10
+ appendix opens with "both consume the same RBS," this one opens
11
+ with the inverse: both *produce* type information from plain
12
+ Ruby. The interesting differences are in *how* they infer and
13
+ *what they do with the result*.
14
+
15
+ This appendix is for users who already think in TypeProf terms
16
+ and want to know which Rigor concept matches which TypeProf
17
+ concept.
18
+
19
+ ## The five-second pitch
20
+
21
+ | Question | TypeProf | Rigor |
22
+ | --- | --- | --- |
23
+ | Primary job | Generate RBS prototypes from Ruby | Check Ruby for provable bugs (`rigor check`) |
24
+ | Needs `.rbs` to start? | No — infers from `.rb` | No — infers from `.rb` |
25
+ | Inference strategy | Whole-program abstract interpretation ("type-level execution") from entry points | Local, per-method inference with budgets + a catalog at boundaries |
26
+ | Scale target | Small files / a prototyping pass | Whole codebase, incrementally, cached |
27
+ | Default output | RBS signatures (+ some errors as a side effect) | Diagnostics (+ RBS on demand via `sig-gen`) |
28
+ | Diagnostic philosophy | Report what abstract interpretation stumbles on | Stay silent unless the bug is provable |
29
+ | Literal precision | Widens to nominal in output (`1` → `Integer`) | Keeps `Constant<1>`, refinements, `IntegerRange` |
30
+
31
+ If TypeProf's slogan is "run your Ruby at the type level and
32
+ write down what came back," Rigor's is "prove what you can,
33
+ flag only that, and scale it." The two overlap most exactly at
34
+ one feature — `rigor sig-gen` (Chapter 11) does the job
35
+ TypeProf's CLI was built for.
36
+
37
+ ## Both infer without annotations — that is the common ground
38
+
39
+ TypeProf and Rigor are the two Ruby
40
+ tools that give you something useful on a `lib/` directory
41
+ with **zero `.rbs` files**. Steep, by contrast, expects the
42
+ signatures up front. So if you came to Rigor from TypeProf,
43
+ the "no annotations needed" stance (Chapter 1) will already
44
+ feel familiar — it is the assumption you have been working
45
+ under all along.
46
+
47
+ ```ruby
48
+ # slug.rb — no sig/ directory anywhere
49
+ class Slug
50
+ def normalise(raw)
51
+ raw.strip.downcase
52
+ end
53
+ end
54
+ ```
55
+
56
+ TypeProf abstractly interprets `normalise`, sees `String#strip`
57
+ then `String#downcase`, and (given a call site that passes a
58
+ `String`) emits `def normalise: (String) -> String`. Rigor
59
+ infers the same body locally and, under `sig-gen`, emits the
60
+ same signature — while *also* knowing internally that the
61
+ result is a `non-empty?`-unconstrained but lower-cased string
62
+ carrier, which it erases to `String` at the boundary.
63
+
64
+ Where they diverge:
65
+
66
+ - **TypeProf** follows the call graph. To learn that `raw` is
67
+ a `String`, it needs to *see a call* that passes one — so
68
+ TypeProf is most useful when pointed at a program with an
69
+ entry point (or a small harness that exercises the methods).
70
+ - **Rigor** infers each method on its own terms against a
71
+ catalog of core/stdlib/gem types, bounded by inference
72
+ budgets, and falls back to `Dynamic[top]` for a parameter it
73
+ cannot pin — no entry point or harness required.
74
+
75
+ ## Type vocabulary — TypeProf output vs Rigor carriers
76
+
77
+ TypeProf emits RBS, so its *output* vocabulary is RBS. Rigor
78
+ reads and erases to the same RBS. The difference is internal
79
+ precision: TypeProf widens literals on the way out; Rigor
80
+ keeps a richer carrier and only erases at the boundary.
81
+
82
+ | Ruby expression | TypeProf output | Rigor internal (display) |
83
+ | --- | --- | --- |
84
+ | `1` | `Integer` | `Constant<1>` (erases: `Integer`) |
85
+ | `"foo".upcase` | `String` | `Constant<"FOO">` (erases: `String`) |
86
+ | `[1, "a"]` | `[Integer, String]` | `Tuple[Constant<1>, Constant<"a">]` |
87
+ | `{name: "x", age: 1}` | `{name: String, age: Integer}` | `HashShape{name: …, age: …}` |
88
+ | `x` unknown | `untyped` | `Dynamic[top]` (display: `untyped`) |
89
+ | `nil`-or-`Integer` | `Integer?` | `Integer \| Constant<nil>` (display: `Integer?`) |
90
+ | a `> 0`-guarded int | `Integer` | `positive-int` refinement (erases: `Integer`) |
91
+
92
+ The practical upshot: feed the same file to both, and
93
+ TypeProf's RBS and `rigor sig-gen`'s RBS will usually agree at
94
+ the *declaration* level, because Rigor's
95
+ [RBS erasure](../type-specification/rbs-erasure.md) deliberately
96
+ discards exactly the extra precision (`Constant`, `Refined`,
97
+ `IntegerRange`) that TypeProf never tracked. Inside the
98
+ analyzer, Rigor is carrying more — which is what powers its
99
+ refinement-carrier narrowing and constant-folding diagnostics.
100
+ (TypeProf does its own flow-sensitive narrowing on type-identity
101
+ predicates like `is_a?` / `nil?`; what it does not carry is the
102
+ *value-predicate refinement* layer or the curated diagnostics
103
+ Rigor produces from it.)
104
+
105
+ ## Analysis model — the largest conceptual difference
106
+
107
+ This is where the tools part ways.
108
+
109
+ **TypeProf is a whole-program abstract interpreter.** It walks
110
+ the program from its entry points and *executes it at the type
111
+ level*: every method call propagates abstract argument types
112
+ to the callee, the callee's body is interpreted, and return
113
+ types flow back. This is inter-procedural and remarkably
114
+ precise on small programs — TypeProf can infer a method's
115
+ parameter types purely from how it is *called*, which neither
116
+ Steep nor Rigor attempts.
117
+
118
+ The cost is scale. Abstract interpretation of a whole program
119
+ is expensive, and the analysis can blow up combinatorially on
120
+ large or highly polymorphic codebases. TypeProf is explicitly
121
+ positioned as a tool for small programs, single files, or a
122
+ *starting-point* RBS pass — not a checker you run over a
123
+ 100k-line app on every save.
124
+
125
+ **Rigor is a local, budgeted inferrer with a catalog at the
126
+ boundary.** It infers one method at a time. When that method
127
+ calls another, Rigor consults a *catalog* of known return
128
+ types (core, stdlib, gem RBS, plugin contributions) rather
129
+ than re-interpreting the callee's body. Calls it cannot
130
+ resolve become `Dynamic[top]` and stop there. This is less
131
+ precise than TypeProf on a self-contained toy program — Rigor
132
+ will not deduce a parameter type from a single call site the
133
+ way TypeProf can — but it is bounded by
134
+ [inference budgets](../type-specification/inference-budgets.md)
135
+ and backed by per-file caching (ADR-6), so it stays usable on
136
+ a whole codebase and across runs.
137
+
138
+ | | TypeProf | Rigor |
139
+ | --- | --- | --- |
140
+ | Unit of analysis | Whole program from entry points | One method at a time |
141
+ | Cross-method types | Re-interprets the callee body | Looks up the callee in a catalog |
142
+ | Infers params from call sites? | Yes (its signature move) | No (params default to `Dynamic[top]`) |
143
+ | Bounded? | By practical blow-up limits | By explicit inference budgets |
144
+ | Incremental / cached? | TypeProf 2 / `--lsp` improves this | Per-file cache across runs + machines |
145
+
146
+ The trade is intentional: TypeProf buys precision on
147
+ small inputs with whole-program interpretation; Rigor buys
148
+ scale and silence with local inference and a catalog.
149
+
150
+ ## RBS generation — `typeprof` CLI vs `rigor sig-gen`
151
+
152
+ This is the one feature where the tools do the *same job*, and
153
+ it is worth a direct comparison. Generating RBS from Ruby is
154
+ TypeProf's entire reason to exist; for Rigor it is one
155
+ secondary command, [Chapter 11](11-sig-gen.md).
156
+
157
+ | | `typeprof foo.rb` | `rigor sig-gen` |
158
+ | --- | --- | --- |
159
+ | Output | RBS to stdout | RBS via `--print` / `--diff` / `--write` |
160
+ | Param types | Inferred from call sites | Conservative; `--params` policy, ADR-5 trade-off |
161
+ | Existing sigs | Regenerates from scratch | `new-file` / `new-method` / `tighter-return` classification; never overwrites a tighter human return |
162
+ | Literal precision in output | Widened to nominal | Erased to RBS (same widening at the boundary) |
163
+ | Driving signal it feeds back | A one-shot prototype to hand-edit | Gaps in `sig-gen` are themselves the signal Rigor should infer better |
164
+
165
+ A note specific to *this* repository: the project's standing
166
+ policy (AGENTS.md § "RBS Authorship") is to prefer
167
+ `rigor sig-gen` over hand- or AI-authored RBS, precisely
168
+ *because* a gap in `sig-gen` output is more valuable feedback
169
+ than a patched-over signature. If you are used to running
170
+ `typeprof` and pasting its output into `sig/`, the Rigor
171
+ analogue is `rigor sig-gen --diff` — but the mindset shifts
172
+ from "scaffold then edit" to "if the scaffold is wrong, fix
173
+ the inference."
174
+
175
+ ## Tests as inference fuel — the bidirectional question
176
+
177
+ A natural follow-on to the analysis-model section: if TypeProf
178
+ learns parameter types from call sites, and tests are nothing
179
+ *but* call sites, how do the two tools use your `spec/` (or
180
+ `test/`) suite? There are two directions, and the tools
181
+ differ on each.
182
+
183
+ ### Direction 1 — tests → method types
184
+
185
+ Tests are a pile of concrete examples of how a method is
186
+ called, so they are evidence for parameter types. This is
187
+ where the analysis models show through most sharply.
188
+
189
+ - **TypeProf fuels on call sites — and a test is just one
190
+ kind of call site.** TypeProf has no concept of "a test." It
191
+ infers parameters by abstractly interpreting the whole
192
+ program, so *any* call it can see feeds a parameter type:
193
+ top-level code, an example under `__END__`, a throwaway
194
+ driver, or a line in a spec are all the same to it. A call
195
+ that runs `Foo.new.bar(42)` — wherever it lives — is how
196
+ TypeProf learns `bar` takes an `Integer`; with no such call,
197
+ the parameter stays `untyped`. Tests happen to be a rich
198
+ *source* of call sites (so pointing TypeProf at exercising
199
+ code, including a suite, helps), but TypeProf neither
200
+ recognises nor privileges them as tests.
201
+ - **Rigor does not read tests for `rigor check`.** Its local
202
+ model leaves un-narrowed parameters at `Dynamic[top]`; the
203
+ bug-finding gate never consults `spec/` to tighten them.
204
+ Tests become a parameter signal only in the opt-in
205
+ `rigor sig-gen --params=observed --observe=spec/` path
206
+ ([Chapter 11](11-sig-gen.md)), which unions the observed
207
+ argument type per position across every call site.
208
+
209
+ So the contrast is sharp: TypeProf interprets a spec as
210
+ ordinary Ruby (the `it` blocks are just more call sites),
211
+ whereas Rigor's sig-gen collector models the RSpec DSL
212
+ *structurally* — `described_class`, `subject`, and `let` are
213
+ recognised as bindings, not just executed:
214
+
215
+ ```ruby
216
+ RSpec.describe Calc do
217
+ subject { Calc.new } # :subject → Nominal[Calc]
218
+ let(:other) { Calc.new } # :other → Nominal[Calc]
219
+ it { subject.greet("Alice") } # observed: Calc#greet receives String
220
+ it { described_class.new.add(1, 2) } # observed: Calc#add receives Integer, Integer
221
+ end
222
+ ```
223
+
224
+ ### Direction 2 — method types → tests
225
+
226
+ The reverse flow: once signatures land in `sig/`, they make
227
+ the *spec itself* checkable. `rigor check` (and the
228
+ `rigor-rspec` plugin) types `subject` / `let` bodies against
229
+ the real return types and checks matchers, which sharpens the
230
+ next `sig-gen` run, closing the loop. (Note the two RSpec
231
+ machines are separate: sig-gen's collector is built in and
232
+ needs no plugin; `rigor-rspec` is the standalone diagnostic
233
+ analyser. They run side by side, not shared.)
234
+
235
+ ### The decisive difference — ADR-5
236
+
237
+ Observation-derived parameters are almost always **too
238
+ narrow**: a method only ever exercised with `String` in the
239
+ suite looks like it takes `(String)` even when it accepts far
240
+ more. The tools split on what to do about that.
241
+
242
+ - **TypeProf emits the observed-narrow parameter** — its job
243
+ is to report what the type-level run saw.
244
+ - **Rigor refuses to make that the default.** Under the
245
+ [robustness principle](../type-specification/robustness-principle.md)
246
+ (strict returns, lenient parameters), `--params=observed` is
247
+ a deliberate opt-in and its output is a *suggestion to
248
+ review*, not a frozen contract. The default `untyped` is the
249
+ stance that the suite's current usage should not silently
250
+ become every future caller's obligation.
251
+
252
+ | | TypeProf | Rigor |
253
+ | --- | --- | --- |
254
+ | Role of tests | No special role — a test is just one more call site that fuels inference | Opt-in fuel for `sig-gen` only (never the `check` gate) |
255
+ | How a spec is read | Executed as ordinary Ruby (not recognised as a test) | `subject` / `let` / `described_class` recognised structurally |
256
+ | Observed-narrow params | Emitted as-is | Treated as a reviewable suggestion (ADR-5, opt-in, human gate) |
257
+ | Bidirectional loop | arg ↔ return within one pass | spec → sig → spec-checking → sharper sig |
258
+
259
+ Two honest limits on the Rigor side (both in Chapter 11):
260
+ same-name `let` bindings nested across `describe` / `context`
261
+ are last-wins, and `before` / `around` hook bodies are not
262
+ consulted for binding mutations.
263
+
264
+ ## Diagnostics — a side effect vs the main product
265
+
266
+ TypeProf reports some errors as it interprets — an undefined
267
+ method, an obviously impossible operation — but these are a
268
+ *byproduct* of the inference pass, not a curated linter. They
269
+ can be noisy, and TypeProf is not pitched as a bug-finding gate.
270
+
271
+ Rigor inverts this. The diagnostic stream *is* the product,
272
+ and the governing rule (ADR-0, and the
273
+ [false-positive discipline](08-understanding-errors.md) the
274
+ whole tool is built around) is to stay silent unless the bug
275
+ is provable. Running `rigor check lib` yields a small,
276
+ high-confidence set of findings, not a transcript of
277
+ everything the interpreter found surprising.
278
+
279
+ So the mental shift coming from TypeProf is: do not expect
280
+ Rigor's output to be "the types it found" (use `sig-gen` for
281
+ that). Expect it to be "the small number of places the code is
282
+ provably wrong."
283
+
284
+ ## Invocation
285
+
286
+ | TypeProf | Rigor |
287
+ | --- | --- |
288
+ | `typeprof app.rb` | `rigor sig-gen --print lib/app.rb` (for RBS) / `rigor check lib` (for bugs) |
289
+ | `typeprof -I sig app.rb` (load RBS) | `signature_paths:` in `.rigor.yml` (auto-detected) |
290
+ | `typeprof --lsp` (TypeProf for IDE) | `rigor lsp` (ADR-19) |
291
+ | harness file to drive call-site inference | not needed — local inference per method |
292
+ | core/stdlib RBS loaded automatically | catalog of core/stdlib types loaded automatically |
293
+
294
+ Both tools read gem RBS via the standard `rbs-collection`
295
+ mechanism, the same one Steep uses.
296
+
297
+ ## What TypeProf has and Rigor does not
298
+
299
+ - **Parameter inference from call sites.** TypeProf's
300
+ whole-program interpretation lets it deduce a method's
301
+ *parameter* types from how the method is called. Rigor does
302
+ not — an un-annotated, un-narrowed parameter is
303
+ `Dynamic[top]` until a `.rbs` or a guard says otherwise.
304
+ - **True inter-procedural body interpretation.** TypeProf
305
+ re-interprets callee bodies; Rigor looks callees up in a
306
+ catalog. On a small self-contained program TypeProf can
307
+ follow data flow further than Rigor will.
308
+ - **First-class "infer the whole signature" output.** It is
309
+ TypeProf's primary product, with years of tuning aimed
310
+ squarely at it. `rigor sig-gen` is deliberately more
311
+ conservative (especially on parameters, per ADR-5).
312
+
313
+ ## What Rigor has and TypeProf does not
314
+
315
+ - **Refinement carriers from value predicates.** Both tools do
316
+ flow-sensitive *occurrence typing* on type-identity predicates
317
+ (`is_a?`, `nil?`, `case`/`when`) — TypeProf included. What
318
+ Rigor adds is **refinement carriers**: a *value* predicate
319
+ like `unless s.empty?` or `n > 0` narrows into a named
320
+ refinement type (`non-empty-string`, `positive-int`). TypeProf
321
+ has no refinement-carrier concept, so those value-predicate
322
+ refinements widen back to `String` / `Integer`.
323
+ - **Constant folding through method calls.** `"foo".upcase`
324
+ resolves to `Constant<"FOO">`. TypeProf's output is
325
+ `String`.
326
+ - **A curated, false-positive-disciplined diagnostic gate.**
327
+ Rigor is a checker first; TypeProf's errors are a byproduct.
328
+ - **Whole-codebase scale with caching.** Inference budgets
329
+ plus a per-file cache (ADR-6) keep `rigor check` usable on a
330
+ large app on every run; TypeProf is positioned for smaller
331
+ inputs.
332
+ - **RBS::Extended directives.** `%a{rigor:v1:…}` refinement /
333
+ predicate / assertion grammar (Chapter 7) has no TypeProf
334
+ analogue.
335
+ - **The plugin ecosystem.** Sorbet-input adapter, Rails-side
336
+ narrowing, `dynamic_return` return-type
337
+ contributions — extension surfaces TypeProf does not model.
338
+
339
+ ## A coexistence pattern
340
+
341
+ The two tools compose naturally because they sit at different
342
+ points in the lifecycle:
343
+
344
+ 1. **Bootstrap with TypeProf (optional).** On a brand-new file
345
+ or small library with no sigs, `typeprof` can give you a
346
+ first RBS draft to read. (Or skip straight to step 2 —
347
+ Rigor does not need it.)
348
+ 2. **Check with Rigor.** Run `rigor check lib` for the
349
+ provable-bug gate. This is the day-to-day signal.
350
+ 3. **Generate sigs with `rigor sig-gen`** when you want RBS
351
+ that round-trips with Rigor's own inference and respects
352
+ the `tighter-return` no-overwrite rule.
353
+ 4. **Add Rigor to CI.** The checker gate runs on every push;
354
+ TypeProf stays a local, occasional prototyping aid.
355
+
356
+ The standing rule when their RBS disagrees: TypeProf may infer
357
+ a *narrower parameter* from a call site that Rigor reports as
358
+ `untyped`. That is not a Rigor bug; it is the local-vs-whole-
359
+ program trade. If you want Rigor to honour that narrower
360
+ parameter, write it into `sig/` (both tools then read it) or
361
+ add a guard the engine can narrow on.
362
+
363
+ ## A migration vignette
364
+
365
+ Suppose you have been using TypeProf to bootstrap `sig/` for a
366
+ mid-sized gem: you run `typeprof lib/**/*.rb`, hand-edit the
367
+ prototypes, and commit them. You want Rigor's bug-finding on
368
+ top.
369
+
370
+ Steps:
371
+
372
+ 1. **Keep your generated `sig/`.** Rigor reads it as input
373
+ exactly like Steep would — TypeProf-authored RBS is just
374
+ RBS.
375
+ 2. **Run `rigor check lib` once.** Expect a *different* kind
376
+ of output than TypeProf gave you: not signatures, but a
377
+ short list of provable findings — narrowing-aware
378
+ diagnostics (`flow.always-truthy-condition`), return
379
+ mismatches against your committed sigs
380
+ (`def.return-type-mismatch`). Triage as bugs vs noise.
381
+ 3. **Switch your RBS-generation step to `rigor sig-gen`.**
382
+ Where you previously re-ran `typeprof` and re-edited, run
383
+ `rigor sig-gen --diff` instead. The classification model
384
+ (`new-file` / `new-method` / `tighter-return`) means it
385
+ will *not* clobber a parameter type you hand-tightened.
386
+ 4. **Optionally tighten sigs with `RBS::Extended`.** TypeProf
387
+ treats `%a{rigor:v1:…}` as ordinary RBS comments and
388
+ ignores them; Rigor reads them as refinement directives.
389
+ The same `.rbs` file produces stricter Rigor output and
390
+ unchanged TypeProf output.
391
+
392
+ The migration is low-friction because the shared assumption —
393
+ RBS as the interchange format, inference as the default — is
394
+ exactly the one TypeProf trained you on.
395
+
396
+ ## What's next
397
+
398
+ You probably do not need to read the rest of this appendix
399
+ section sequentially. Three useful pointers:
400
+
401
+ - [Chapter 11 — Generating RBS with `rigor sig-gen`](11-sig-gen.md)
402
+ — the feature that does TypeProf's job inside Rigor, with
403
+ the no-overwrite classification model.
404
+ - [Chapter 8 — Understanding errors](08-understanding-errors.md)
405
+ for the diagnostic gate that is Rigor's product, the thing
406
+ TypeProf does not set out to be.
407
+ - [`docs/type-specification/inference-budgets.md`](../type-specification/inference-budgets.md)
408
+ for the budget model that lets local inference scale where
409
+ whole-program interpretation does not.
410
+
411
+ If you want to compare against another tool, the sibling
412
+ appendix pages cover [Steep](appendix-steep.md) — Ruby's other
413
+ static checker — plus [TypeScript](appendix-typescript.md),
414
+ [PHPStan](appendix-phpstan.md), [mypy](appendix-mypy.md),
415
+ [Java / C#](appendix-java-csharp.md), [Rust](appendix-rust.md),
416
+ [Go](appendix-go.md), and [Elixir](appendix-elixir.md).