rigortype 0.3.8 → 0.4.0

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 (634) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/data/capability_roles/capability_roles.rbs +36 -0
  4. data/data/core_overlay/enumerable.rbs +51 -0
  5. data/data/core_overlay/enumerator.rbs +84 -0
  6. data/data/core_overlay/hash_rbs3.rbs +41 -0
  7. data/data/core_overlay/process.rbs +40 -0
  8. data/data/core_overlay/string_io.rbs +33 -0
  9. data/data/effects/core.yml +3 -3
  10. data/data/gem_overlay/activesupport/core_ext.rbs +236 -17
  11. data/docs/handbook/02-everyday-types.md +17 -14
  12. data/docs/handbook/03-narrowing.md +132 -14
  13. data/docs/handbook/04-tuples-and-shapes.md +8 -6
  14. data/docs/handbook/05-methods-and-blocks.md +1 -1
  15. data/docs/handbook/06-classes.md +2 -2
  16. data/docs/handbook/07-rbs-and-extended.md +19 -1
  17. data/docs/handbook/10-sorbet.md +9 -10
  18. data/docs/handbook/11-sig-gen.md +525 -13
  19. data/docs/handbook/README.md +1 -1
  20. data/docs/handbook/appendix-elixir.md +2 -2
  21. data/docs/handbook/appendix-go.md +2 -2
  22. data/docs/handbook/appendix-java-csharp.md +2 -2
  23. data/docs/handbook/appendix-mypy.md +2 -2
  24. data/docs/handbook/appendix-phpstan.md +1 -1
  25. data/docs/handbook/appendix-rust.md +1 -1
  26. data/docs/handbook/appendix-type-theory.md +4 -4
  27. data/docs/handbook/appendix-typescript.md +1 -1
  28. data/docs/llms.txt +2 -0
  29. data/docs/manual/02-cli-reference.md +95 -6
  30. data/docs/manual/03-configuration.md +18 -0
  31. data/docs/manual/04-diagnostics.md +15 -0
  32. data/docs/manual/07-plugins.md +15 -5
  33. data/docs/manual/08-skills.md +23 -2
  34. data/docs/manual/10-mcp-server.md +3 -2
  35. data/docs/manual/11-ci.md +9 -0
  36. data/docs/manual/15-type-protection-coverage.md +8 -0
  37. data/docs/manual/16-rbs-extended-annotations.md +46 -8
  38. data/docs/manual/18-removing-dead-code.md +10 -8
  39. data/docs/manual/19-effect-labels.md +10 -0
  40. data/docs/manual/plugins/README.md +7 -0
  41. data/docs/manual/plugins/rigor-actioncable.md +8 -1
  42. data/docs/manual/plugins/rigor-actionmailer.md +7 -0
  43. data/docs/manual/plugins/rigor-actionpack.md +261 -0
  44. data/docs/manual/plugins/rigor-active-model-serializers.md +153 -0
  45. data/docs/manual/plugins/rigor-activejob.md +7 -0
  46. data/docs/manual/plugins/rigor-activerecord.md +138 -4
  47. data/docs/manual/plugins/rigor-activestorage.md +8 -1
  48. data/docs/manual/plugins/rigor-activesupport-core-ext.md +43 -7
  49. data/docs/manual/plugins/rigor-grape.md +106 -0
  50. data/docs/manual/plugins/rigor-graphql.md +23 -2
  51. data/docs/manual/plugins/rigor-pundit.md +8 -1
  52. data/docs/manual/plugins/rigor-rails-i18n.md +10 -5
  53. data/docs/manual/plugins/rigor-rails-routes.md +7 -0
  54. data/docs/manual/plugins/rigor-rbs-inline.md +229 -1
  55. data/docs/manual/plugins/rigor-sidekiq.md +8 -1
  56. data/docs/manual/plugins/rigor-sorbet.md +21 -5
  57. data/exe/rigor +19 -4
  58. data/lib/rigor/analysis/baseline.rb +3 -3
  59. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +1 -1
  60. data/lib/rigor/analysis/check_rules/dead_version_guard_arms.rb +1 -5
  61. data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +167 -2
  62. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +10 -2
  63. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +189 -0
  64. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +6 -4
  65. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +15 -6
  66. data/lib/rigor/analysis/check_rules/rule_ids.rb +21 -6
  67. data/lib/rigor/analysis/check_rules/rule_walk.rb +41 -5
  68. data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +1 -1
  69. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +1 -1
  70. data/lib/rigor/analysis/check_rules/source_arity.rb +323 -0
  71. data/lib/rigor/analysis/check_rules/special_global_setters.rb +146 -0
  72. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +38 -4
  73. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +0 -1
  74. data/lib/rigor/analysis/check_rules.rb +718 -79
  75. data/lib/rigor/analysis/crash_signature.rb +8 -24
  76. data/lib/rigor/analysis/dependency_recorder.rb +23 -0
  77. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +1 -1
  78. data/lib/rigor/analysis/dependency_source_inference/builder.rb +0 -2
  79. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +0 -2
  80. data/lib/rigor/analysis/dependency_source_inference/index.rb +5 -5
  81. data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +1 -2
  82. data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
  83. data/lib/rigor/analysis/effects_cache_probe.rb +3 -4
  84. data/lib/rigor/analysis/erb_template_detector.rb +1 -2
  85. data/lib/rigor/analysis/fact_store.rb +15 -1
  86. data/lib/rigor/analysis/incremental.rb +42 -0
  87. data/lib/rigor/analysis/incremental_session.rb +117 -26
  88. data/lib/rigor/analysis/plugin_fact_fingerprint.rb +0 -1
  89. data/lib/rigor/analysis/project_scan.rb +11 -1
  90. data/lib/rigor/analysis/reachability/graph.rb +3 -5
  91. data/lib/rigor/analysis/reachability/plugin_roots.rb +5 -5
  92. data/lib/rigor/analysis/reachability/project_files.rb +2 -2
  93. data/lib/rigor/analysis/reachability/scan.rb +5 -5
  94. data/lib/rigor/analysis/reachability/scan_cache.rb +3 -2
  95. data/lib/rigor/analysis/reachability/signature_scan.rb +2 -2
  96. data/lib/rigor/analysis/result.rb +1 -3
  97. data/lib/rigor/analysis/rule_catalog.rb +131 -1
  98. data/lib/rigor/analysis/run_cache_key.rb +21 -12
  99. data/lib/rigor/analysis/run_cache_probe.rb +3 -5
  100. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -8
  101. data/lib/rigor/analysis/runner/declaration_position.rb +1 -2
  102. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +340 -33
  103. data/lib/rigor/analysis/runner/effect_annotation_residual_pass.rb +2 -3
  104. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +18 -14
  105. data/lib/rigor/analysis/runner/pool_coordinator.rb +357 -97
  106. data/lib/rigor/analysis/runner/project_pre_passes.rb +38 -13
  107. data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
  108. data/lib/rigor/analysis/runner.rb +407 -51
  109. data/lib/rigor/analysis/template_unit_collector.rb +303 -0
  110. data/lib/rigor/analysis/template_unit_paths.rb +91 -0
  111. data/lib/rigor/analysis/template_unit_positions.rb +293 -0
  112. data/lib/rigor/analysis/template_units.rb +399 -0
  113. data/lib/rigor/analysis/worker_session.rb +62 -20
  114. data/lib/rigor/bleeding_edge.rb +10 -24
  115. data/lib/rigor/builtins/hkt_builtins.rb +3 -2
  116. data/lib/rigor/builtins/imported_refinements.rb +159 -21
  117. data/lib/rigor/builtins/predefined_constant_refinements.rb +5 -7
  118. data/lib/rigor/builtins/regex_refinement.rb +21 -13
  119. data/lib/rigor/builtins/static_return_refinements.rb +7 -6
  120. data/lib/rigor/cache/annotation_location.rb +2 -4
  121. data/lib/rigor/cache/descriptor.rb +63 -20
  122. data/lib/rigor/cache/engine_source.rb +33 -5
  123. data/lib/rigor/cache/file_digest.rb +9 -1
  124. data/lib/rigor/cache/incremental_snapshot.rb +72 -6
  125. data/lib/rigor/cache/rbs_cache_producer.rb +4 -0
  126. data/lib/rigor/cache/rbs_class_ancestor_table.rb +0 -3
  127. data/lib/rigor/cache/rbs_class_type_param_names.rb +0 -3
  128. data/lib/rigor/cache/rbs_constant_table.rb +0 -3
  129. data/lib/rigor/cache/rbs_descriptor.rb +112 -14
  130. data/lib/rigor/cache/rbs_environment.rb +8 -4
  131. data/lib/rigor/cache/rbs_known_class_names.rb +0 -3
  132. data/lib/rigor/cache/store.rb +76 -32
  133. data/lib/rigor/ci_detector.rb +1 -0
  134. data/lib/rigor/cli/annotate_command.rb +5 -6
  135. data/lib/rigor/cli/check_command.rb +57 -10
  136. data/lib/rigor/cli/check_invocation.rb +6 -11
  137. data/lib/rigor/cli/check_runner_factory.rb +1 -5
  138. data/lib/rigor/cli/coverage_command.rb +1 -1
  139. data/lib/rigor/cli/coverage_mutation.rb +12 -3
  140. data/lib/rigor/cli/coverage_scan.rb +1 -5
  141. data/lib/rigor/cli/diff_command.rb +1 -1
  142. data/lib/rigor/cli/doc_links.rb +4 -4
  143. data/lib/rigor/cli/docs_command.rb +6 -6
  144. data/lib/rigor/cli/doctor_command.rb +85 -38
  145. data/lib/rigor/cli/effects_command.rb +2 -2
  146. data/lib/rigor/cli/effects_diff_renderer.rb +3 -3
  147. data/lib/rigor/cli/effects_snapshot_command.rb +1 -1
  148. data/lib/rigor/cli/explain_command.rb +34 -1
  149. data/lib/rigor/cli/fused_protection_report.rb +7 -1
  150. data/lib/rigor/cli/lsp_command.rb +1 -1
  151. data/lib/rigor/cli/mcp_command.rb +1 -1
  152. data/lib/rigor/cli/measurement_integrity_warning.rb +4 -5
  153. data/lib/rigor/cli/mutation_fork_scan.rb +6 -6
  154. data/lib/rigor/cli/mutation_protection_report.rb +7 -1
  155. data/lib/rigor/cli/plugin_command.rb +4 -4
  156. data/lib/rigor/cli/plugins_command.rb +2 -1
  157. data/lib/rigor/cli/prism_colorizer.rb +1 -2
  158. data/lib/rigor/cli/protection_fork_scan.rb +6 -6
  159. data/lib/rigor/cli/show_bleedingedge_command.rb +1 -1
  160. data/lib/rigor/cli/sig_gen_command.rb +212 -31
  161. data/lib/rigor/cli/skill_command.rb +2 -2
  162. data/lib/rigor/cli/skill_deep_probe.rb +4 -4
  163. data/lib/rigor/cli/skill_describe.rb +35 -35
  164. data/lib/rigor/cli/trace_command.rb +4 -4
  165. data/lib/rigor/cli/trace_renderer.rb +5 -6
  166. data/lib/rigor/cli/triage_command.rb +1 -1
  167. data/lib/rigor/cli/type_of_command.rb +29 -11
  168. data/lib/rigor/cli/type_of_renderer.rb +20 -9
  169. data/lib/rigor/cli/type_of_template_probe.rb +189 -0
  170. data/lib/rigor/cli/type_scan_command.rb +4 -4
  171. data/lib/rigor/cli/unused_command.rb +11 -3
  172. data/lib/rigor/cli/upgrade_command.rb +1 -1
  173. data/lib/rigor/cli.rb +89 -9
  174. data/lib/rigor/config_audit.rb +30 -5
  175. data/lib/rigor/configuration/severity_profile.rb +25 -9
  176. data/lib/rigor/configuration.rb +109 -7
  177. data/lib/rigor/effects/ancestry_recorder.rb +191 -0
  178. data/lib/rigor/effects/attribution.rb +12 -5
  179. data/lib/rigor/effects/callee_rule.rb +368 -0
  180. data/lib/rigor/effects/catalog.rb +7 -4
  181. data/lib/rigor/effects/collector.rb +11 -5
  182. data/lib/rigor/effects/config_envelopes.rb +15 -10
  183. data/lib/rigor/effects/definition_context.rb +179 -0
  184. data/lib/rigor/effects/definition_lines.rb +25 -6
  185. data/lib/rigor/effects/effect_table.rb +11 -4
  186. data/lib/rigor/effects/entry_points.rb +0 -2
  187. data/lib/rigor/effects/envelope_check.rb +9 -9
  188. data/lib/rigor/effects/envelope_index.rb +20 -8
  189. data/lib/rigor/effects/file_collection.rb +59 -5
  190. data/lib/rigor/effects/framework_units.rb +5 -7
  191. data/lib/rigor/effects/identity.rb +20 -6
  192. data/lib/rigor/effects/inline_anchor.rb +7 -7
  193. data/lib/rigor/effects/label_intent.rb +3 -4
  194. data/lib/rigor/effects/liskov_check.rb +8 -8
  195. data/lib/rigor/effects/local_ownership.rb +38 -12
  196. data/lib/rigor/effects/method_key.rb +22 -1
  197. data/lib/rigor/effects/mutation_classifier.rb +23 -12
  198. data/lib/rigor/effects/plugin_facts.rb +47 -37
  199. data/lib/rigor/effects/propagator.rb +297 -19
  200. data/lib/rigor/effects/registry.rb +1 -1
  201. data/lib/rigor/effects/scanner.rb +122 -73
  202. data/lib/rigor/effects/signature_sources.rb +4 -6
  203. data/lib/rigor/effects/snapshot.rb +12 -11
  204. data/lib/rigor/effects/snapshot_diff.rb +1 -1
  205. data/lib/rigor/effects/summary.rb +27 -4
  206. data/lib/rigor/effects/unit_scan.rb +393 -38
  207. data/lib/rigor/effects/unknown_label_check.rb +3 -8
  208. data/lib/rigor/effects/unknown_label_report.rb +3 -4
  209. data/lib/rigor/effects/visibility.rb +101 -0
  210. data/lib/rigor/environment/bundle_sig_discovery.rb +6 -6
  211. data/lib/rigor/environment/class_registry.rb +3 -1
  212. data/lib/rigor/environment/failure_slot.rb +2 -2
  213. data/lib/rigor/environment/installed_gem_set.rb +85 -0
  214. data/lib/rigor/environment/lockfile_resolver.rb +68 -4
  215. data/lib/rigor/environment/member_consistency/comparator.rb +708 -0
  216. data/lib/rigor/environment/member_consistency.rb +298 -0
  217. data/lib/rigor/environment/missing_gem_constant_index.rb +4 -4
  218. data/lib/rigor/environment/rbs_collection_discovery.rb +5 -5
  219. data/lib/rigor/environment/rbs_coverage_report.rb +5 -5
  220. data/lib/rigor/environment/rbs_hierarchy.rb +3 -1
  221. data/lib/rigor/environment/rbs_loader.rb +674 -79
  222. data/lib/rigor/environment.rb +174 -24
  223. data/lib/rigor/flow_contribution/merger.rb +0 -2
  224. data/lib/rigor/flow_contribution.rb +11 -13
  225. data/lib/rigor/hashing/xxh3.rb +264 -0
  226. data/lib/rigor/inference/acceptance.rb +292 -17
  227. data/lib/rigor/inference/block_auto_splat.rb +216 -0
  228. data/lib/rigor/inference/block_call_timing.rb +338 -0
  229. data/lib/rigor/inference/block_parameter_binder.rb +75 -30
  230. data/lib/rigor/inference/block_repetition.rb +71 -0
  231. data/lib/rigor/inference/body_fixpoint.rb +7 -6
  232. data/lib/rigor/inference/budget_trace.rb +3 -6
  233. data/lib/rigor/inference/builtins/method_catalog.rb +2 -1
  234. data/lib/rigor/inference/builtins/string_catalog.rb +1 -1
  235. data/lib/rigor/inference/captured_locals.rb +389 -18
  236. data/lib/rigor/inference/closure_escape_analyzer.rb +159 -17
  237. data/lib/rigor/inference/content_join.rb +200 -27
  238. data/lib/rigor/inference/coverage_scanner.rb +4 -6
  239. data/lib/rigor/inference/def_return_typer.rb +11 -7
  240. data/lib/rigor/inference/define_method_block_self.rb +64 -0
  241. data/lib/rigor/inference/dynamic_origin.rb +1 -1
  242. data/lib/rigor/inference/element_read_widening.rb +180 -0
  243. data/lib/rigor/inference/error_info.rb +196 -0
  244. data/lib/rigor/inference/expression_typer.rb +2124 -468
  245. data/lib/rigor/inference/external_ancestor_resolution.rb +267 -0
  246. data/lib/rigor/inference/fork_map.rb +5 -7
  247. data/lib/rigor/inference/fresh_frame_blocks.rb +127 -0
  248. data/lib/rigor/inference/global_write_census.rb +239 -0
  249. data/lib/rigor/inference/guard_rebinding.rb +447 -0
  250. data/lib/rigor/inference/hash_lookup_mutation.rb +88 -0
  251. data/lib/rigor/inference/hkt_reducer.rb +2 -3
  252. data/lib/rigor/inference/hkt_registry.rb +4 -7
  253. data/lib/rigor/inference/index_write_widening.rb +16 -7
  254. data/lib/rigor/inference/indexed_narrowing.rb +61 -9
  255. data/lib/rigor/inference/jump_targets.rb +82 -0
  256. data/lib/rigor/inference/last_line/implicit_self.rb +210 -0
  257. data/lib/rigor/inference/last_line/self_evidence.rb +329 -0
  258. data/lib/rigor/inference/last_line.rb +340 -0
  259. data/lib/rigor/inference/last_status.rb +144 -0
  260. data/lib/rigor/inference/macro_block_self_type.rb +168 -21
  261. data/lib/rigor/inference/match_rebinding/calls.rb +281 -0
  262. data/lib/rigor/inference/match_rebinding/frame.rb +148 -0
  263. data/lib/rigor/inference/match_rebinding/operands.rb +236 -0
  264. data/lib/rigor/inference/match_rebinding/self_calls.rb +83 -0
  265. data/lib/rigor/inference/match_rebinding.rb +392 -0
  266. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
  267. data/lib/rigor/inference/method_dispatcher/block_folding.rb +87 -33
  268. data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +1 -1
  269. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +305 -19
  270. data/lib/rigor/inference/method_dispatcher/data_folding.rb +1 -1
  271. data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +147 -0
  272. data/lib/rigor/inference/method_dispatcher/file_folding.rb +1 -1
  273. data/lib/rigor/inference/method_dispatcher/hash_transform_keys_folding.rb +191 -0
  274. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +6 -2
  275. data/lib/rigor/inference/method_dispatcher/json_folding.rb +1 -1
  276. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -1
  277. data/lib/rigor/inference/method_dispatcher/match_data_folding.rb +159 -0
  278. data/lib/rigor/inference/method_dispatcher/math_folding.rb +41 -3
  279. data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -5
  280. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +165 -111
  281. data/lib/rigor/inference/method_dispatcher/process_folding.rb +56 -0
  282. data/lib/rigor/inference/method_dispatcher/proven_overload.rb +72 -0
  283. data/lib/rigor/inference/method_dispatcher/random_folding.rb +82 -0
  284. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +1046 -69
  285. data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +25 -4
  286. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +2 -8
  287. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +1 -1
  288. data/lib/rigor/inference/method_dispatcher/self_substitute.rb +142 -0
  289. data/lib/rigor/inference/method_dispatcher/set_folding.rb +1 -1
  290. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +129 -54
  291. data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +1 -1
  292. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +1 -2
  293. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -6
  294. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +85 -4
  295. data/lib/rigor/inference/method_dispatcher/time_folding.rb +1 -1
  296. data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +1 -2
  297. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  298. data/lib/rigor/inference/method_dispatcher.rb +225 -25
  299. data/lib/rigor/inference/method_parameter_binder.rb +12 -9
  300. data/lib/rigor/inference/multi_target_binder.rb +341 -55
  301. data/lib/rigor/inference/mutation_rejoin.rb +171 -0
  302. data/lib/rigor/inference/mutation_widening.rb +156 -85
  303. data/lib/rigor/inference/narrowing.rb +835 -162
  304. data/lib/rigor/inference/operand_effects.rb +167 -0
  305. data/lib/rigor/inference/operand_walk.rb +88 -0
  306. data/lib/rigor/inference/optimistic_origin.rb +153 -16
  307. data/lib/rigor/inference/origin_lookup.rb +2 -3
  308. data/lib/rigor/inference/parameter_inference_collector.rb +7 -7
  309. data/lib/rigor/inference/pre_eval_constants.rb +6 -5
  310. data/lib/rigor/inference/precision_scanner.rb +3 -4
  311. data/lib/rigor/inference/project_method_ownership.rb +136 -0
  312. data/lib/rigor/inference/project_patched_methods.rb +9 -4
  313. data/lib/rigor/inference/project_patched_scanner.rb +10 -6
  314. data/lib/rigor/inference/protection_scanner.rb +1 -2
  315. data/lib/rigor/inference/range_constant.rb +57 -0
  316. data/lib/rigor/inference/rbs_type_translator.rb +49 -11
  317. data/lib/rigor/inference/receiver_alias.rb +93 -4
  318. data/lib/rigor/inference/receiver_blind_block.rb +219 -0
  319. data/lib/rigor/inference/refinement_mutation.rb +76 -0
  320. data/lib/rigor/inference/repeated_or_writes.rb +463 -0
  321. data/lib/rigor/inference/return_barrier.rb +54 -0
  322. data/lib/rigor/inference/rewrite_mutation.rb +120 -0
  323. data/lib/rigor/inference/scope_indexer.rb +5228 -627
  324. data/lib/rigor/inference/statement_evaluator.rb +3452 -595
  325. data/lib/rigor/inference/stored_block_call.rb +54 -0
  326. data/lib/rigor/inference/string_mutation.rb +97 -0
  327. data/lib/rigor/inference/struct_fold_safety.rb +6 -7
  328. data/lib/rigor/inference/synthetic_method_index.rb +1 -2
  329. data/lib/rigor/inference/synthetic_method_scanner.rb +4 -6
  330. data/lib/rigor/inference/unknown_store_widening.rb +200 -0
  331. data/lib/rigor/inference/unthreaded_rebinds.rb +282 -0
  332. data/lib/rigor/inference/version_guard.rb +28 -21
  333. data/lib/rigor/inference/void_origin.rb +3 -3
  334. data/lib/rigor/inference/void_tail_summary.rb +2 -4
  335. data/lib/rigor/language_server/buffer_table.rb +4 -4
  336. data/lib/rigor/language_server/completion_provider.rb +1 -1
  337. data/lib/rigor/language_server/debouncer.rb +0 -1
  338. data/lib/rigor/language_server/diagnostic_publisher.rb +7 -6
  339. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  340. data/lib/rigor/language_server/folding_range_provider.rb +1 -1
  341. data/lib/rigor/language_server/hover_provider.rb +1 -1
  342. data/lib/rigor/language_server/hover_renderer.rb +4 -4
  343. data/lib/rigor/language_server/incremental_sync.rb +6 -6
  344. data/lib/rigor/language_server/project_context.rb +7 -8
  345. data/lib/rigor/language_server/publish_batcher.rb +2 -2
  346. data/lib/rigor/language_server/selection_range_provider.rb +2 -2
  347. data/lib/rigor/language_server/server.rb +9 -9
  348. data/lib/rigor/language_server/signature_help_provider.rb +1 -1
  349. data/lib/rigor/language_server/uri.rb +1 -1
  350. data/lib/rigor/mcp/server.rb +2 -1
  351. data/lib/rigor/plugin/base.rb +258 -26
  352. data/lib/rigor/plugin/box_probe.rb +91 -0
  353. data/lib/rigor/plugin/bundled_catalog.rb +167 -0
  354. data/lib/rigor/plugin/effect_attribution.rb +65 -11
  355. data/lib/rigor/plugin/effect_entry_points.rb +3 -3
  356. data/lib/rigor/plugin/fact_store.rb +6 -6
  357. data/lib/rigor/plugin/io_boundary.rb +93 -8
  358. data/lib/rigor/plugin/isolation.rb +29 -6
  359. data/lib/rigor/plugin/loader.rb +33 -13
  360. data/lib/rigor/plugin/macro/block_as_method.rb +45 -6
  361. data/lib/rigor/plugin/macro/heredoc_template.rb +1 -1
  362. data/lib/rigor/plugin/macro/trait_registry.rb +1 -1
  363. data/lib/rigor/plugin/manifest.rb +94 -11
  364. data/lib/rigor/plugin/registry.rb +76 -12
  365. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +21 -4
  366. data/lib/rigor/plugin/template_unit.rb +196 -0
  367. data/lib/rigor/plugin/trust_policy.rb +2 -4
  368. data/lib/rigor/plugin/type_node_resolver.rb +3 -3
  369. data/lib/rigor/plugin.rb +1 -0
  370. data/lib/rigor/plugin_gap_advisory.rb +96 -0
  371. data/lib/rigor/project_environment.rb +138 -0
  372. data/lib/rigor/protection/analysis_guard.rb +93 -14
  373. data/lib/rigor/protection/closure_kill_oracle.rb +67 -23
  374. data/lib/rigor/protection/dependency_closure.rb +5 -8
  375. data/lib/rigor/protection/diagnostic_oracle.rb +2 -2
  376. data/lib/rigor/protection/discovery_seed.rb +14 -12
  377. data/lib/rigor/protection/kill_signature.rb +1 -4
  378. data/lib/rigor/protection/measurement_integrity.rb +1 -3
  379. data/lib/rigor/protection/mutation_cache.rb +7 -11
  380. data/lib/rigor/protection/mutation_scanner.rb +12 -15
  381. data/lib/rigor/protection/mutator.rb +2 -1
  382. data/lib/rigor/protection/test_suite_oracle.rb +5 -5
  383. data/lib/rigor/rbs_extended/conformance_checker.rb +4 -3
  384. data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
  385. data/lib/rigor/rbs_extended/reporter.rb +36 -10
  386. data/lib/rigor/rbs_extended.rb +88 -21
  387. data/lib/rigor/reflection/constant_ancestors.rb +97 -0
  388. data/lib/rigor/reflection/constant_path.rb +139 -0
  389. data/lib/rigor/reflection.rb +93 -110
  390. data/lib/rigor/runtime/jit.rb +6 -8
  391. data/lib/rigor/scope/discovery_index.rb +165 -2
  392. data/lib/rigor/scope.rb +1041 -32
  393. data/lib/rigor/sig_gen/alias_index.rb +289 -0
  394. data/lib/rigor/sig_gen/classification.rb +22 -5
  395. data/lib/rigor/sig_gen/declaration_equivalence.rb +127 -0
  396. data/lib/rigor/sig_gen/effect_annotation.rb +202 -0
  397. data/lib/rigor/sig_gen/generator.rb +673 -53
  398. data/lib/rigor/sig_gen/inline_declarations.rb +184 -0
  399. data/lib/rigor/sig_gen/layout_index.rb +3 -4
  400. data/lib/rigor/sig_gen/meta_class_shape.rb +3 -5
  401. data/lib/rigor/sig_gen/method_candidate.rb +47 -3
  402. data/lib/rigor/sig_gen/observation_collector.rb +18 -16
  403. data/lib/rigor/sig_gen/path_mapper.rb +4 -6
  404. data/lib/rigor/sig_gen/rbs_validity.rb +4 -4
  405. data/lib/rigor/sig_gen/renderer.rb +128 -14
  406. data/lib/rigor/sig_gen/skip_reason_catalog.rb +154 -0
  407. data/lib/rigor/sig_gen/superclass_spelling.rb +27 -0
  408. data/lib/rigor/sig_gen/type_elaborator.rb +1 -3
  409. data/lib/rigor/sig_gen/write_result.rb +19 -3
  410. data/lib/rigor/sig_gen/writer.rb +189 -41
  411. data/lib/rigor/sig_gen.rb +3 -0
  412. data/lib/rigor/signature_path_audit.rb +153 -6
  413. data/lib/rigor/source/literals.rb +0 -23
  414. data/lib/rigor/source/node_children.rb +0 -2
  415. data/lib/rigor/source/node_locator.rb +5 -11
  416. data/lib/rigor/source/node_walker.rb +0 -6
  417. data/lib/rigor/source/parameter_envelope.rb +72 -0
  418. data/lib/rigor/source.rb +1 -0
  419. data/lib/rigor/triage/catalogue.rb +1 -3
  420. data/lib/rigor/triage.rb +3 -5
  421. data/lib/rigor/type/accepts_result.rb +24 -6
  422. data/lib/rigor/type/combinator.rb +144 -5
  423. data/lib/rigor/type/data_class.rb +2 -2
  424. data/lib/rigor/type/data_instance.rb +4 -4
  425. data/lib/rigor/type/difference.rb +1 -0
  426. data/lib/rigor/type/float_range.rb +128 -0
  427. data/lib/rigor/type/hash_shape.rb +6 -6
  428. data/lib/rigor/type/integer_range.rb +13 -8
  429. data/lib/rigor/type/nominal.rb +8 -2
  430. data/lib/rigor/type/refined.rb +4 -3
  431. data/lib/rigor/type/struct_class.rb +3 -3
  432. data/lib/rigor/type/struct_instance.rb +4 -4
  433. data/lib/rigor/type.rb +1 -0
  434. data/lib/rigor/type_node/generic.rb +1 -1
  435. data/lib/rigor/type_node/range_literal.rb +25 -0
  436. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  437. data/lib/rigor/type_node.rb +1 -0
  438. data/lib/rigor/version.rb +1 -1
  439. data/lib/rigor.rb +1 -0
  440. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +0 -4
  441. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +0 -1
  442. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +0 -2
  443. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/effects.rb +1 -0
  444. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +10 -5
  445. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +0 -4
  446. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +3 -5
  447. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +1 -4
  448. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +14 -4
  449. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +32 -30
  450. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +0 -1
  451. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -3
  452. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +96 -0
  453. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/effects.rb +65 -8
  454. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +270 -0
  455. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/render_locals.rb +402 -0
  456. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +370 -0
  457. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_units.rb +132 -0
  458. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +264 -3
  459. data/plugins/rigor-actionpack/sig/action_controller.rbs +71 -0
  460. data/plugins/rigor-actionpack/sig/action_view.rbs +43 -0
  461. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +220 -0
  462. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_index.rb +56 -0
  463. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +263 -0
  464. data/plugins/rigor-active-model-serializers/lib/rigor-active-model-serializers.rb +7 -0
  465. data/plugins/rigor-active-model-serializers/sig/active_model_serializers.rbs +45 -0
  466. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +0 -4
  467. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +2 -1
  468. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +0 -1
  469. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +0 -2
  470. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +1 -1
  471. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +10 -5
  472. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +35 -3
  473. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +72 -12
  474. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +383 -7
  475. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +14 -6
  476. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +1 -2
  477. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +1 -2
  478. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +322 -41
  479. data/plugins/rigor-activerecord/sig/active_record/framework.rbs +123 -0
  480. data/plugins/rigor-activerecord/sig/active_record/relation.rbs +206 -31
  481. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +26 -14
  482. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +9 -2
  483. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +9 -2
  484. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +290 -19
  485. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +1 -0
  486. data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +3 -0
  487. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +9 -7
  488. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +7 -3
  489. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +1 -0
  490. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +8 -6
  491. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +11 -7
  492. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -11
  493. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +9 -4
  494. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +2 -0
  495. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +0 -5
  496. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +0 -1
  497. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +0 -1
  498. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +8 -1
  499. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/types.rb +1 -1
  500. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +36 -4
  501. data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +1 -0
  502. data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +141 -0
  503. data/plugins/rigor-grape/lib/rigor-grape.rb +6 -0
  504. data/plugins/rigor-grape/sig/grape.rbs +263 -0
  505. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +8 -6
  506. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +78 -5
  507. data/plugins/rigor-graphql/sig/graphql.rbs +485 -0
  508. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +1 -0
  509. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -1
  510. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +2 -3
  511. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +1 -0
  512. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +0 -5
  513. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -2
  514. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +0 -1
  515. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +0 -1
  516. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +12 -7
  517. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +0 -12
  518. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +1 -3
  519. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +0 -1
  520. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +36 -37
  521. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/acronyms.rb +5 -5
  522. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +0 -10
  523. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +5 -3
  524. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +3 -2
  525. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +2 -2
  526. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +2 -2
  527. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +9 -10
  528. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +2 -3
  529. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +10 -17
  530. data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +1 -0
  531. data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +13 -1
  532. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline/same_line_annotations.rb +149 -0
  533. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +324 -18
  534. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +0 -3
  535. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +0 -3
  536. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -5
  537. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +2 -3
  538. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +1 -0
  539. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +0 -3
  540. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +1 -0
  541. data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +1 -0
  542. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +1 -9
  543. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +1 -0
  544. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +0 -4
  545. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/effects.rb +1 -1
  546. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +2 -1
  547. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +0 -1
  548. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +0 -2
  549. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +10 -5
  550. data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -0
  551. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +2 -5
  552. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +1 -4
  553. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +3 -9
  554. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +4 -4
  555. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +1 -2
  556. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +3 -5
  557. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +7 -5
  558. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +51 -6
  559. data/plugins/rigor-sorbet/sig/sorbet.rbs +277 -0
  560. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +1 -0
  561. data/sig/rigor/analysis/baseline.rbs +69 -7
  562. data/sig/rigor/analysis/fact_store.rbs +1 -1
  563. data/sig/rigor/analysis/project_scan.rbs +74 -0
  564. data/sig/rigor/analysis/reachability/scan_cache.rbs +12 -0
  565. data/sig/rigor/cache.rbs +2 -3
  566. data/sig/rigor/effects/config_envelopes.rbs +100 -0
  567. data/sig/rigor/effects/effect_table.rbs +60 -0
  568. data/sig/rigor/effects/envelope.rbs +97 -0
  569. data/sig/rigor/effects/envelope_index.rbs +33 -0
  570. data/sig/rigor/effects/file_collection.rbs +81 -0
  571. data/sig/rigor/effects/label.rbs +26 -0
  572. data/sig/rigor/effects/label_set.rbs +46 -0
  573. data/sig/rigor/effects/method_key.rbs +24 -0
  574. data/sig/rigor/effects/origin.rbs +50 -0
  575. data/sig/rigor/effects/plugin_facts.rbs +141 -0
  576. data/sig/rigor/effects/registry.rbs +68 -0
  577. data/sig/rigor/effects/summary.rbs +50 -0
  578. data/sig/rigor/effects/taint_cause.rbs +12 -0
  579. data/sig/rigor/environment.rbs +17 -10
  580. data/sig/rigor/inference/optimistic_origin.rbs +10 -0
  581. data/sig/rigor/inference.rbs +9 -14
  582. data/sig/rigor/plugin/additional_initializer.rbs +36 -0
  583. data/sig/rigor/plugin/base.rbs +30 -7
  584. data/sig/rigor/plugin/effect_ancestry.rbs +27 -0
  585. data/sig/rigor/plugin/effect_attribution.rbs +66 -0
  586. data/sig/rigor/plugin/effect_edge.rbs +33 -0
  587. data/sig/rigor/plugin/effect_entry_points.rbs +25 -0
  588. data/sig/rigor/plugin/io_boundary.rbs +4 -0
  589. data/sig/rigor/plugin/loader.rbs +3 -3
  590. data/sig/rigor/plugin/manifest.rbs +51 -11
  591. data/sig/rigor/plugin/protocol_contract.rbs +68 -0
  592. data/sig/rigor/plugin/registry.rbs +62 -1
  593. data/sig/rigor/plugin.rbs +1 -1
  594. data/sig/rigor/rbs_extended.rbs +1 -1
  595. data/sig/rigor/reflection.rbs +9 -6
  596. data/sig/rigor/scope.rbs +148 -12
  597. data/sig/rigor/sig_gen/skip_reason_catalog.rbs +21 -0
  598. data/sig/rigor/source.rbs +4 -4
  599. data/sig/rigor/testing.rbs +10 -4
  600. data/sig/rigor/type.rbs +33 -1
  601. data/sig/rigor.rbs +39 -20
  602. data/skills/rigor-ask/SKILL.md +16 -11
  603. data/skills/rigor-baseline-reduce/SKILL.md +4 -6
  604. data/skills/rigor-ci-setup/SKILL.md +17 -21
  605. data/skills/rigor-doctor/SKILL.md +24 -22
  606. data/skills/rigor-doctor/references/01-checks.md +97 -33
  607. data/skills/rigor-editor-setup/SKILL.md +6 -4
  608. data/skills/rigor-mcp-setup/SKILL.md +5 -4
  609. data/skills/rigor-monkeypatch-resolve/SKILL.md +5 -3
  610. data/skills/rigor-next-steps/SKILL.md +4 -2
  611. data/skills/rigor-plugin-author/SKILL.md +19 -23
  612. data/skills/rigor-plugin-author/references/01-plan-and-scaffold.md +9 -8
  613. data/skills/rigor-plugin-author/references/02-walker-and-types.md +25 -27
  614. data/skills/rigor-plugin-author/references/03-test-and-ship.md +6 -8
  615. data/skills/rigor-plugin-review/SKILL.md +6 -4
  616. data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +2 -1
  617. data/skills/rigor-plugin-tune/SKILL.md +4 -2
  618. data/skills/rigor-project-init/SKILL.md +15 -10
  619. data/skills/rigor-project-init/references/01-detect.md +13 -9
  620. data/skills/rigor-project-init/references/02-configure.md +43 -14
  621. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +14 -14
  622. data/skills/rigor-project-init/references/04-sig-uplift.md +27 -14
  623. data/skills/rigor-project-init/references/06-agent-contract.md +67 -0
  624. data/skills/rigor-protection-uplift/SKILL.md +4 -6
  625. data/skills/rigor-rbs-setup/SKILL.md +4 -2
  626. data/skills/rigor-type-oracle/SKILL.md +226 -0
  627. data/skills/rigor-type-oracle/references/01-oracle-commands.md +265 -0
  628. data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +52 -0
  629. data/skills/rigor-type-oracle/references/03-gap-protocol.md +125 -0
  630. data/skills/rigor-unused-adjudicate/SKILL.md +12 -7
  631. data/skills/rigor-upgrade/SKILL.md +13 -8
  632. metadata +133 -3
  633. data/lib/rigor/cli/probe_environment.rb +0 -85
  634. data/sig/rigor/inference/builtins/numeric_catalog.rbs +0 -3
@@ -0,0 +1,184 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rbs"
4
+
5
+ require_relative "../configuration"
6
+ require_relative "../rbs_extended"
7
+
8
+ module Rigor
9
+ module SigGen
10
+ # The methods the inline reader declares for each analysed file (ADR-112 WD4): what `# @rbs` / `#:` in a
11
+ # `.rb` file says, member by member, as `rigor sig-gen` needs it to copy that declaration into `sig/`.
12
+ #
13
+ # Read from the RBS the `rigor-rbs-inline` synthesizer contributed (`RbsLoader#virtual_rbs`), not from the
14
+ # built environment. The environment is the wrong witness twice over: once `sig/` declares a member, the
15
+ # loader stands one of the two down before the build (ADR-112 WD5), so the declaration a stale `sig/` copy
16
+ # should be compared against may no longer be there to read; and a member's origin is only recoverable off
17
+ # its location, which the ADR-54 environment cache does not keep. The synthesized text is the loader's input,
18
+ # identical on every run.
19
+ #
20
+ # A member the synthesizer marks `rigor:v1:inferred-signature` is in the text only because rbs-inline
21
+ # declares every `def` of a file it reads: the author annotated nothing on it, so it is present here
22
+ # ({#lookup} finds it, which is what `sig_gen.inline_declared: skip` needs) but not {Member#declared?}.
23
+ class InlineDeclarations
24
+ # One inline-declared method.
25
+ #
26
+ # - `method_types` — the declared overloads (`RBS::MethodType`, names as written, unresolved).
27
+ # - `annotations` — the annotation strings the author wrote on the member (`deprecated` for
28
+ # `%a{deprecated}`), without the synthesizer's own `rigor:v1:inferred-*` markers.
29
+ # - `return_inferred` — the author declared the parameters and not the return: rbs-inline defaulted it,
30
+ # and the synthesizer marked it `rigor:v1:inferred-return`.
31
+ # - `signature_inferred` — every type slot defaulted; the author said nothing about this member.
32
+ # - `attr_line` — for an attribute, the member in RBS's own `attr_*` spelling (`attr_reader name: String`),
33
+ # which is what replaces an `attr_*` declaration in `sig/` under `--overwrite`; nil for a `def`.
34
+ Member = Data.define(:method_types, :annotations, :return_inferred, :signature_inferred, :attr_line) do
35
+ def declared?
36
+ !signature_inferred
37
+ end
38
+
39
+ # The member's annotations as RBS lines, each in a delimiter its content does not close.
40
+ def annotation_lines
41
+ annotations.filter_map { |string| InlineDeclarations.annotation_line(string) }
42
+ end
43
+ end
44
+
45
+ # The synthesizer channel this index reads; another plugin's virtual RBS is not an inline declaration.
46
+ SYNTHESIZER_ID = Configuration::AUTOWIRED_RBS_INLINE_ID
47
+ private_constant :SYNTHESIZER_ID
48
+
49
+ MARKERS = [RbsExtended::INFERRED_RETURN_DIRECTIVE, RbsExtended::INFERRED_SIGNATURE_DIRECTIVE].freeze
50
+ private_constant :MARKERS
51
+
52
+ # rbs lexes `%a` with each of these pairs, up to the first matching closer and with no escape
53
+ # (`rbs/src/lexer.re`), so an annotation is spelled in the first pair its content does not close.
54
+ ANNOTATION_DELIMITERS = Ractor.make_shareable([%w[{ }], %w[( )], %w[[ ]], %w[< >], %w[| |]])
55
+ private_constant :ANNOTATION_DELIMITERS
56
+
57
+ def self.annotation_line(string)
58
+ pair = ANNOTATION_DELIMITERS.find { |_open, close| !string.include?(close) }
59
+ pair && "%a#{pair[0]}#{string}#{pair[1]}"
60
+ end
61
+
62
+ # Fail-soft: an environment with no loader, or a loader this index cannot read, declares nothing inline,
63
+ # which is the answer for a project without the plugin.
64
+ def self.build(environment)
65
+ loader = environment&.rbs_loader
66
+ new(loader.respond_to?(:virtual_rbs) ? loader.virtual_rbs : [])
67
+ rescue StandardError
68
+ new([])
69
+ end
70
+
71
+ def initialize(virtual_rbs)
72
+ @by_path = {}
73
+ @generic_classes = {}
74
+ virtual_rbs.each do |name, content|
75
+ prefix, plugin_id, path = name.to_s.split(":", 3)
76
+ next unless prefix == "virtual" && plugin_id == SYNTHESIZER_ID && path
77
+
78
+ decls = parse(content.to_s)
79
+ next if decls.nil?
80
+
81
+ table = (@by_path[path] ||= {})
82
+ each_member(decls, []) { |class_name, member| record(table, class_name, member) }
83
+ end
84
+ @by_path.freeze
85
+ @generic_classes.freeze
86
+ end
87
+
88
+ # `{ class name => [type parameter names] }` for the classes and modules an inline declaration gives type
89
+ # parameters (`# @rbs generic T`). sig-gen does not write a class's type parameters, and a `sig/` header
90
+ # without them makes rbs reject the class (`GenericParameterMismatchError`), so the generator writes nothing
91
+ # that would open one of these, nor members into a `sig/` declaration that names the parameters otherwise.
92
+ attr_reader :generic_classes
93
+
94
+ # @return the inline declaration of `class_name`'s `method_name` on the `kind` side, declared in the file
95
+ # at `path` — or nil.
96
+ def lookup(path, class_name, method_name, kind)
97
+ @by_path.dig(path.to_s, [class_name, method_name, kind])
98
+ end
99
+
100
+ private
101
+
102
+ def parse(content)
103
+ return nil if content.empty? || !content.valid_encoding?
104
+
105
+ _buffer, _directives, decls = ::RBS::Parser.parse_signature(content)
106
+ decls
107
+ rescue ::RBS::BaseError
108
+ nil
109
+ end
110
+
111
+ def each_member(decls, prefix, &block)
112
+ decls.each do |decl|
113
+ next unless decl.is_a?(::RBS::AST::Declarations::Class) || decl.is_a?(::RBS::AST::Declarations::Module)
114
+
115
+ inner = prefix + [decl.name.to_s.delete_prefix("::")]
116
+ @generic_classes[inner.join("::")] ||= decl.type_params.map(&:name) unless decl.type_params.empty?
117
+ decl.members.each { |member| block.call(inner.join("::"), member) }
118
+ each_member(decl.members, inner, &block)
119
+ end
120
+ end
121
+
122
+ def record(table, class_name, member)
123
+ method_entries(member).each do |method_name, kind, method_types|
124
+ table[[class_name, method_name, kind]] ||= build_member(member, method_types)
125
+ end
126
+ end
127
+
128
+ # `[method_name, kind, method_types]` per method a member defines, mirroring what the RBS definition
129
+ # builder files it under: an attribute is its reader and / or writer, `def self?.x` is both sides. An
130
+ # `overloading?` member (`def x: ... | ...`) composes with another declaration rather than being one,
131
+ # and an alias declares no signature of its own, so neither is recorded.
132
+ def method_entries(member)
133
+ case member
134
+ when ::RBS::AST::Members::MethodDefinition then definition_entries(member)
135
+ when ::RBS::AST::Members::AttrReader then [reader_entry(member)]
136
+ when ::RBS::AST::Members::AttrWriter then [writer_entry(member)]
137
+ when ::RBS::AST::Members::AttrAccessor then [reader_entry(member), writer_entry(member)]
138
+ else []
139
+ end
140
+ end
141
+
142
+ def definition_entries(member)
143
+ return [] if member.overloading?
144
+
145
+ types = member.overloads.map(&:method_type)
146
+ kinds = member.kind == :singleton_instance ? %i[instance singleton] : [member.kind]
147
+ kinds.map { |kind| [member.name, kind, types] }
148
+ end
149
+
150
+ def reader_entry(member)
151
+ [member.name, member.kind, [::RBS::Parser.parse_method_type("() -> #{member.type}")]]
152
+ end
153
+
154
+ def writer_entry(member)
155
+ [:"#{member.name}=", member.kind, [::RBS::Parser.parse_method_type("(#{member.type}) -> #{member.type}")]]
156
+ end
157
+
158
+ ATTR_KEYWORDS = {
159
+ ::RBS::AST::Members::AttrReader => "attr_reader",
160
+ ::RBS::AST::Members::AttrWriter => "attr_writer",
161
+ ::RBS::AST::Members::AttrAccessor => "attr_accessor"
162
+ }.freeze
163
+ private_constant :ATTR_KEYWORDS
164
+
165
+ def attr_line(member)
166
+ keyword = ATTR_KEYWORDS[member.class]
167
+ return nil if keyword.nil?
168
+
169
+ "#{keyword} #{'self.' if member.kind == :singleton}#{member.name}: #{member.type}"
170
+ end
171
+
172
+ def build_member(member, method_types)
173
+ strings = member.annotations.map { |annotation| annotation.string.to_s.strip }
174
+ Member.new(
175
+ method_types: method_types.freeze,
176
+ annotations: (strings - MARKERS).freeze,
177
+ return_inferred: strings.include?(RbsExtended::INFERRED_RETURN_DIRECTIVE),
178
+ signature_inferred: strings.include?(RbsExtended::INFERRED_SIGNATURE_DIRECTIVE),
179
+ attr_line: attr_line(member)
180
+ )
181
+ end
182
+ end
183
+ end
184
+ end
@@ -21,20 +21,19 @@ module Rigor
21
21
  # in multiple files for additive member contributions, but the writer only needs one canonical target per
22
22
  # class.
23
23
  class LayoutIndex
24
- # @param signature_paths [Array<String, Pathname>, nil]
24
+ # @param signature_paths —
25
25
  # the `.rigor.yml`-configured signature directories.
26
26
  # When `nil` or empty, falls back to `<project_root>/sig`
27
27
  # if it exists (matching `Environment.for_project`'s
28
28
  # auto-detection convention).
29
- # @param project_root [String, Pathname]
30
29
  def initialize(signature_paths:, project_root: Dir.pwd)
31
30
  @signature_paths = resolve_paths(signature_paths, project_root)
32
31
  @index = nil
33
32
  end
34
33
 
35
- # @param class_name [String] fully-qualified Ruby class
34
+ # @param class_name — fully-qualified Ruby class
36
35
  # name (e.g. `"Rigor::Type::Top"`).
37
- # @return [Pathname, nil] absolute path of the sig file
36
+ # @return absolute path of the sig file
38
37
  # that already declares this class, or `nil` when no
39
38
  # existing declaration is found.
40
39
  def file_for(class_name)
@@ -29,11 +29,9 @@ module Rigor
29
29
 
30
30
  module_function
31
31
 
32
- # @param kind [:data, :struct]
33
- # @param members [Array<Symbol>] ordered member names from the ADR-48 layout.
34
- # @param member_types [Hash{Symbol => String}] erased RBS spellings; a member with no entry renders `untyped`.
35
- # @param keyword_init [Boolean] the `Struct.new(..., keyword_init: true)` flag; ignored for `:data`.
36
- # @return [Shape]
32
+ # @param members — ordered member names from the ADR-48 layout.
33
+ # @param member_types — erased RBS spellings; a member with no entry renders `untyped`.
34
+ # @param keyword_init — the `Struct.new(..., keyword_init: true)` flag; ignored for `:data`.
37
35
  def of(kind:, members:, member_types: {}, keyword_init: false)
38
36
  types = members.to_h { |member| [member, member_types[member] || "untyped"] }
39
37
  accessors = kind == :struct ? struct_accessors(members, types) : readers(members, types)
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "classification"
4
+ require_relative "effect_annotation"
5
+
3
6
  module Rigor
4
7
  module SigGen
5
8
  # Per-method record produced by the generator.
@@ -15,17 +18,28 @@ module Rigor
15
18
  # - `inferred_return` — `Rigor::Type` instance (or `nil` when the inference pass disqualified the def).
16
19
  # - `declared_return_rbs` — the existing RBS-declared return spelling, or `nil` when no RBS declares the
17
20
  # method.
21
+ # - `declared_rbs` — for an `inline_overwrite`, or a `tighter_return` over the class's own declaration, the
22
+ # whole declared line the proposal replaces, so `--diff` and `--check` can show both; `nil` otherwise.
23
+ # - `declared_annotations` — for a member declared inline, the annotations the author wrote on it
24
+ # (`%a{deprecated}`), rendered above `rbs` like `annotations`. Empty otherwise.
18
25
  # - `rbs` — the rendered RBS one-liner the generator would emit (`nil` for skipped / equivalent rows).
19
26
  # - `skip_reason` — one of {Classification::SKIP_DIAGNOSTIC_IDS} keys when classification is `:skipped`,
20
27
  # else `nil`.
28
+ # - `annotations` — RBS annotation lines to render ABOVE `rbs` (`%a{pure}` /
29
+ # `%a{rigor:v1:effect …}`, ADR-103 WD9). Empty unless the effects opt-in is on and the method's
30
+ # summary earned one; see {EffectAnnotation}.
31
+ # - `effect_reason` — one of {EffectAnnotation::DIAGNOSTIC_IDS} keys saying why `annotations` is what
32
+ # it is, or `nil` when the run had nothing to say about this method's effects.
21
33
  class MethodCandidate
22
34
  attr_reader :path, :class_name, :method_name, :kind, :classification,
23
35
  :inferred_return, :declared_return_rbs, :rbs, :skip_reason,
24
- :namespace_kinds, :class_shells, :class_superclasses
36
+ :namespace_kinds, :class_shells, :class_superclasses,
37
+ :annotations, :effect_reason, :declared_rbs, :declared_annotations
25
38
 
26
39
  def initialize(path:, class_name:, method_name:, kind:, classification:, # rubocop:disable Metrics/ParameterLists
27
40
  inferred_return: nil, declared_return_rbs: nil, rbs: nil, skip_reason: nil,
28
- namespace_kinds: {}, class_shells: [], class_superclasses: {})
41
+ namespace_kinds: {}, class_shells: [], class_superclasses: {},
42
+ annotations: [], effect_reason: nil, declared_rbs: nil, declared_annotations: [])
29
43
  @path = path
30
44
  @class_name = class_name
31
45
  @method_name = method_name
@@ -41,9 +55,32 @@ module Rigor
41
55
  # superclasses appear; computed ones are absent. The Writer emits `class Bar < Base` for the leaf when
42
56
  # present.
43
57
  @class_superclasses = class_superclasses.freeze
58
+ @annotations = annotations.freeze
59
+ @effect_reason = effect_reason
60
+ @declared_rbs = declared_rbs
61
+ @declared_annotations = declared_annotations.freeze
44
62
  freeze
45
63
  end
46
64
 
65
+ # Every line this candidate contributes to a declaration body, annotations first. The single place
66
+ # the renderer and the writer agree that an annotation precedes the `def` line it binds.
67
+ def rbs_lines
68
+ @declared_annotations + @annotations + [@rbs].compact
69
+ end
70
+
71
+ # A copy carrying a different annotation decision. The generator's effect pass rebuilds rather than
72
+ # mutates, because a candidate is frozen the moment it is built.
73
+ def with_effect_annotation(annotations, reason)
74
+ self.class.new(
75
+ path: @path, class_name: @class_name, method_name: @method_name, kind: @kind,
76
+ classification: @classification, inferred_return: @inferred_return,
77
+ declared_return_rbs: @declared_return_rbs, rbs: @rbs, skip_reason: @skip_reason,
78
+ namespace_kinds: @namespace_kinds, class_shells: @class_shells,
79
+ class_superclasses: @class_superclasses, annotations: annotations, effect_reason: reason,
80
+ declared_rbs: @declared_rbs, declared_annotations: @declared_annotations
81
+ )
82
+ end
83
+
47
84
  def to_h
48
85
  {
49
86
  file: path,
@@ -54,7 +91,14 @@ module Rigor
54
91
  rbs: rbs,
55
92
  inferred_return: inferred_return&.erase_to_rbs,
56
93
  declared_return_rbs: declared_return_rbs,
57
- skip_reason: skip_reason ? Classification::SKIP_DIAGNOSTIC_IDS.fetch(skip_reason) : nil
94
+ declared_rbs: declared_rbs,
95
+ declared_annotations: declared_annotations.empty? ? nil : declared_annotations,
96
+ skip_reason: skip_reason ? Classification::SKIP_DIAGNOSTIC_IDS.fetch(skip_reason) : nil,
97
+ # Named fields rather than a merge into `rbs`: a consumer routing on the annotation has to be
98
+ # able to find it without re-lexing the rendered line. Both are absent (not empty / null) when
99
+ # the run had no effect answer, so an effects-off payload is byte-identical to a pre-#391 one.
100
+ effect_annotations: annotations.empty? ? nil : annotations,
101
+ effect_reason: effect_reason ? EffectAnnotation::DIAGNOSTIC_IDS.fetch(effect_reason) : nil
58
102
  }.compact
59
103
  end
60
104
  end
@@ -3,8 +3,10 @@
3
3
  require "prism"
4
4
 
5
5
  require_relative "../environment"
6
+ require_relative "../project_environment"
6
7
  require_relative "../scope"
7
8
  require_relative "../type"
9
+ require_relative "../source/constant_path"
8
10
  require_relative "../source/literals"
9
11
  require_relative "../source/node_children"
10
12
  require_relative "../inference/scope_indexer"
@@ -13,12 +15,12 @@ module Rigor
13
15
  module SigGen
14
16
  # ADR-14 slice 3 — caller-side argument-type observation collector.
15
17
  #
16
- # Walks the user-supplied `--observe=PATH...` tree (default `spec/`), parses every `.rb` file with `Prism`,
18
+ # Walks the `--observe=PATH...` tree (default: the project's test roots), parses every `.rb` file with `Prism`,
17
19
  # scope- indexes it the same way the main generator does, and records the per-call-site argument-type tuples
18
20
  # for every `Prism::CallNode` whose receiver types as a `Type::Nominal`. The {Generator} consumes the
19
21
  # resulting map to render `--params=observed` RBS:
20
22
  #
21
- # @return [Hash{[class_name, method_name] => Array<Array<Rigor::Type>>}]
23
+ # @return => Array<Array<Rigor::Type>>}]
22
24
  #
23
25
  # ADR-5 clause 2 compliance: the observed union is the MOST PERMISSIVE parameter contract the existing
24
26
  # callers prove sufficient — by construction it accepts every type any caller has already passed. The
@@ -32,10 +34,9 @@ module Rigor
32
34
  # the collector cannot attribute them to a specific class.
33
35
  # - Zero-argument calls give no observation; methods are matched by `(class_name, method_name)` only.
34
36
  class ObservationCollector # rubocop:disable Metrics/ClassLength
35
- # @param configuration [Rigor::Configuration]
36
- # @param paths [Array<String>] observe paths (files /
37
+ # @param paths — observe paths (files /
37
38
  # directories).
38
- # @param source_paths [Array<String>] source-tree paths
39
+ # @param source_paths — source-tree paths
39
40
  # (defaults to `configuration.paths`) pre-walked to
40
41
  # register every project-defined class so that calls
41
42
  # like `Foo.new.bar(x)` in the observe tree resolve
@@ -51,10 +52,11 @@ module Rigor
51
52
  def collect
52
53
  return {} if @paths.empty?
53
54
 
54
- environment = build_environment
55
+ resolved = resolve_paths(@paths)
56
+ environment = build_environment(resolved)
55
57
  discovered_classes = preindex_source_classes
56
58
  observations = Hash.new { |h, k| h[k] = [] }
57
- resolve_paths(@paths).each do |path|
59
+ resolved.each do |path|
58
60
  collect_from_file(path, environment, discovered_classes, observations)
59
61
  end
60
62
  observations.transform_values(&:freeze).freeze
@@ -62,11 +64,11 @@ module Rigor
62
64
 
63
65
  private
64
66
 
65
- def build_environment
66
- Environment.for_project(
67
- libraries: @configuration.libraries,
68
- signature_paths: @configuration.signature_paths
69
- )
67
+ # Issue #821 — same universe as the generator and as `rigor check`; see
68
+ # {Generator#build_environment}. An observation collected against a narrower environment types its
69
+ # argument differently from the run that will consume it.
70
+ def build_environment(source_files = [])
71
+ ProjectEnvironment.build(configuration: @configuration, source_files: source_files)
70
72
  end
71
73
 
72
74
  def resolve_paths(args)
@@ -118,11 +120,11 @@ module Rigor
118
120
  return unless node.is_a?(Prism::Node)
119
121
 
120
122
  if node.is_a?(Prism::ClassNode) || node.is_a?(Prism::ModuleNode)
121
- name = qualified_constant_path(node.constant_path)
122
- if name
123
- full = (prefix + [name]).join("::")
123
+ child_prefix = Source::ConstantPath.declaration_prefix(prefix, node.constant_path)
124
+ if child_prefix
125
+ full = child_prefix.join("::")
124
126
  accumulator[full] = Type::Combinator.singleton_of(full)
125
- walk_class_decls(node.body, prefix + [name], accumulator) if node.body
127
+ walk_class_decls(node.body, child_prefix, accumulator) if node.body
126
128
  return
127
129
  end
128
130
  end
@@ -21,9 +21,8 @@ module Rigor
21
21
  # When the source path is not under any configured source root (e.g. files supplied directly on the CLI
22
22
  # from outside `lib/`), the full relative path is preserved under the sig root.
23
23
  class PathMapper
24
- # @param configuration [Rigor::Configuration]
25
- # @param project_root [String, Pathname] (defaults to `Dir.pwd`)
26
- # @param layout_index [LayoutIndex, nil] optional class
24
+ # @param project_root — (defaults to `Dir.pwd`)
25
+ # @param layout_index — optional class
27
26
  # → existing sig file index; routes the target to the
28
27
  # consolidated file when the class is already declared.
29
28
  def initialize(configuration:, project_root: Dir.pwd, layout_index: nil)
@@ -32,12 +31,11 @@ module Rigor
32
31
  @layout_index = layout_index
33
32
  end
34
33
 
35
- # @param source_path [String]
36
- # @param class_name [String, nil] fully-qualified Ruby
34
+ # @param class_name — fully-qualified Ruby
37
35
  # class name. When supplied and matched by the
38
36
  # `LayoutIndex`, the consolidated sig file's path is
39
37
  # returned instead of the 1:1 mirror.
40
- # @return [Pathname] absolute path of the target `.rbs`
38
+ # @return absolute path of the target `.rbs`
41
39
  # file for the candidate.
42
40
  def target_for(source_path, class_name: nil)
43
41
  existing = existing_target_for(class_name)
@@ -23,16 +23,16 @@ module Rigor
23
23
 
24
24
  module_function
25
25
 
26
- # @param line [String] a rendered RBS method line, e.g. `def self.parse: (String) -> Integer`.
27
- # @return [String, nil] the parse error's first line, or nil when the line is valid.
26
+ # @param line — a rendered RBS method line, e.g. `def self.parse: (String) -> Integer`.
27
+ # @return the parse error's first line, or nil when the line is valid.
28
28
  def method_line_error(line)
29
29
  return nil if line.nil?
30
30
 
31
31
  source_error("class #{PROBE_CLASS}\n #{line}\nend\n")
32
32
  end
33
33
 
34
- # @param source [String] a complete `.rbs` file's text.
35
- # @return [String, nil] the parse error's first line, or nil when the file is valid.
34
+ # @param source — a complete `.rbs` file's text.
35
+ # @return the parse error's first line, or nil when the file is valid.
36
36
  def source_error(source)
37
37
  ::RBS::Parser.parse_signature(::RBS::Buffer.new(name: "(rigor sig-gen)", content: source))
38
38
  nil
@@ -3,6 +3,7 @@
3
3
  require "json"
4
4
 
5
5
  require_relative "classification"
6
+ require_relative "superclass_spelling"
6
7
 
7
8
  module Rigor
8
9
  module SigGen
@@ -21,10 +22,8 @@ module Rigor
21
22
  @out = out
22
23
  end
23
24
 
24
- # @param candidates [Array<MethodCandidate>]
25
- # @param mode [:print, :diff]
26
- # @param format [String] "text" or "json"
27
- # @param selection [Array<Symbol>] subset of
25
+ # @param format — "text" or "json"
26
+ # @param selection — subset of
28
27
  # {Classification} constants to include; an empty
29
28
  # array means "all emittable classifications".
30
29
  def render(candidates:, mode:, format:, selection:)
@@ -75,9 +74,13 @@ module Rigor
75
74
  when Classification::NEW_FILE then "[new-file]"
76
75
  when Classification::TIGHTER_RETURN
77
76
  "[tighter, was: #{candidate.declared_return_rbs}]"
77
+ when Classification::INLINE_OVERWRITE
78
+ "[inline-overwrite, was: #{candidate.declared_rbs}]"
78
79
  end
79
80
  @out.puts(" # #{tag}")
80
- @out.puts(" #{candidate.rbs}")
81
+ # Annotations first: an RBS annotation binds the declaration BELOW it, so `%a{pure}` printed
82
+ # after the `def` line would bind the next member — or nothing at all at the end of a class.
83
+ candidate.rbs_lines.each { |line| @out.puts(" #{line}") }
81
84
  end
82
85
  @out.puts("end")
83
86
  end
@@ -91,7 +94,7 @@ module Rigor
91
94
  return "module #{class_name}" if candidate.namespace_kinds[class_name] == :module
92
95
 
93
96
  superclass = candidate.class_superclasses[class_name]
94
- superclass ? "class #{class_name} < #{superclass}" : "class #{class_name}"
97
+ superclass ? "class #{class_name} < #{SuperclassSpelling.absolute(superclass)}" : "class #{class_name}"
95
98
  end
96
99
 
97
100
  def render_diff(candidates)
@@ -102,13 +105,22 @@ module Rigor
102
105
 
103
106
  candidates.each do |candidate|
104
107
  @out.puts("--- #{candidate.path}: #{candidate.class_name}##{candidate.method_name}")
105
- declared = candidate.declared_return_rbs
106
- @out.puts("- def #{candidate.method_name}: () -> #{declared}") if declared
107
- @out.puts("+ #{candidate.rbs}")
108
+ render_removed_line(candidate)
109
+ candidate.rbs_lines.each { |line| @out.puts("+ #{line}") }
108
110
  @out.puts
109
111
  end
110
112
  end
111
113
 
114
+ # A tighter return or an inline update replaces a whole `sig/` line, which it carries; any other row
115
+ # knows only the declared return.
116
+ def render_removed_line(candidate)
117
+ if candidate.declared_rbs
118
+ @out.puts("- #{candidate.declared_rbs}")
119
+ elsif candidate.declared_return_rbs
120
+ @out.puts("- def #{candidate.method_name}: () -> #{candidate.declared_return_rbs}")
121
+ end
122
+ end
123
+
112
124
  def render_json(candidates)
113
125
  payload = { candidates: candidates.map(&:to_h) }
114
126
  @out.puts(JSON.pretty_generate(payload))
@@ -118,16 +130,94 @@ module Rigor
118
130
 
119
131
  # Renders the per-source-file outcomes of a `--write` run. Distinct from {#render} because the write
120
132
  # path's reporting surface is action-oriented (created / updated / skipped) rather than candidate-oriented.
121
- def render_write(results:, format:)
133
+ # @param refused — methods refused as `sig.skipped.inline-differs`; the JSON payload names them under
134
+ # `refused` (absent when there are none, so an ordinary payload is unchanged), and text mode leaves them
135
+ # to the command's stderr `REFUSED` lines.
136
+ def render_write(results:, format:, refused: [])
122
137
  case format
123
- when "json" then render_write_json(results)
138
+ when "json" then render_write_json(results, refused)
124
139
  when "text" then render_write_text(results)
125
140
  else raise ArgumentError, "unsupported format: #{format}"
126
141
  end
127
142
  end
128
143
 
144
+ # ADR-112 WD4 — `sig-gen --check`: the results of a dry-run `--write`. Only the targets `--write` would
145
+ # change (or refuse) are shown, each with the lines it would add; the verdict is the exit status, which
146
+ # the command derives from the same results ({.out_of_date}).
147
+ #
148
+ # @param refused — the methods the generator refused to reconcile (`sig.skipped.inline-differs`):
149
+ # `sig/` is not up to date while one stands, and `--write` cannot fix it.
150
+ def render_check(results:, format:, refused: [])
151
+ stale = self.class.out_of_date(results)
152
+ case format
153
+ when "json"
154
+ @out.puts(JSON.pretty_generate({ up_to_date: stale.empty? && refused.empty?,
155
+ results: stale.map { |r| check_entry(r) },
156
+ refused: refused.map(&:to_h) }))
157
+ when "text" then render_check_text(stale, refused)
158
+ else raise ArgumentError, "unsupported format: #{format}"
159
+ end
160
+ end
161
+
162
+ # One line per method sig-gen refused to reconcile with its `sig/` copy. Shared by `--write` (on stderr,
163
+ # next to the write report) and `--check`.
164
+ def self.refusal_lines(refused)
165
+ refused.map do |candidate|
166
+ separator = candidate.kind == :singleton ? "." : "#"
167
+ "REFUSED #{candidate.path}: #{candidate.class_name}#{separator}#{candidate.method_name} — its inline " \
168
+ "declaration and its sig/ declaration disagree, so neither was changed " \
169
+ "(#{Classification::SKIP_DIAGNOSTIC_IDS.fetch(candidate.skip_reason)}). Make them agree, or pass " \
170
+ "--overwrite to replace the sig/ member with the inline declaration; `rigor explain " \
171
+ "#{Classification::SKIP_DIAGNOSTIC_IDS.fetch(candidate.skip_reason)}` says when that is not enough."
172
+ end
173
+ end
174
+
175
+ # The results that make a `--check` fail: a target `--write` would create or change, and one it would
176
+ # refuse, since a write that cannot happen is not an up-to-date `sig/` either.
177
+ def self.out_of_date(results)
178
+ results.reject { |result| %i[noop skipped_outside_sig_root].include?(result.action) }
179
+ end
180
+
181
+ # Nothing was written, so the entry must not read like one that was: `created` / `updated` become
182
+ # `would_create` / `would_update`. A refusal keeps its action — `--write` would refuse the same way.
183
+ CHECK_ACTIONS = { created: "would_create", updated: "would_update" }.freeze
184
+ private_constant :CHECK_ACTIONS
185
+
129
186
  private
130
187
 
188
+ def check_entry(result)
189
+ entry = result.to_h
190
+ entry[:action] = CHECK_ACTIONS.fetch(result.action, entry[:action])
191
+ entry
192
+ end
193
+
194
+ def render_check_text(stale, refused)
195
+ if stale.empty? && refused.empty?
196
+ @out.puts("sig/ is up to date")
197
+ return
198
+ end
199
+
200
+ self.class.refusal_lines(refused).each { |line| @out.puts(line) }
201
+
202
+ stale.each do |result|
203
+ case result.action
204
+ when :created, :updated then render_check_change(result)
205
+ when :skipped_invalid_rbs then render_write_invalid(result)
206
+ when :skipped_invalid_encoding then render_write_invalid_encoding(result)
207
+ end
208
+ end
209
+ end
210
+
211
+ def render_check_change(result)
212
+ counts = result.action == :created ? "#{result.applied.size} method(s)" : applied_counts(result)
213
+ verb = result.action == :created ? "would create" : "would update"
214
+ @out.puts("#{verb} #{result.target_path} (#{counts})")
215
+ result.applied.each do |candidate|
216
+ @out.puts(" - #{candidate.declared_rbs}") if candidate.declared_rbs
217
+ candidate.rbs_lines.each { |line| @out.puts(" + #{line}") }
218
+ end
219
+ end
220
+
131
221
  def render_write_text(results)
132
222
  if results.all? { |r| r.action == :noop }
133
223
  @out.puts("No changes")
@@ -145,13 +235,35 @@ module Rigor
145
235
  end
146
236
  end
147
237
 
238
+ # `+N` counts added lines; an existing line replaced (`--overwrite`, or an inline update) is counted apart,
239
+ # because "added 2" when one of them rewrote a line the project already had would understate the change.
240
+ def applied_counts(result)
241
+ added = "+#{result.applied.size - result.replaced.size}"
242
+ result.replaced.empty? ? added : "#{added}, replaced #{result.replaced.size}"
243
+ end
244
+
148
245
  def render_write_created(result)
149
246
  @out.puts("created #{result.target_path} (#{result.applied.size} method(s))")
150
247
  end
151
248
 
152
249
  def render_write_updated(result)
153
- @out.puts("updated #{result.target_path} (+#{result.applied.size}, " \
250
+ @out.puts("updated #{result.target_path} (#{applied_counts(result)}, " \
154
251
  "skipped #{result.skipped.size} user-authored)")
252
+ render_left_unreadable(result)
253
+ end
254
+
255
+ # ADR-103 WD9 — the declarations whose annotation region the writer refused to rewrite. Named per
256
+ # method rather than counted: the fix is a human reading one existing annotation and deciding what
257
+ # it should say, and there is no count of those a reader could act on.
258
+ def render_left_unreadable(result)
259
+ return if result.left_unreadable.empty?
260
+
261
+ @out.puts(" left #{result.left_unreadable.size} existing annotation(s) byte-untouched " \
262
+ "(sig.effect.left-unreadable):")
263
+ result.left_unreadable.each do |candidate|
264
+ @out.puts(" #{candidate.class_name}##{candidate.method_name} — " \
265
+ "would have emitted #{candidate.annotations.join(' ')}")
266
+ end
155
267
  end
156
268
 
157
269
  def render_write_skipped(result)
@@ -174,8 +286,10 @@ module Rigor
174
286
  @out.puts(" Re-save the file as UTF-8 and re-run; sig-gen never modifies a file it cannot read faithfully.")
175
287
  end
176
288
 
177
- def render_write_json(results)
178
- @out.puts(JSON.pretty_generate({ results: results.map(&:to_h) }))
289
+ def render_write_json(results, refused)
290
+ payload = { results: results.map(&:to_h) }
291
+ payload[:refused] = refused.map(&:to_h) unless refused.empty?
292
+ @out.puts(JSON.pretty_generate(payload))
179
293
  end
180
294
  end
181
295
  end