rigortype 0.3.9 → 0.4.1

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 (407) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +5 -4
  3. data/data/core_overlay/enumerable.rbs +51 -0
  4. data/data/core_overlay/enumerator.rbs +84 -0
  5. data/data/core_overlay/hash_rbs3.rbs +41 -0
  6. data/data/core_overlay/process.rbs +40 -0
  7. data/data/core_overlay/string_io.rbs +33 -0
  8. data/data/effects/core.yml +3 -3
  9. data/data/gem_overlay/activesupport/core_ext.rbs +127 -4
  10. data/docs/handbook/03-narrowing.md +95 -10
  11. data/docs/handbook/04-tuples-and-shapes.md +8 -6
  12. data/docs/handbook/07-rbs-and-extended.md +16 -11
  13. data/docs/handbook/08-understanding-errors.md +3 -2
  14. data/docs/handbook/10-sorbet.md +9 -10
  15. data/docs/handbook/11-sig-gen.md +454 -13
  16. data/docs/install.md +87 -15
  17. data/docs/manual/02-cli-reference.md +65 -4
  18. data/docs/manual/03-configuration.md +12 -0
  19. data/docs/manual/04-diagnostics.md +4 -0
  20. data/docs/manual/07-plugins.md +1 -1
  21. data/docs/manual/08-skills.md +4 -3
  22. data/docs/manual/10-mcp-server.md +3 -2
  23. data/docs/manual/12-caching.md +43 -4
  24. data/docs/manual/14-rails-quickstart.md +19 -11
  25. data/docs/manual/16-rbs-extended-annotations.md +40 -7
  26. data/docs/manual/19-effect-labels.md +10 -0
  27. data/docs/manual/plugins/README.md +7 -0
  28. data/docs/manual/plugins/rigor-actioncable.md +8 -1
  29. data/docs/manual/plugins/rigor-actionmailer.md +7 -0
  30. data/docs/manual/plugins/rigor-actionpack.md +243 -0
  31. data/docs/manual/plugins/rigor-active-model-serializers.md +153 -0
  32. data/docs/manual/plugins/rigor-activejob.md +7 -0
  33. data/docs/manual/plugins/rigor-activerecord.md +110 -4
  34. data/docs/manual/plugins/rigor-activestorage.md +8 -1
  35. data/docs/manual/plugins/rigor-activesupport-core-ext.md +11 -5
  36. data/docs/manual/plugins/rigor-grape.md +106 -0
  37. data/docs/manual/plugins/rigor-graphql.md +23 -2
  38. data/docs/manual/plugins/rigor-pundit.md +8 -1
  39. data/docs/manual/plugins/rigor-rails-i18n.md +10 -5
  40. data/docs/manual/plugins/rigor-rails-routes.md +7 -0
  41. data/docs/manual/plugins/rigor-rbs-inline.md +212 -25
  42. data/docs/manual/plugins/rigor-sidekiq.md +8 -1
  43. data/docs/manual/plugins/rigor-sorbet.md +21 -5
  44. data/exe/rigor +19 -4
  45. data/lib/rigor/analysis/baseline.rb +1 -1
  46. data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +167 -2
  47. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +189 -0
  48. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -3
  49. data/lib/rigor/analysis/check_rules/rule_ids.rb +20 -5
  50. data/lib/rigor/analysis/check_rules/rule_walk.rb +2 -1
  51. data/lib/rigor/analysis/check_rules/source_arity.rb +427 -0
  52. data/lib/rigor/analysis/check_rules/special_global_setters.rb +146 -0
  53. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +36 -2
  54. data/lib/rigor/analysis/check_rules.rb +638 -136
  55. data/lib/rigor/analysis/dependency_recorder.rb +222 -17
  56. data/lib/rigor/analysis/fact_store.rb +9 -0
  57. data/lib/rigor/analysis/incremental.rb +26 -0
  58. data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +266 -0
  59. data/lib/rigor/analysis/incremental_run_slot.rb +171 -0
  60. data/lib/rigor/analysis/incremental_session.rb +309 -53
  61. data/lib/rigor/analysis/plugin_fact_fingerprint.rb +24 -13
  62. data/lib/rigor/analysis/project_scan.rb +11 -1
  63. data/lib/rigor/analysis/reachability/plugin_roots.rb +0 -1
  64. data/lib/rigor/analysis/reachability/scan_cache.rb +3 -1
  65. data/lib/rigor/analysis/rule_catalog.rb +127 -0
  66. data/lib/rigor/analysis/run_cache_key.rb +46 -18
  67. data/lib/rigor/analysis/run_stats.rb +2 -2
  68. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +168 -12
  69. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +9 -4
  70. data/lib/rigor/analysis/runner/pool_coordinator.rb +109 -17
  71. data/lib/rigor/analysis/runner/project_pre_passes.rb +44 -17
  72. data/lib/rigor/analysis/runner.rb +603 -54
  73. data/lib/rigor/analysis/shadow_harness.rb +27 -0
  74. data/lib/rigor/analysis/source_rbs_gate.rb +157 -0
  75. data/lib/rigor/analysis/template_unit_collector.rb +303 -0
  76. data/lib/rigor/analysis/template_unit_paths.rb +93 -0
  77. data/lib/rigor/analysis/template_unit_positions.rb +293 -0
  78. data/lib/rigor/analysis/template_units.rb +409 -0
  79. data/lib/rigor/analysis/worker_session.rb +57 -13
  80. data/lib/rigor/bleeding_edge.rb +0 -2
  81. data/lib/rigor/builtins/hkt_builtins.rb +1 -0
  82. data/lib/rigor/builtins/imported_refinements.rb +4 -0
  83. data/lib/rigor/builtins/regex_refinement.rb +17 -9
  84. data/lib/rigor/builtins/static_return_refinements.rb +2 -0
  85. data/lib/rigor/cache/descriptor.rb +45 -1
  86. data/lib/rigor/cache/engine_source.rb +29 -1
  87. data/lib/rigor/cache/file_digest.rb +28 -0
  88. data/lib/rigor/cache/incremental_snapshot.rb +102 -8
  89. data/lib/rigor/cache/rbs_cache_producer.rb +4 -0
  90. data/lib/rigor/cache/rbs_descriptor.rb +37 -7
  91. data/lib/rigor/cache/store.rb +63 -4
  92. data/lib/rigor/cache/value_digest.rb +325 -0
  93. data/lib/rigor/ci_detector.rb +1 -0
  94. data/lib/rigor/cli/check_command.rb +83 -13
  95. data/lib/rigor/cli/doc_links.rb +1 -1
  96. data/lib/rigor/cli/docs_command.rb +4 -4
  97. data/lib/rigor/cli/doctor_command.rb +1 -1
  98. data/lib/rigor/cli/effects_report.rb +2 -2
  99. data/lib/rigor/cli/plugin_command.rb +3 -3
  100. data/lib/rigor/cli/prism_colorizer.rb +0 -1
  101. data/lib/rigor/cli/sig_gen_command.rb +210 -29
  102. data/lib/rigor/cli/skill_command.rb +1 -1
  103. data/lib/rigor/cli/skill_describe.rb +0 -1
  104. data/lib/rigor/cli/type_of_command.rb +19 -5
  105. data/lib/rigor/cli/type_of_renderer.rb +20 -9
  106. data/lib/rigor/cli/type_of_template_probe.rb +189 -0
  107. data/lib/rigor/cli/unused_command.rb +3 -3
  108. data/lib/rigor/cli.rb +21 -3
  109. data/lib/rigor/config_audit.rb +3 -2
  110. data/lib/rigor/config_path.rb +29 -0
  111. data/lib/rigor/configuration/severity_profile.rb +24 -3
  112. data/lib/rigor/configuration.rb +71 -7
  113. data/lib/rigor/effects/ancestry_recorder.rb +191 -0
  114. data/lib/rigor/effects/attribution.rb +11 -2
  115. data/lib/rigor/effects/callee_rule.rb +368 -0
  116. data/lib/rigor/effects/catalog.rb +7 -4
  117. data/lib/rigor/effects/collector.rb +11 -5
  118. data/lib/rigor/effects/config_envelopes.rb +9 -2
  119. data/lib/rigor/effects/definition_context.rb +179 -0
  120. data/lib/rigor/effects/effect_table.rb +11 -3
  121. data/lib/rigor/effects/envelope_check.rb +1 -1
  122. data/lib/rigor/effects/envelope_index.rb +15 -0
  123. data/lib/rigor/effects/file_collection.rb +59 -5
  124. data/lib/rigor/effects/framework_units.rb +1 -1
  125. data/lib/rigor/effects/identity.rb +16 -0
  126. data/lib/rigor/effects/local_ownership.rb +38 -12
  127. data/lib/rigor/effects/method_key.rb +21 -0
  128. data/lib/rigor/effects/mutation_classifier.rb +23 -12
  129. data/lib/rigor/effects/plugin_facts.rb +49 -37
  130. data/lib/rigor/effects/propagator.rb +295 -16
  131. data/lib/rigor/effects/registry.rb +1 -1
  132. data/lib/rigor/effects/scanner.rb +121 -72
  133. data/lib/rigor/effects/signature_sources.rb +1 -1
  134. data/lib/rigor/effects/snapshot.rb +2 -1
  135. data/lib/rigor/effects/summary.rb +27 -4
  136. data/lib/rigor/effects/unit_scan.rb +385 -30
  137. data/lib/rigor/effects/visibility.rb +101 -0
  138. data/lib/rigor/environment/bundle_sig_discovery.rb +5 -3
  139. data/lib/rigor/environment/lockfile_resolver.rb +27 -1
  140. data/lib/rigor/environment/member_consistency/comparator.rb +708 -0
  141. data/lib/rigor/environment/member_consistency.rb +298 -0
  142. data/lib/rigor/environment/rbs_collection_discovery.rb +10 -1
  143. data/lib/rigor/environment/rbs_loader.rb +400 -134
  144. data/lib/rigor/environment/source_rbs_synthesis.rb +147 -0
  145. data/lib/rigor/environment.rb +104 -96
  146. data/lib/rigor/hashing/xxh3.rb +264 -0
  147. data/lib/rigor/inference/acceptance.rb +139 -9
  148. data/lib/rigor/inference/block_auto_splat.rb +216 -0
  149. data/lib/rigor/inference/block_call_timing.rb +338 -0
  150. data/lib/rigor/inference/block_parameter_binder.rb +88 -26
  151. data/lib/rigor/inference/block_repetition.rb +71 -0
  152. data/lib/rigor/inference/body_fixpoint.rb +2 -1
  153. data/lib/rigor/inference/budget_trace.rb +2 -1
  154. data/lib/rigor/inference/builtins/method_catalog.rb +2 -1
  155. data/lib/rigor/inference/builtins/string_catalog.rb +1 -1
  156. data/lib/rigor/inference/captured_locals.rb +393 -15
  157. data/lib/rigor/inference/closure_escape_analyzer.rb +157 -13
  158. data/lib/rigor/inference/content_join.rb +200 -27
  159. data/lib/rigor/inference/declaration_walk/context.rb +195 -0
  160. data/lib/rigor/inference/declaration_walk/errors.rb +18 -0
  161. data/lib/rigor/inference/declaration_walk/shadow.rb +147 -0
  162. data/lib/rigor/inference/declaration_walk/traversal.rb +441 -0
  163. data/lib/rigor/inference/declaration_walk.rb +7 -0
  164. data/lib/rigor/inference/def_node_resolver.rb +3 -2
  165. data/lib/rigor/inference/def_return_typer.rb +11 -7
  166. data/lib/rigor/inference/define_method_block_self.rb +64 -0
  167. data/lib/rigor/inference/definer_resolution.rb +266 -0
  168. data/lib/rigor/inference/element_read_widening.rb +22 -10
  169. data/lib/rigor/inference/error_info.rb +196 -0
  170. data/lib/rigor/inference/expression_typer.rb +1665 -538
  171. data/lib/rigor/inference/external_ancestor_resolution.rb +264 -0
  172. data/lib/rigor/inference/fresh_frame_blocks.rb +127 -0
  173. data/lib/rigor/inference/global_write_census.rb +239 -0
  174. data/lib/rigor/inference/guard_rebinding.rb +488 -0
  175. data/lib/rigor/inference/hash_lookup_mutation.rb +88 -0
  176. data/lib/rigor/inference/index_write_widening.rb +16 -3
  177. data/lib/rigor/inference/indexed_narrowing.rb +61 -9
  178. data/lib/rigor/inference/jump_targets.rb +82 -0
  179. data/lib/rigor/inference/last_line/implicit_self.rb +210 -0
  180. data/lib/rigor/inference/last_line/self_evidence.rb +329 -0
  181. data/lib/rigor/inference/last_line.rb +340 -0
  182. data/lib/rigor/inference/last_status.rb +144 -0
  183. data/lib/rigor/inference/macro_block_self_type.rb +179 -17
  184. data/lib/rigor/inference/match_rebinding/calls.rb +281 -0
  185. data/lib/rigor/inference/match_rebinding/frame.rb +148 -0
  186. data/lib/rigor/inference/match_rebinding/operands.rb +236 -0
  187. data/lib/rigor/inference/match_rebinding/self_calls.rb +83 -0
  188. data/lib/rigor/inference/match_rebinding.rb +392 -0
  189. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
  190. data/lib/rigor/inference/method_dispatcher/block_folding.rb +85 -24
  191. data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +147 -0
  192. data/lib/rigor/inference/method_dispatcher/hash_transform_keys_folding.rb +191 -0
  193. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +4 -0
  194. data/lib/rigor/inference/method_dispatcher/match_data_folding.rb +159 -0
  195. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +113 -73
  196. data/lib/rigor/inference/method_dispatcher/process_folding.rb +56 -0
  197. data/lib/rigor/inference/method_dispatcher/proven_overload.rb +72 -0
  198. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +901 -38
  199. data/lib/rigor/inference/method_dispatcher/self_substitute.rb +142 -0
  200. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +128 -53
  201. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +1 -1
  202. data/lib/rigor/inference/method_dispatcher.rb +214 -13
  203. data/lib/rigor/inference/method_parameter_binder.rb +8 -3
  204. data/lib/rigor/inference/module_function_state.rb +213 -0
  205. data/lib/rigor/inference/multi_target_binder.rb +345 -52
  206. data/lib/rigor/inference/mutation_rejoin.rb +4 -1
  207. data/lib/rigor/inference/mutation_widening.rb +70 -48
  208. data/lib/rigor/inference/narrowing.rb +674 -124
  209. data/lib/rigor/inference/operand_effects.rb +167 -0
  210. data/lib/rigor/inference/operand_walk.rb +88 -0
  211. data/lib/rigor/inference/optimistic_origin.rb +152 -9
  212. data/lib/rigor/inference/parameter_inference_collector.rb +13 -11
  213. data/lib/rigor/inference/project_method_ownership.rb +136 -0
  214. data/lib/rigor/inference/project_patched_methods.rb +7 -2
  215. data/lib/rigor/inference/project_patched_scanner.rb +7 -3
  216. data/lib/rigor/inference/receiver_alias.rb +90 -1
  217. data/lib/rigor/inference/receiver_blind_block.rb +219 -0
  218. data/lib/rigor/inference/refinement_mutation.rb +15 -11
  219. data/lib/rigor/inference/repeated_or_writes.rb +463 -0
  220. data/lib/rigor/inference/return_barrier.rb +54 -0
  221. data/lib/rigor/inference/rewrite_mutation.rb +120 -0
  222. data/lib/rigor/inference/scope_indexer/certainty.rb +176 -0
  223. data/lib/rigor/inference/scope_indexer/class_cvars_collector.rb +35 -0
  224. data/lib/rigor/inference/scope_indexer/def_nestings_collector.rb +47 -0
  225. data/lib/rigor/inference/scope_indexer/layered_def_nestings.rb +36 -0
  226. data/lib/rigor/inference/scope_indexer/member_layouts_collector.rb +61 -0
  227. data/lib/rigor/inference/scope_indexer/mixin_accumulator.rb +130 -0
  228. data/lib/rigor/inference/scope_indexer/superclasses_collector.rb +64 -0
  229. data/lib/rigor/inference/scope_indexer.rb +5913 -701
  230. data/lib/rigor/inference/singleton_hook_decline.rb +253 -0
  231. data/lib/rigor/inference/statement_evaluator.rb +3249 -497
  232. data/lib/rigor/inference/stored_block_call.rb +54 -0
  233. data/lib/rigor/inference/string_mutation.rb +44 -7
  234. data/lib/rigor/inference/unknown_store_widening.rb +200 -0
  235. data/lib/rigor/inference/unthreaded_rebinds.rb +282 -0
  236. data/lib/rigor/language_server/debouncer.rb +0 -1
  237. data/lib/rigor/language_server/diagnostic_publisher.rb +3 -2
  238. data/lib/rigor/language_server/hover_renderer.rb +3 -3
  239. data/lib/rigor/language_server/project_context.rb +5 -3
  240. data/lib/rigor/mcp/server.rb +2 -1
  241. data/lib/rigor/plugin/base.rb +196 -13
  242. data/lib/rigor/plugin/box_probe.rb +91 -0
  243. data/lib/rigor/plugin/bundled_catalog.rb +1 -1
  244. data/lib/rigor/plugin/effect_attribution.rb +58 -4
  245. data/lib/rigor/plugin/io_boundary.rb +82 -4
  246. data/lib/rigor/plugin/isolation.rb +1 -1
  247. data/lib/rigor/plugin/loader.rb +2 -1
  248. data/lib/rigor/plugin/macro/block_as_method.rb +45 -6
  249. data/lib/rigor/plugin/manifest.rb +71 -10
  250. data/lib/rigor/plugin/registry.rb +35 -1
  251. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +9 -3
  252. data/lib/rigor/plugin/template_unit.rb +196 -0
  253. data/lib/rigor/plugin/trust_policy.rb +3 -3
  254. data/lib/rigor/plugin.rb +1 -0
  255. data/lib/rigor/protection/discovery_seed.rb +20 -9
  256. data/lib/rigor/protection/kill_signature.rb +0 -1
  257. data/lib/rigor/protection/mutation_cache.rb +4 -5
  258. data/lib/rigor/rbs_extended.rb +27 -0
  259. data/lib/rigor/reflection/constant_ancestors.rb +146 -0
  260. data/lib/rigor/reflection/constant_path.rb +41 -13
  261. data/lib/rigor/reflection.rb +73 -107
  262. data/lib/rigor/scope/discovery_index.rb +351 -2
  263. data/lib/rigor/scope/resolution_chain/relevance.rb +165 -0
  264. data/lib/rigor/scope/resolution_chain.rb +884 -0
  265. data/lib/rigor/scope.rb +981 -198
  266. data/lib/rigor/sig_gen/alias_index.rb +289 -0
  267. data/lib/rigor/sig_gen/classification.rb +22 -5
  268. data/lib/rigor/sig_gen/declaration_equivalence.rb +127 -0
  269. data/lib/rigor/sig_gen/effect_annotation.rb +202 -0
  270. data/lib/rigor/sig_gen/generator.rb +580 -47
  271. data/lib/rigor/sig_gen/inline_declarations.rb +184 -0
  272. data/lib/rigor/sig_gen/method_candidate.rb +47 -3
  273. data/lib/rigor/sig_gen/observation_collector.rb +1 -1
  274. data/lib/rigor/sig_gen/renderer.rb +124 -9
  275. data/lib/rigor/sig_gen/skip_reason_catalog.rb +44 -1
  276. data/lib/rigor/sig_gen/write_result.rb +19 -3
  277. data/lib/rigor/sig_gen/writer.rb +166 -35
  278. data/lib/rigor/sig_gen.rb +3 -0
  279. data/lib/rigor/signature_path_audit.rb +1 -1
  280. data/lib/rigor/source/node_walker.rb +0 -3
  281. data/lib/rigor/source/parameter_envelope.rb +72 -0
  282. data/lib/rigor/source.rb +1 -0
  283. data/lib/rigor/type/combinator.rb +112 -12
  284. data/lib/rigor/type/difference.rb +1 -0
  285. data/lib/rigor/type/hash_shape.rb +1 -1
  286. data/lib/rigor/type/refined.rb +1 -0
  287. data/lib/rigor/version.rb +1 -1
  288. data/lib/rigor.rb +1 -0
  289. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/effects.rb +1 -0
  290. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +9 -5
  291. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +13 -4
  292. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +0 -1
  293. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +96 -0
  294. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/effects.rb +65 -8
  295. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +270 -0
  296. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/render_locals.rb +402 -0
  297. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +370 -0
  298. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_units.rb +132 -0
  299. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +239 -4
  300. data/plugins/rigor-actionpack/sig/action_view.rbs +43 -0
  301. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +220 -0
  302. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_index.rb +56 -0
  303. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +263 -0
  304. data/plugins/rigor-active-model-serializers/lib/rigor-active-model-serializers.rb +7 -0
  305. data/plugins/rigor-active-model-serializers/sig/active_model_serializers.rbs +45 -0
  306. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -0
  307. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +9 -5
  308. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +35 -3
  309. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +72 -12
  310. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +380 -3
  311. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +14 -6
  312. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +281 -35
  313. data/plugins/rigor-activerecord/sig/active_record/relation.rbs +206 -31
  314. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +25 -14
  315. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +9 -2
  316. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +171 -5
  317. data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +2 -0
  318. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +1 -0
  319. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/types.rb +1 -1
  320. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +10 -0
  321. data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +141 -0
  322. data/plugins/rigor-grape/lib/rigor-grape.rb +6 -0
  323. data/plugins/rigor-grape/sig/grape.rbs +263 -0
  324. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +72 -2
  325. data/plugins/rigor-graphql/sig/graphql.rbs +485 -0
  326. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +2 -0
  327. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +11 -7
  328. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +35 -37
  329. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +0 -1
  330. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -0
  331. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +1 -0
  332. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +9 -17
  333. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline/same_line_annotations.rb +149 -0
  334. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +217 -36
  335. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +1 -0
  336. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +9 -5
  337. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +0 -1
  338. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +46 -1
  339. data/plugins/rigor-sorbet/sig/sorbet.rbs +277 -0
  340. data/sig/rigor/analysis/baseline.rbs +69 -7
  341. data/sig/rigor/analysis/fact_store.rbs +1 -1
  342. data/sig/rigor/analysis/project_scan.rbs +74 -0
  343. data/sig/rigor/effects/config_envelopes.rbs +100 -0
  344. data/sig/rigor/effects/effect_table.rbs +60 -0
  345. data/sig/rigor/effects/envelope.rbs +97 -0
  346. data/sig/rigor/effects/envelope_index.rbs +33 -0
  347. data/sig/rigor/effects/file_collection.rbs +81 -0
  348. data/sig/rigor/effects/label.rbs +26 -0
  349. data/sig/rigor/effects/label_set.rbs +46 -0
  350. data/sig/rigor/effects/method_key.rbs +24 -0
  351. data/sig/rigor/effects/origin.rbs +50 -0
  352. data/sig/rigor/effects/plugin_facts.rbs +141 -0
  353. data/sig/rigor/effects/registry.rbs +68 -0
  354. data/sig/rigor/effects/summary.rbs +50 -0
  355. data/sig/rigor/effects/taint_cause.rbs +12 -0
  356. data/sig/rigor/environment.rbs +14 -10
  357. data/sig/rigor/inference/optimistic_origin.rbs +10 -0
  358. data/sig/rigor/inference.rbs +6 -4
  359. data/sig/rigor/plugin/additional_initializer.rbs +36 -0
  360. data/sig/rigor/plugin/base.rbs +30 -7
  361. data/sig/rigor/plugin/effect_ancestry.rbs +27 -0
  362. data/sig/rigor/plugin/effect_attribution.rbs +66 -0
  363. data/sig/rigor/plugin/effect_edge.rbs +33 -0
  364. data/sig/rigor/plugin/effect_entry_points.rbs +25 -0
  365. data/sig/rigor/plugin/io_boundary.rbs +2 -1
  366. data/sig/rigor/plugin/loader.rbs +3 -3
  367. data/sig/rigor/plugin/manifest.rbs +50 -11
  368. data/sig/rigor/plugin/protocol_contract.rbs +68 -0
  369. data/sig/rigor/plugin/registry.rbs +62 -1
  370. data/sig/rigor/plugin.rbs +1 -1
  371. data/sig/rigor/rbs_extended.rbs +1 -1
  372. data/sig/rigor/reflection.rbs +7 -6
  373. data/sig/rigor/scope.rbs +177 -20
  374. data/sig/rigor/sig_gen/skip_reason_catalog.rbs +14 -7
  375. data/sig/rigor/source.rbs +4 -4
  376. data/sig/rigor/testing.rbs +10 -4
  377. data/sig/rigor/type.rbs +6 -0
  378. data/sig/rigor.rbs +38 -20
  379. data/skills/rigor-ask/SKILL.md +8 -5
  380. data/skills/rigor-baseline-reduce/SKILL.md +4 -6
  381. data/skills/rigor-ci-setup/SKILL.md +17 -21
  382. data/skills/rigor-doctor/SKILL.md +24 -22
  383. data/skills/rigor-doctor/references/01-checks.md +97 -33
  384. data/skills/rigor-editor-setup/SKILL.md +6 -4
  385. data/skills/rigor-mcp-setup/SKILL.md +5 -4
  386. data/skills/rigor-monkeypatch-resolve/SKILL.md +5 -3
  387. data/skills/rigor-next-steps/SKILL.md +4 -2
  388. data/skills/rigor-plugin-author/SKILL.md +19 -23
  389. data/skills/rigor-plugin-author/references/01-plan-and-scaffold.md +9 -8
  390. data/skills/rigor-plugin-author/references/02-walker-and-types.md +12 -25
  391. data/skills/rigor-plugin-author/references/03-test-and-ship.md +6 -8
  392. data/skills/rigor-plugin-review/SKILL.md +6 -4
  393. data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +2 -1
  394. data/skills/rigor-plugin-tune/SKILL.md +4 -2
  395. data/skills/rigor-project-init/SKILL.md +46 -18
  396. data/skills/rigor-project-init/references/01-detect.md +13 -9
  397. data/skills/rigor-project-init/references/02-configure.md +40 -10
  398. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +14 -14
  399. data/skills/rigor-project-init/references/04-sig-uplift.md +27 -14
  400. data/skills/rigor-protection-uplift/SKILL.md +4 -6
  401. data/skills/rigor-rbs-setup/SKILL.md +4 -2
  402. data/skills/rigor-type-oracle/SKILL.md +4 -6
  403. data/skills/rigor-type-oracle/references/01-oracle-commands.md +12 -8
  404. data/skills/rigor-type-oracle/references/03-gap-protocol.md +0 -3
  405. data/skills/rigor-unused-adjudicate/SKILL.md +4 -2
  406. data/skills/rigor-upgrade/SKILL.md +13 -8
  407. metadata +132 -1
data/docs/install.md CHANGED
@@ -10,19 +10,42 @@ skill (`rigor-project-init` for a project that has never run Rigor).
10
10
  **Do not add Rigor to the project's `Gemfile`** — Rigor is a
11
11
  standalone tool, not a library.
12
12
 
13
+ **Install the latest release, not whatever is already on the
14
+ machine.** A `rigor` already on PATH, or a version manager that
15
+ resolves to an older release, is not a reason to stop early: Rigor
16
+ changes quickly, and the skills this guide hands off to are written
17
+ for the current release. Step 1 looks up the latest version; Step 2
18
+ installs it and Step 3 checks it.
19
+
13
20
  ---
14
21
 
15
22
  ## Step 1 — Detect the environment
16
23
 
17
- Run these four checks and note which tools are available:
24
+ Run these checks and note which tools are available:
18
25
 
19
26
  ```sh
20
27
  which mise # preferred — see Step 2A
21
28
  which asdf # fallback — see Step 2B
22
29
  ruby --version 2>/dev/null | head -1 # is Ruby 4.0 already on PATH?
23
- which docker # last resort — see Step 2D
30
+ which docker # last resort — see "Last resort — Docker"
31
+ rigor --version 2>/dev/null # an existing install, if any
32
+ ```
33
+
34
+ Then look up the latest Rigor release on RubyGems:
35
+
36
+ ```sh
37
+ curl -fsS https://rubygems.org/api/v1/versions/rigortype/latest.json
24
38
  ```
25
39
 
40
+ It prints `{"version":"X.Y.Z"}`. Below, `<LATEST>` stands for that
41
+ `X.Y.Z`: replace it with the version you got, never type `<LATEST>`
42
+ literally. Without `curl`, `gem search --remote --exact rigortype`
43
+ gives the same answer once Ruby is available. If neither works, tell
44
+ the user you could not determine the latest version instead of
45
+ guessing one.
46
+
47
+ An existing `rigor` older than `<LATEST>` still goes through Step 2;
48
+ tell the user which version they have and which you are installing.
26
49
  Then proceed to the **first** matching case below.
27
50
 
28
51
  ---
@@ -40,23 +63,51 @@ that can be committed alongside the code. Other contributors — and CI
40
63
  — run `mise install` to restore those versions with no `Gemfile`
41
64
  involvement.
42
65
 
43
- Run in the project root:
66
+ First ask mise which Rigor version it would pick:
67
+
68
+ ```sh
69
+ mise latest gem:rigortype
70
+ ```
71
+
72
+ If that is older than `<LATEST>`, mise is holding the newer release
73
+ back — usually its `minimum_release_age` quarantine, which hides
74
+ releases for a while after they are published (`mise ls-remote
75
+ gem:rigortype` then warns `1 newer gem:rigortype release hidden by
76
+ minimum_release_age` on stderr). A bare `mise use gem:rigortype`
77
+ would install the older version with no error, so this is the step where agents
78
+ silently end up on a stale Rigor. The quarantine is a supply-chain
79
+ safeguard, so do not bypass it on your own: tell the user both
80
+ versions and ask which to install. `mise settings get
81
+ minimum_release_age` tells you whose choice it is: an error ("not
82
+ set") means mise's built-in default is in effect and you can recommend
83
+ `<LATEST>`; a value means the user configured it, so recommend
84
+ respecting it.
85
+
86
+ Then run in the project root, with the version the user agreed to:
44
87
 
45
88
  ```sh
46
89
  mise use ruby@4.0
47
- mise use --pin gem:rigortype
90
+ mise use --pin gem:rigortype@<LATEST>
48
91
  ```
49
92
 
50
93
  `mise use` installs the tools and writes their versions to `mise.toml`
51
- in one step. Commit `mise.toml` so the version is shared.
94
+ in one step. Commit `mise.toml` so the version is shared. Name the
95
+ version explicitly, as above: an explicit version is installed even
96
+ while the quarantine hides it from `mise latest`.
52
97
 
53
- `--pin` records the exact Rigor version (`"gem:rigortype" = "0.2.9"`).
98
+ `--pin` records the exact Rigor version (`"gem:rigortype" = "X.Y.Z"`).
54
99
  Without it mise writes `"gem:rigortype" = "latest"`, which every
55
100
  machine re-resolves to whatever is newest when it first installs — so
56
101
  a committed `latest` does not give the team one shared version. The
57
102
  trade-off: a pin will not move on its own, and `mise outdated` cannot
58
- report a pinned tool as behind. Upgrade with `mise upgrade --bump
59
- gem:rigortype`.
103
+ report a pinned tool as behind. Upgrade by running `mise use --pin
104
+ gem:rigortype@<version>` again with the newer version; `mise upgrade
105
+ --bump gem:rigortype` goes through the same quarantine as a bare
106
+ `mise use`.
107
+
108
+ A user-wide `~/.config/mise/config.toml` entry for `gem:rigortype`
109
+ keeps applying outside this project. Mention it if it names an older
110
+ version, but do not edit global config without the user's consent.
60
111
 
61
112
  Then verify:
62
113
 
@@ -88,7 +139,8 @@ gem is installed with `gem install` after setting the Ruby version.
88
139
  ```sh
89
140
  asdf install ruby latest:4.0
90
141
  asdf local ruby latest:4.0
91
- gem install rigortype
142
+ gem install rigortype -v <LATEST>
143
+ asdf reshim ruby
92
144
  ```
93
145
 
94
146
  Verify:
@@ -108,7 +160,7 @@ pinning; see <https://mise.jdx.dev/getting-started.html>.
108
160
  If `ruby --version` reports `ruby 4.0.*`, install the gem directly:
109
161
 
110
162
  ```sh
111
- gem install rigortype
163
+ gem install rigortype -v <LATEST>
112
164
  ```
113
165
 
114
166
  Verify:
@@ -152,6 +204,11 @@ and does not integrate well with editor LSP. Use it only when a
152
204
  host-side Ruby 4.0 is genuinely unavailable (for example, Windows
153
205
  without WSL). For all other environments, prefer Case A–D above.
154
206
 
207
+ With Docker there is no `rigor` on the host PATH, so Step 3 checks the
208
+ image instead: `docker run --rm ghcr.io/rigortype/rigor:latest rigor
209
+ --version`. The image can trail RubyGems by a release; report its
210
+ version to the user rather than treating a mismatch as a failure.
211
+
155
212
  ---
156
213
 
157
214
  ## Step 3 — Verify the installation
@@ -160,8 +217,23 @@ without WSL). For all other environments, prefer Case A–D above.
160
217
  rigor --version
161
218
  ```
162
219
 
163
- A version string like `rigor 0.2.x` confirms a successful install.
164
- If the command is not found, revisit Step 2 for your case.
220
+ The reported version should be the one you installed in Step 2
221
+ (normally `<LATEST>`). If it is older, you are running a different
222
+ `rigor` than the one just installed. With mise, first check what the
223
+ project resolves to:
224
+
225
+ ```sh
226
+ mise exec -- rigor --version
227
+ mise which rigor
228
+ ```
229
+
230
+ If `mise exec` reports the new version, the shell's PATH is stale (a
231
+ non-interactive shell does not re-run mise's activation after `mise
232
+ use`); run `eval "$(mise hook-env)"`, or use `mise exec -- rigor` for
233
+ the remaining steps. Otherwise run `which -a rigor` to find the older
234
+ install. Do not uninstall anything to fix this without asking, and do
235
+ not continue on the older version without telling the user. If the
236
+ command is not found, revisit Step 2 for your case.
165
237
 
166
238
  ---
167
239
 
@@ -192,6 +264,6 @@ project is set up, re-run `rigor skill describe` for the step after
192
264
  that.
193
265
 
194
266
  If `rigor skill describe` is not recognised, your Rigor version predates
195
- it. Run `rigor --version` and upgrade with `mise upgrade --bump
196
- gem:rigortype` (or `gem update rigortype` for Case B/C); on an older
197
- version, run `rigor skill rigor-project-init` directly.
267
+ it: you are not on the version Step 1 found. Go back to Step 3. If
268
+ you are staying on an older version with the user's agreement, run
269
+ `rigor skill rigor-project-init` directly.
@@ -34,8 +34,8 @@ the `paths:` list from the configuration file.
34
34
  | `--format=FORMAT` | Output format. Default `text`. Also `json` (structured stream), plus the CI-native renderings `sarif`, `github`, `gitlab`, `checkstyle`, `junit`, and `teamcity` — see [Running Rigor in CI](11-ci.md). |
35
35
  | `--no-ci-detect` | Disable CI auto-detection — by default `text` output also emits the running CI's native annotations / hint (`RIGOR_CI_DETECT=0` does the same). See [Running Rigor in CI § auto-detection](11-ci.md). |
36
36
  | `--explain` | Surface fail-soft fallback events as `info` diagnostics. |
37
- | `--no-cache` | Skip the persistent cache for this run. |
38
- | `--incremental` | Re-analyse only the files changed since the last run plus the files that depend on them, serving the rest from a cross-process disk snapshot (ADR-46). Diagnostics are identical to a full run; a config / gem / version change (or a file added or removed) transparently forces a full re-analysis. See [Caching](12-caching.md). |
37
+ | `--no-cache` | Skip the persistent cache for this run. With `--incremental` it is a cold full analysis: the incremental snapshot and the run-result cache are neither read nor written, so the next plain `--incremental` run still uses the snapshot it had before. |
38
+ | `--incremental` | Re-analyse only the files changed since the last run plus the files that depend on them, serving the rest from a cross-process disk snapshot (ADR-46). Diagnostics are identical to a full run; a config / gem / version change (or a file added or removed) transparently forces a full re-analysis. A run with nothing changed is answered from the cache without starting the analyzer. See [Caching](12-caching.md). |
39
39
  | `--verify-incremental` | Acceptance gate: run the incremental analyzer against a full `--no-cache` run and assert the diagnostics are byte-identical, then exit (0 on match, 1 with the differing diagnostics on mismatch). Used in CI to guarantee `--incremental` never serves a stale result. |
40
40
  | `--clear-cache` | Delete the cache directory before running. |
41
41
  | `--cache-stats` | Print the on-disk cache inventory when finished — on stdout under `--format text`, on stderr under every other format, so machine-readable output stays a parseable document. `--clear-cache`'s and `--verify-incremental`'s notes follow the same rule. |
@@ -138,6 +138,28 @@ truncation explicit. `--trace` records fail-soft fallbacks,
138
138
  after the rows of a line table in text output. The editor-mode
139
139
  `--tmp-file` / `--instead-of` pair is accepted as on `check`.
140
140
 
141
+ A template a plugin compiles into Ruby (an ERB view under
142
+ `rigor-actionpack`) is probed the way `rigor check` analyses
143
+ it: the compiled Ruby is typed under the view's declared
144
+ `self`, locals and instance variables, and `LINE:COL` are the
145
+ template's own. A column answers only when it lies in Ruby the
146
+ compiler copied verbatim — the `@user.name` in
147
+ `<%= @user.name %>` — and denotes exactly one compiled
148
+ expression. A column in HTML text, in code the plugin rewrote
149
+ (a layout's `<%= yield %>`), or in bytes copied to more than
150
+ one place prints `no expression found at …` with the reason
151
+ and exits `1`; the command never answers about a nearby
152
+ node instead. Two tags that name the same thing (`<%= v %>`
153
+ beside `<% if "a" <= v %>`, or `<%= v %> <%= v.to_s %>`)
154
+ decline for that reason as well; a name repeated inside ONE
155
+ tag still answers. A bare `FILE:LINE` lists only the expressions
156
+ that map back to a template column. `--trace` fallbacks are
157
+ reported at the template line, and a JSON fallback carries a
158
+ `column` only when its position maps back to one. A template
159
+ the plugin declined to compile prints
160
+ `plugin declined the template; probing its bytes as Ruby`
161
+ and is probed as plain Ruby, parse error included.
162
+
141
163
  The four probe commands — `type-of`, `type-scan`, `trace` and
142
164
  `annotate` — build their environment fresh on every invocation
143
165
  and never read or write the persistent cache. That is why none
@@ -477,13 +499,52 @@ rigor sig-gen [paths]
477
499
  | `--print` | Write RBS to stdout. Default. |
478
500
  | `--diff` | Write a unified diff against existing RBS. |
479
501
  | `--write` | Write RBS to `sig/<path>.rbs` files. |
480
- | `--overwrite` | Allow tighter-return updates to replace user-authored RBS. |
502
+ | `--check` | Write nothing; print what `--write` with the same options would change, and exit `1` if anything would. The CI freshness gate. |
503
+ | `--overwrite` | Allow tighter-return updates, and inline declarations that disagree with `sig/`, to replace user-authored RBS. |
481
504
  | `--include-private` | Emit private and protected methods too. |
482
505
  | `--params=untyped\|observed\|observed-strict` | Parameter-typing policy. Default `untyped`. |
483
- | `--observe=PATH` | Scan `PATH` for call-site observations. Repeatable. |
506
+ | `--observe=PATH` | Scan `PATH` for call-site observations. Repeatable. Default: the configured `test_paths:` (unset: whichever of `spec/` and `test/` exist). |
484
507
  | `--new-files` / `--new-methods` / `--tighter-returns` | Emit only that classification. |
508
+ | `--effect-envelopes` | Also emit `%a{rigor:v1:effect …}` for effectful methods. Needs the `effects:` opt-in. |
509
+ | `--no-cache` | Do not read or write the analysis cache. Only effect collection uses it. |
485
510
  | `--format=text\|json` | Output format. |
486
511
 
512
+ `--print`, `--diff`, `--write` and `--check` are mutually exclusive.
513
+ `--check` fails exactly when `--write` would create, change or
514
+ refuse a file, so a tighter return `--write` declines without
515
+ `--overwrite` does not fail it; `--check --overwrite` counts one.
516
+ See [handbook chapter 11](../handbook/11-sig-gen.md#keeping-sig-current-in-ci).
517
+
518
+ A method declared inline with `# @rbs` / `#:` is written as that
519
+ declaration, not as what its body infers; a parameter-only
520
+ annotation keeps its parameters and takes the return from the body,
521
+ and `initialize` is always `-> void`. When `sig/` already declares
522
+ the method (a `def` or an `attr_*`) and the two disagree as types —
523
+ parameter names and union spelling do not count, overload order
524
+ does — sig-gen changes neither: the method is refused
525
+ (`sig.skipped.inline-differs`, listed under `refused` in `--format=json`),
526
+ and `--write` / `--check` exit `1` until you make them agree or pass
527
+ `--overwrite`, which replaces the whole `sig/` member with the inline
528
+ declaration. For a parameter-only annotation only the parameters are
529
+ compared; the return follows the ordinary proposal rules. A class
530
+ made generic inline is not written unless `sig/` declares it with the
531
+ same type parameters. A project whose Steep reads the same
532
+ annotations sets `sig_gen.inline_declared: skip` in `.rigor.yml` to
533
+ keep those methods out of `sig/`. See
534
+ [handbook chapter 11](../handbook/11-sig-gen.md#methods-declared-inline).
535
+
536
+ When `.rigor.yml` carries an `effects:` block, sig-gen also writes
537
+ `%a{pure}` above a method whose effect summary is **exhaustive**
538
+ (every call it reaches was resolved), **undischarged** (nothing in
539
+ its footprint is only invisible because `effects.tolerated:` says
540
+ so), **claimed** (every callee is described by a catalogue row, a
541
+ plugin, an envelope or a project definition), carries no authored
542
+ bound of its own, and is free of surviving labels in the `≤` lane. Nothing else is annotated, and
543
+ `--effect-envelopes` adds Rigor's own labelled spelling for methods
544
+ that do have a footprint. With no `effects:` block the output is
545
+ byte-for-byte what it was before. See
546
+ [handbook chapter 11](../handbook/11-sig-gen.md#emitting-effect-annotations).
547
+
487
548
  Every signature is parsed before it is emitted. A method whose
488
549
  generated RBS does not parse is **skipped** (`sig.skipped.unrenderable-rbs`)
489
550
  and reported on stderr rather than written — an unparseable
@@ -21,6 +21,11 @@ discovery entirely.
21
21
 
22
22
  All relative paths in a config file resolve against that
23
23
  file's own directory.
24
+ A leading `~/` (or a bare `~`) in a path setting expands to your home directory; `~name` is an ordinary
25
+ directory called `~name`, not another user's home. This covers `paths:`, `signature_paths:`, `test_paths:`,
26
+ `pre_eval:`, `plugins_io.allowed_paths:`, `includes:`, `bundler.bundle_path:`, `bundler.lockfile:` and
27
+ `rbs_collection.lockfile:`. It does not apply to `cache.path:`, `baseline:` or `effects.snapshot.path:`, which stay
28
+ literal and relative to the working directory.
24
29
 
25
30
  ## Editor validation
26
31
 
@@ -60,6 +65,7 @@ cache:
60
65
  | `target_ruby` | String | `"4.0"` | The Ruby version *your* project runs — `"X.Y"`, `"X.Y.Z"`, or `"latest"`. Independent of the Ruby Rigor itself runs on. |
61
66
  | `paths` | Array | `["lib"]` | Directories or files to analyse. |
62
67
  | `exclude` | Array | `[]` | Glob patterns to skip. `vendor/bundle`, `.bundle`, and `node_modules` are always excluded. |
68
+ | `test_paths` | Array | `nil` | The project's test roots: the directories (or files) holding its tests. Relative entries resolve against the config file's directory. Unset auto-detects whichever of `spec/` and `test/` exist; `[]` declares none. `rigor sig-gen --params=observed` reads call sites there to type parameters, and names on stderr a declared root that does not exist. Test roots are not analysed unless `paths:` also lists them, and changing them invalidates no cache. |
63
69
  | `includes` | Array | `[]` | Other config files to layer underneath this one. |
64
70
  | `fold_platform_specific_paths` | Boolean | `false` | Resolve Ruby-version-conditional load paths when discovering sources. |
65
71
  | `parameter_inference` | Boolean | `false` | Opt-in call-site parameter type inference on the `check` walk ([ADR-67](../adr/67-parameter-type-inference.md) WD6). When `true`, an undeclared `def` / `initialize` / setter parameter is typed to the union of its resolved call-site argument types, sharpening downstream ivar reads, folds, and protection coverage. Precision-additive only — the negative rules never fire against an inferred parameter. Cannot be combined with `--incremental`. |
@@ -73,6 +79,12 @@ cache:
73
79
  | `pre_eval` | Array | `[]` | Files (or globs) walked before per-file analysis, to register project monkey-patches and publish their top-level constants project-wide. |
74
80
  | `plugins` | Array | `[]` | Plugins to activate — see [Using plugins](07-plugins.md). |
75
81
 
82
+ ### Signature generation
83
+
84
+ | Key | Type | Default | Meaning |
85
+ | --- | --- | --- | --- |
86
+ | `sig_gen.inline_declared` | String | `"write"` | What `rigor sig-gen` does with a method already declared inline by `# @rbs` / `#:`. `write` copies the inline declaration into `sig/`, so the generated signature is the complete contract a gem ships, and refuses (exit 1) a `sig/` declaration that later disagrees with the inline one until the two agree or `--overwrite` replaces it. `skip` leaves every method the inline reader declares out of `sig/` (`sig.skipped.inline-declared`): set it when Steep reads the same annotations (`inline: true` beside `signature "sig"`), where a copy would declare each method twice (`DuplicatedMethodDefinition`). The cost of `skip`: a consumer reading only your shipped `sig/` never sees those methods, and an annotation on one — a Rigor refinement included — takes effect only where the source itself is analysed. Any other value, or any other key under `sig_gen:`, is a load error. Only `rigor sig-gen` reads this key, and changing it invalidates no cache. See [handbook chapter 11](../handbook/11-sig-gen.md#methods-declared-inline). |
87
+
76
88
  ### Config validation warnings
77
89
 
78
90
  `rigor check` warns on STDERR when a configured value silently resolves to
@@ -16,6 +16,7 @@ Every rule has a two-segment `family.rule` identifier:
16
16
  | `call` | Call sites — undefined methods, arity, argument types, nil receivers. |
17
17
  | `flow` | Control-flow proofs — always-raises, dead branches, constant conditions. |
18
18
  | `def` | Method definitions — return types, ivar writes, visibility. |
19
+ | `global` | Writes to special globals — a value the setter rejects, a read-only variable. |
19
20
  | `assert` | `assert_type` checks. |
20
21
  | `dump` | `dump_type` notices. |
21
22
 
@@ -70,11 +71,14 @@ carries no `documentation_url`.
70
71
  | <a id="rule-def-override-visibility-reduced"></a>`def.override-visibility-reduced` | An override reduces the visibility it inherits from a project-defined ancestor. | high |
71
72
  | <a id="rule-def-override-return-widened"></a>`def.override-return-widened` | An override's declared return type widens the inherited return (covariance). | high |
72
73
  | <a id="rule-def-override-param-narrowed"></a>`def.override-param-narrowed` | An override narrows an inherited parameter type (contravariance). | high |
74
+ | <a id="rule-global-write-type-mismatch"></a>`global.write-type-mismatch` | A special global is assigned a literal its setter rejects, so the write raises `TypeError` every time it runs. `$/`, `$-0`, `$,` and `$\` take only a String or nil (`$/ = 1` and `$/ = /x/` report). `$;` and `$-F` take a String, a Regexp, nil, or an object with `to_str`. `$~` takes a MatchData or nil. `$0` and `$PROGRAM_NAME` take a String or an object with `to_str`, so `$0 = nil` and `$0 = :name` report. `$.` takes an Integer, a Float, or an object with `to_int`, so `$. = "3"` reports and `$. = 3r` does not. `$-i` takes a String, nil, false, or an object with `to_str`. `$stdout`, `$>` and `$stderr` take anything that responds to `write`: `$stdout = 1` reports, `$stdout = StringIO.new` does not. Ruby's setter decides, not the global's RBS type. Only a value written as a literal is judged — a number, a string, a symbol, an array, a hash or a regexp literal (interpolated or not), or `nil`, `true` or `false` — so a variable, a method call or a constant never reports, whatever its type. A literal stays silent when your program defines the method the setter asks for (`write`, `to_str`, `to_int`) or a `method_missing` / `respond_to_missing?` / `respond_to?` anywhere, in any spelling and on any class — Rigor cannot always tell which objects such a definition reaches, so it does not try — or when a top-level `include` / `extend` or an ancestor's `include` names a module Rigor has no RBS for. For `$stdout`, `$>` and `$stderr` it also stays silent where a `using` is in effect for a refinement that may add `write`. `$/`, `$-0`, `$,`, `$\` and `$~` accept only their classes, so none of that silences them. A write to `$stdin` is never checked, and a special any file aliases (`alias $stdout $out`, including a `pre_eval:` file) is exempt. `warning` under `lenient`. | high |
75
+ | <a id="rule-global-readonly-write"></a>`global.readonly-write` | A read-only special global is written — `$!`, `$$`, `$?`, `$<`, `$FILENAME`, `$*`, `$:` / `$LOAD_PATH` / `$-I`, `$"` / `$LOADED_FEATURES`, `$-W`, `$-p`, `$-l` or `$-a` — which raises `NameError` whatever the value. Covers `$g = value`, `$g += value` and a multiple-assignment target. `$LOAD_PATH ||= []` never writes and `$LOAD_PATH << dir` is a method call, so neither fires. A special any file aliases (`alias $! $err`) is exempt. An error in every profile. | high |
73
76
  | <a id="rule-static-value-use-void"></a>`static.value-use.void` | A value recovered from an author-declared `-> void` return is used in value context (an assignment right-hand side, a call receiver, or a call argument). Off by default; reaches a run only through the `use-of-void-value` bleeding-edge feature (ADR-100). A bare-statement `void` call and a legitimate `top` value both stay silent. | high |
74
77
  | <a id="rule-effect-envelope-exceeded"></a>`effect.envelope-exceeded` | A method performs an effect its declared envelope does not admit — its proven effect labels (its own body plus everything it calls) are not covered by the `%a{pure}` or `%a{rigor:v1:effect …}` bound written on it or on its class. Opt-in twice over: it needs an `effects:` block in `.rigor.yml` and an envelope you wrote. Positioned at the Ruby `def`. Unproven ("and possibly more") effects never fire, and `mutate.local` is tolerated by every envelope. | high |
75
78
  | <a id="rule-effect-liskov-widened"></a>`effect.liskov-widened` | An override escapes the envelope written on the method it overrides. A `PgRepo` is usable wherever a `Repo` is, so a `%a{rigor:v1:effect io.db}` on `Repo#find` binds `PgRepo#find` too: an implementation may be purer than the bound it inherits, never less pure. Either what the override *does* exceeds the inherited bound, or the envelope the override *declares for itself* is wider than it. Both sides must be authored — nothing fires unless someone wrote an envelope on the ancestor — and only subclassing counts, not `include`. Positioned at the override's `def`. Needs an `effects:` block. | high |
76
79
  | <a id="rule-effect-unknown-label"></a>`effect.unknown-label` | An effect declaration names a label the registry does not know — a typo in an envelope (`%a{rigor:v1:effect io.bd}`), or a member of `effects.tolerated:`. The whole tag then reads as unbounded, so the declaration quietly stops doing anything; this says so. Positioned at the declaration: the `.rbs` line, the `.rb` line for an rbs-inline annotation, or `.rigor.yml` for a config value. `# rigor:disable` comments are not read out of `.rbs` or `.rigor.yml`, so use `disable:` or the baseline there. Only fires where the spelling is evidently meant to be a label (close to a known one, next to a known one, dotted, or retired) — a word nothing resembles stays silent, because you may be opening your own vocabulary. Needs an `effects:` block. | high |
77
80
  | <a id="rule-effect-annotations-unchecked"></a>`effect.annotations-unchecked` | Your signatures carry `%a{pure}` / `%a{rigor:v1:effect …}` but `.rigor.yml` has no `effects:` block, so nothing checks them. One `:info` per run, positioned at the first annotation. An annotation never turns effect collection on by itself — that would make one line in one file more expensive for every run — so this is how it tells you instead. Add `effects: {}` to opt in, or `disable:` it to keep the annotations documentary. Reads both annotation lanes — `sig/*.rbs` and rbs-inline comments — on every run, a warm `--incremental` with nothing changed included. | — |
81
+ | <a id="rule-rbs-contradicting-signature"></a>`rbs.contradicting-signature` | A method is declared both in your `sig/` and by an inline `# @rbs` / `#:` annotation, and the two provably contradict — no value or call satisfies both: in the return or a parameter every call must pass, the two types share no value (`::String` against `::Integer`). Only absolutely written Ruby core or stdlib classes count: a module such as `Comparable`, a class your project declares, a gem's class, or a relative name like plain `String` never does, or two keyword-free declarations accept positional counts that cannot meet, or one requires a keyword the other cannot take in any form. Overloads are paired by correspondence, not order. Also fires when a member-level `%a{rigor:v1:return: …}` / `%a{rigor:v1:param: …}` refinement shares no value with the type its own member declares. Positioned at the `.rbs` member; Rigor reads that declaration. A stale generated signature is the usual cause: regenerate it, or fix the annotation. Two declarations where one refines the other merge to the more precise one without a word, and a pair Rigor cannot rank drops the inline side with a `source-rbs-annotation-not-honoured` `:info` instead ([Precedence](plugins/rigor-rbs-inline.md#precedence)). An error in every profile. | high |
78
82
  | <a id="rule-suppression-unknown-rule"></a>`suppression.unknown-rule` | A `# rigor:disable[-file]` comment names a rule that does not exist (typically a typo), so the suppression silently does nothing. `plugin.`-prefixed tokens are never flagged. | high |
79
83
  | <a id="rule-suppression-empty"></a>`suppression.empty` | A `# rigor:disable[-file]` comment lists no rules, so it suppresses nothing. | high |
80
84
  | <a id="rule-suppression-unknown-marker"></a>`suppression.unknown-marker` | A comment uses a suppression marker Rigor does not recognise — typically the RuboCop reflex `# rigor:disable-next-line <rule>` or `# rigor:enable <rule>`. Rigor's only markers are `# rigor:disable <rules>` (suppresses on its own line) and `# rigor:disable-file <rules>`, so the comment suppresses nothing. | high |
@@ -95,7 +95,7 @@ with the `plugins_isolation:` configuration key or the
95
95
  | --- | --- |
96
96
  | `process` (default) | Run the call in a forked, crash-contained worker, so the target library's monkey-patches and any crash never contaminate Rigor. Falls back to `none` where `fork` is unavailable (Windows / JRuby). |
97
97
  | `none` | Load the library into Rigor's own process and call it directly. |
98
- | `ruby_box` | Run inside an experimental `Ruby::Box` sandbox. This needs the `RUBY_BOX=1` start flag, so the `rigor` launcher re-execs itself with it set when you select this strategy. **Environment variable only** — the configuration file is read long after Ruby has booted, so `plugins_isolation: ruby_box` is reported as a configuration error instead. |
98
+ | `ruby_box` | Run inside an experimental `Ruby::Box` sandbox. This needs the `RUBY_BOX=1` start flag, so the `rigor` launcher re-execs itself with it set when you select this strategy. It also needs a Ruby that fixes [Ruby Bug #22260](https://bugs.ruby-lang.org/issues/22260), which no release up to 4.0.7 does. The launcher checks for the fix first, and without it prints a warning and uses the configured strategy instead. **Environment variable only** — the configuration file is read long after Ruby has booted, so `plugins_isolation: ruby_box` is reported as a configuration error instead. |
99
99
 
100
100
  The environment variable wins over `plugins_isolation:`, so you can
101
101
  override a project's committed choice for one invocation. The legacy
@@ -60,9 +60,10 @@ is not repeated here.)
60
60
  - **`rigor-project-init`** — onboards a project from a cold start. It
61
61
  detects the stack (Rails, RSpec, dry-rb, …), proposes the matching
62
62
  [plugins](07-plugins.md), picks an adoption mode — a
63
- [baseline](06-baseline.md) snapshot for an existing codebase or a
64
- zero-diagnostic gate for a clean one — writes a `.rigor.dist.yml`, and
65
- generates the first baseline. Reach for it when setting Rigor up for
63
+ [baseline](06-baseline.md) snapshot by default, new projects
64
+ included, or an opt-in zero-diagnostic gate for users fluent in type
65
+ theory and RBS — writes a `.rigor.dist.yml`, and generates the first
66
+ baseline. Reach for it when setting Rigor up for
66
67
  the first time.
67
68
  - **`rigor-rbs-setup`** — installs community RBS for your gems
68
69
  (`rbs collection install`) so RBS-less dependencies stop typing as
@@ -358,8 +358,9 @@ Generate RBS skeleton signatures inferred from Ruby source files.
358
358
  | `params` | `"untyped"` \| `"observed"` | no | `"untyped"` |
359
359
  | `config` | `string` | no | session default |
360
360
 
361
- `params: "observed"` harvests call-site argument types from `spec/`
362
- (or a directory named via `--observe=PATH` in the underlying CLI).
361
+ `params: "observed"` harvests call-site argument types from the
362
+ project's test roots: the configured `test_paths:`, or whichever of
363
+ `spec/` and `test/` exist.
363
364
 
364
365
  **Returns:** JSON — the same as `rigor sig-gen --print --format json`.
365
366
 
@@ -135,7 +135,7 @@ CI detection that `auto` relies on.
135
135
 
136
136
  | Flag | Effect |
137
137
  | --- | --- |
138
- | `rigor check --no-cache` | Run without reading or writing the persistent cache. |
138
+ | `rigor check --no-cache` | Run without reading or writing the persistent cache. With `--incremental` this includes the snapshot: every file is analysed and the snapshot on disk is left untouched. |
139
139
  | `rigor check --clear-cache` | Delete the cache directory, then run. |
140
140
  | `rigor check --cache-stats` | Print the on-disk cache inventory when the run finishes (on stderr under every `--format` but `text`, so the document on stdout stays parseable). |
141
141
  | `rigor check --incremental` | Re-analyse only what changed; serve the rest from the incremental snapshot (see below). |
@@ -144,9 +144,12 @@ There is no config key to disable caching permanently — the
144
144
  flags are per-run toggles. To run without a persistent cache
145
145
  habitually, point `cache.path` at a disposable directory.
146
146
 
147
+ With `--incremental --no-cache` and an editor buffer (`--tmp-file` with `--instead-of`), the buffer is analysed on its own, as it is when no snapshot exists, and the note says that `--no-cache` skipped the snapshot rather than suggesting `rigor check --incremental`.
148
+
147
149
  ## Size and eviction
148
150
 
149
- A project's active cache set is small (around 2 MB). Entries
151
+ A project's active cache set is a few megabytes (about 10 MB
152
+ for a Rails application the size of Mastodon). Entries
150
153
  are content-keyed, so events like a gem upgrade or an `.rbs`
151
154
  edit write fresh entries and leave the old ones *orphaned* —
152
155
  nothing references them, and no run would otherwise delete
@@ -219,9 +222,45 @@ dependents of removed ones.) As with the rest of the cache, a
219
222
  missing or corrupt snapshot is simply a full run; it can never
220
223
  wedge analysis.
221
224
 
225
+ When nothing has changed since the last `--incremental` run, the
226
+ answer comes straight from the cache, without starting the
227
+ analyzer at all. Rigor first checks the inputs that answer was
228
+ computed from — your source files and view templates, your RBS,
229
+ your configuration and the files your plugins read — and runs the
230
+ ordinary incremental re-check the moment any of them differs. On
231
+ a Rails application the size of Mastodon, an unchanged
232
+ `--incremental` run takes about a quarter of a second. An
233
+ unchanged plain `rigor check` skips the analyzer the same way,
234
+ except when a plugin compiles your view templates, as
235
+ rigor-actionpack does.
236
+
237
+ The shortcut stands aside under `--workers=N` (or
238
+ `parallel.workers:`), `--explain`, `--cache-stats`, `--coverage`,
239
+ an editor buffer, and an `effects:` block, and when the cache
240
+ directory is on a different filesystem from the project; those
241
+ runs re-check as before. It also switches itself off, silently:
242
+
243
+ - when an input changes that the ordinary re-check does not look
244
+ at itself: an auto-detected `sig/` directory (one you did not
245
+ list under `signature_paths:`), a file a narrower
246
+ `rigor check --incremental DIR` only discovers from the rest of
247
+ your configured `paths:`, or a `pre_eval:` file outside the
248
+ analysed paths. The re-check may keep printing the answer from
249
+ before that change; the shortcut does not repeat it;
250
+ - when an `--incremental` run cannot record its answer safely:
251
+ after a run with `--workers=N` that re-analysed files, when a file or directory changes while a run is reading
252
+ it, after a CI cache is restored onto a fresh checkout, or when
253
+ two runs race.
254
+
255
+ Either way it stays off, and every run is an ordinary re-check,
256
+ until the next full analysis. The ordinary re-check never starts
257
+ one by itself: only a configuration, `Gemfile.lock` or Rigor
258
+ version change does, or `--clear-cache`. So the shortcut can stay
259
+ off indefinitely; run `rigor check --incremental --clear-cache`
260
+ once to turn it back on.
261
+
222
262
  `--incremental` is most useful for fast local re-checks and CI
223
- on a changed branch. For a one-shot run on an unchanged project,
224
- the ordinary cache already serves the whole result in one step.
263
+ on a changed branch.
225
264
 
226
265
  ## Concurrency
227
266
 
@@ -90,8 +90,9 @@ It runs eight phases in order:
90
90
  2. **Choose an adoption mode** — proposes either *acknowledge*
91
91
  (snapshot today's diagnostics into a baseline; catch
92
92
  regressions going forward) or *strict* (drive the project to
93
- zero and keep it there). It recommends acknowledge for
94
- codebases with more than ~100 initial diagnostics.
93
+ zero and keep it there). It recommends acknowledge for every
94
+ project, new ones included; strict is an opt-in for users
95
+ fluent in type theory and RBS.
95
96
  3. **Select plugins** — proposes the plugin set matching your
96
97
  detected stack; you confirm or trim the list.
97
98
  4. **Write `.rigor.dist.yml`** — the committed shared config,
@@ -151,14 +152,19 @@ source-of-truth copy is
151
152
 
152
153
  | Mode | When | What happens |
153
154
  | --- | --- | --- |
154
- | **Acknowledge** | Existing codebase with many diagnostics | Record today's diagnostics in a baseline; surface only new ones on each PR. |
155
- | **Strict** | New or small project | Zero outstanding diagnostics; no baseline. |
155
+ | **Acknowledge** | Recommended for every project, new ones included | Record today's diagnostics in a baseline; surface only new ones on each PR. |
156
+ | **Strict** | Opt-in, for users fluent in type theory and RBS | Zero outstanding diagnostics; no baseline. |
156
157
 
157
- If your first `rigor check` reports more than ~100 diagnostics,
158
- acknowledge mode is the natural starting point. You can tighten
159
- it later. In `.rigor.dist.yml` (Step 3) the mode maps to a
160
- `severity_profile:` — acknowledge → `lenient`, strict → `strict`
161
- (omit the key for the default `balanced`).
158
+ Start with acknowledge mode even on a brand-new project. Rigor's
159
+ inference still has gaps, so a zero-diagnostic gate meets
160
+ diagnostics on correct code, and clearing those means fixing
161
+ signatures or RBS rather than your code. Choose strict only if
162
+ you are comfortable doing that yourself. You can tighten later.
163
+
164
+ In `.rigor.dist.yml` (Step 3) the mode maps to a
165
+ `severity_profile:`. Acknowledge mode uses the default `balanced`
166
+ (omit the key), or `lenient` when the first `rigor check` reports
167
+ more than ~100 diagnostics; strict mode uses `strict`.
162
168
 
163
169
  ### Step 3 — Write .rigor.dist.yml
164
170
 
@@ -198,7 +204,7 @@ plugins:
198
204
  - rigor-rspec
199
205
  - rigor-factorybot
200
206
 
201
- severity_profile: lenient # "strict" for strict mode; omit for "balanced"
207
+ # severity_profile: lenient # only for >100 initial diagnostics; "strict" for strict mode
202
208
 
203
209
  # baseline: .rigor-baseline.yml # uncomment after Step 6 (acknowledge mode only)
204
210
  ```
@@ -288,7 +294,9 @@ first, then large clusters to record in a baseline.
288
294
 
289
295
  ### Step 6 — Generate a baseline (acknowledge mode)
290
296
 
291
- *Skip this step if you chose strict mode.*
297
+ *Skip this step if you chose strict mode.* Settle `severity_profile:`
298
+ first: changing it afterwards changes which rules fire and leaves the
299
+ baseline out of step.
292
300
 
293
301
  ```sh
294
302
  rigor baseline generate
@@ -22,25 +22,56 @@ The plain `() -> String` stays the compatibility contract; the
22
22
  annotation tells Rigor the return is a non-empty string.
23
23
 
24
24
  You may also write any of them **in a `.rb` file**, as an
25
- rbs-inline `# @rbs %a{…}` comment — `%a{}` is rbs-inline's own
26
- upstream grammar, and the annotation reaches Rigor on the same
27
- path the generated signature does:
25
+ inline-RBS comment — `%a{}` is RBS's own annotation grammar, and
26
+ the annotation reaches Rigor on the same path the generated
27
+ signature does. Rigor reads three spellings:
28
28
 
29
29
  ```rb
30
30
  # rbs_inline: enabled
31
31
 
32
32
  class Reader
33
+ # Own line: the annotation, then the type on its own tag.
33
34
  # @rbs %a{rigor:v1:return: non-empty-string}
34
35
  # @rbs return: String
35
36
  def read_name = "x"
37
+
38
+ # Same line, `@rbs` method type.
39
+ # @rbs %a{rigor:v1:return: non-empty-string} () -> String
40
+ def title = "x"
41
+
42
+ # Same line, `#:` method type.
43
+ #: %a{rigor:v1:return: non-empty-string} () -> String
44
+ def label = "x"
36
45
  end
37
46
  ```
38
47
 
48
+ Several annotations may stand before the method type
49
+ (`#: %a{pure} %a{rigor:v1:return: non-empty-string} () -> String`).
50
+ The other inline-RBS readers do not accept all three:
51
+
52
+ | spelling | rbs's built-in inline parser, Steep with `inline: true` | the `rbs-inline` gem's own `--output` |
53
+ | --- | --- | --- |
54
+ | own line | syntax error (`expected a token pARROW`), annotation lost | annotation kept |
55
+ | same line | annotation and method type kept | annotation kept, method type **dropped** |
56
+
57
+ Rigor keeps both halves of every row. If you also run Steep in
58
+ inline mode, use the same-line spelling. The measurement is in
59
+ [ADR-111](../adr/111-inline-refinement-carrier.md). If the method type after a same-line annotation
60
+ does not parse, the method is left untyped and Rigor reports it as
61
+ [`plugin.rbs-inline.source-rbs-annotation-not-honoured`](plugins/rigor-rbs-inline.md#same-line-annotations).
62
+
39
63
  This needs the `rbs-inline` library installed; Rigor ingests
40
64
  inline annotations by default when it is
41
- ([ADR-93](../adr/93-default-rbs-inline-ingestion.md)). There is
42
- no Rigor-only comment dialect: `# rigor:` comments remain
43
- suppression-only.
65
+ ([ADR-93](../adr/93-default-rbs-inline-ingestion.md)). `# rigor:`
66
+ comments remain suppression-only.
67
+
68
+ A dedicated `# @extrbs` comment for what RBS cannot spell is
69
+ accepted in [ADR-112](../adr/112-extrbs-comment-channel.md) but
70
+ not implemented yet ([#1073](https://github.com/rigortype/rigor/issues/1073)).
71
+ Until it ships, the `%a{}` forms above are the inline route. A type
72
+ plain RBS can spell, such as `:asc | :desc`, belongs in `# @rbs` or
73
+ `#:` either way.
74
+
44
75
  This page is the *operational* reference — the directives you can
45
76
  write and their syntax. For the normative rules (conflict
46
77
  handling, merging, provenance) see
@@ -183,7 +214,9 @@ class UserRepository
183
214
  end
184
215
  ```
185
216
 
186
- The same two work as rbs-inline comments in a `.rb` file:
217
+ The same two work as rbs-inline comments in a `.rb` file, in any of
218
+ the three spellings shown above — the own-line
219
+ one here:
187
220
 
188
221
  ```rb
189
222
  # rbs_inline: enabled
@@ -214,6 +214,16 @@ rigor effects --pure
214
214
  436 methods on Redmine, and they are your `%a{pure}` candidates — the on-ramp to
215
215
  the last section of this chapter.
216
216
 
217
+ You do not have to write them all out by hand. With the `effects:` block in
218
+ place, `rigor sig-gen` annotates the methods in this set whose signature it is
219
+ proposing anyway: `%a{pure}` above every method that is exhaustive, undischarged,
220
+ free of surviving `≤` labels, not already carrying a bound you wrote, and calling
221
+ nothing the analyzer had no description for; and, under `--effect-envelopes`, the labelled spelling for the methods that
222
+ do have a footprint. A method that is clean only because `effects.tolerated:`
223
+ says so is deliberately left bare — the annotation would travel to readers who
224
+ do not share your tolerated list. See
225
+ [handbook chapter 11](../handbook/11-sig-gen.md#emitting-effect-annotations).
226
+
217
227
  ### Asking it a question
218
228
 
219
229
  **By label.** The question this chapter opens with, in one command:
@@ -54,6 +54,9 @@ The full catalogue, with a one-line scope for every plugin, is
54
54
  `Result` / `Option` unwrap types and synthesises `Enum` variants.
55
55
  - [rigor-pundit](rigor-pundit.md) — policy-class existence and
56
56
  `authorize(record, :action)` predicate validation.
57
+ - [rigor-active-model-serializers](rigor-active-model-serializers.md) —
58
+ types the `object` reader inside a serializer as the serializer's
59
+ model (no diagnostics).
57
60
  - [rigor-sidekiq](rigor-sidekiq.md) — Sidekiq `Worker.perform_*`
58
61
  argument arity against the discovered `#perform`.
59
62
  - [rigor-actioncable](rigor-actioncable.md) — `broadcast_to` channel
@@ -62,6 +65,10 @@ The full catalogue, with a one-line scope for every plugin, is
62
65
  through Minitest / Test::Unit assertions and spec matchers.
63
66
  - [rigor-graphql](rigor-graphql.md) — GraphQL-Ruby type / enum / input
64
67
  / mutation table publication (cross-plugin facts, no diagnostics).
68
+ - [rigor-grape](rigor-grape.md) — types the `Grape::API` endpoint DSL
69
+ (`params`, `namespace`, verb macros, `desc`, `route_setting`) and
70
+ `Grape::Entity` `expose` declarations, including the `instance_eval`'d
71
+ block `self` bindings.
65
72
  - [rigor-rspec-rails](rigor-rspec-rails.md) — `have_http_status`
66
73
  argument validation (out-of-range codes, unknown status symbols).
67
74
  - [rigor-shoulda-matchers](rigor-shoulda-matchers.md) — shoulda matcher