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
@@ -13,9 +13,10 @@ require_relative "../inference/scope_indexer"
13
13
  require_relative "../inference/precision_scanner"
14
14
  require_relative "../source/node_children"
15
15
  require_relative "type_of_renderer"
16
+ require_relative "type_of_template_probe"
16
17
  require_relative "command"
17
18
  require_relative "options"
18
- require_relative "probe_environment"
19
+ require_relative "../project_environment"
19
20
 
20
21
  module Rigor
21
22
  class CLI
@@ -28,10 +29,15 @@ module Rigor
28
29
  # type-of UX (extra flags, watch mode, streaming output) without bloating the CLI shell. Output formatting is
29
30
  # delegated to {TypeOfRenderer}.
30
31
  class TypeOfCommand < Command
32
+ include TypeOfTemplateProbe
33
+
31
34
  USAGE = "Usage: rigor type-of [options] FILE:LINE[:COL] [FILE:LINE[:COL] ...]"
32
35
 
33
- Result = Data.define(:file, :line, :column, :node, :type, :tracer, :enumeration) do
34
- def initialize(enumeration: nil, **rest)
36
+ # `location_mapper` is set for a template unit's result (#1040): the `--trace` fallback locations are
37
+ # positions in the COMPILED Ruby, and the mapper turns one into the `[template_line, column_or_nil]`
38
+ # the user can find in the file they named.
39
+ Result = Data.define(:file, :line, :column, :node, :type, :tracer, :enumeration, :location_mapper) do
40
+ def initialize(enumeration: nil, location_mapper: nil, **rest)
35
41
  super
36
42
  end
37
43
  end
@@ -46,7 +52,7 @@ module Rigor
46
52
  LINE_ENUMERATION_CAP = 40
47
53
  private_constant :LINE_ENUMERATION_CAP
48
54
 
49
- # @return [Integer] CLI exit status.
55
+ # @return CLI exit status.
50
56
  def run
51
57
  options = parse_options
52
58
  buffer = Options.resolve_buffer_binding(options, err: @err)
@@ -106,13 +112,25 @@ module Rigor
106
112
  physical = buffer ? buffer.resolve(file) : file
107
113
  return 1 unless file_exists?(buffer ? physical : file)
108
114
 
115
+ unit_probe = template_unit_probe(file, base_scope.environment, buffer)
116
+ return unit_probe if unit_probe.is_a?(Integer)
117
+
118
+ if unit_probe
119
+ context = { configuration: configuration, base_scope: base_scope, options: options }
120
+ return resolve_template(file, physical, unit_probe, indexed_targets, context)
121
+ end
122
+
109
123
  source = File.read(physical)
110
124
  parse_result = Prism.parse(source, filepath: file, version: configuration.target_ruby)
111
125
  return 1 if parse_errors?(parse_result, file)
112
126
 
113
127
  # Built with no tracer attached — it would otherwise double-record fallback events with the per-node
114
- # `type_of` calls below.
115
- scope_index = Inference::ScopeIndexer.index(parse_result.value, default_scope: base_scope)
128
+ # `type_of` calls below. `with_source_path(file)` (like `annotate` and `trace` already do) matters
129
+ # beyond diagnostics attribution: a plugin `dynamic_return file_methods:` rule (ADR-52 slice 5a,
130
+ # e.g. rigor-rspec's `let`-binding resolver, #921) gates on `scope.source_path` and fails closed
131
+ # when it is nil — an unset source_path silently declined every such rule under this command.
132
+ scope_index = Inference::ScopeIndexer.index(parse_result.value,
133
+ default_scope: base_scope.with_source_path(file))
116
134
  locator = Source::NodeLocator.new(source: source, root: parse_result.value)
117
135
  lines = indexed_targets.filter_map { |target, _index| target.line if target.column.nil? }
118
136
  line_nodes, line_totals = collect_line_nodes(parse_result.value, lines)
@@ -187,21 +205,21 @@ module Rigor
187
205
  end
188
206
  end
189
207
 
190
- def type_result(file, line, column, node, scope_index, options, enumeration: nil)
208
+ # `presentation` is the Result's optional `enumeration:` / `location_mapper:`.
209
+ def type_result(file, line, column, node, scope_index, options, **presentation)
191
210
  tracer = options[:trace] ? Inference::FallbackTracer.new : nil
192
211
  type = scope_index[node].type_of(node, tracer: tracer)
193
- Result.new(file: file, line: line, column: column, node: node, type: type, tracer: tracer,
194
- enumeration: enumeration)
212
+ Result.new(file: file, line: line, column: column, node: node, type: type, tracer: tracer, **presentation)
195
213
  end
196
214
 
197
215
  # Builds the plugin-aware environment relative to the probed file, so the reported type matches what `rigor
198
216
  # check` computes for the same position — including types synthesized from inline RBS annotations by the
199
- # ADR-93 auto-wired `rigor-rbs-inline` plugin (see {ProbeEnvironment} for the #162 misattribution this
217
+ # ADR-93 auto-wired `rigor-rbs-inline` plugin (see {ProjectEnvironment} for the #162 misattribution this
200
218
  # closes). The probed file is threaded as the synthesizer's `source_files:`. Project-RBS auto-detection
201
219
  # roots at CWD today; future work will walk parent directories to find the enclosing `Gemfile`/`*.gemspec`
202
220
  # so probes against files outside the current process's CWD still see the right `sig/` tree.
203
221
  def project_environment(files, configuration)
204
- ProbeEnvironment.build(configuration: configuration, source_files: files)
222
+ ProjectEnvironment.build(configuration: configuration, source_files: files)
205
223
  end
206
224
 
207
225
  def file_exists?(file)
@@ -84,7 +84,7 @@ module Rigor
84
84
  @out.puts("fallbacks: none")
85
85
  else
86
86
  @out.puts("fallbacks (#{tracer.size}):")
87
- tracer.each { |event| @out.puts(" - #{format_fallback_text(event, result.file)}") }
87
+ tracer.each { |event| @out.puts(" - #{format_fallback_text(event, result)}") }
88
88
  end
89
89
  end
90
90
 
@@ -118,21 +118,31 @@ module Rigor
118
118
  type: result.type.describe,
119
119
  erased: result.type.erase_to_rbs
120
120
  }
121
- payload[:fallbacks] = result.tracer.map { |event| fallback_to_h(event) } if result.tracer
121
+ payload[:fallbacks] = result.tracer.map { |event| fallback_to_h(event, result) } if result.tracer
122
122
  payload
123
123
  end
124
124
 
125
- def format_fallback_text(event, file)
126
- "#{event.node_class} (#{event.family}) @ #{location_text(event.location, file)}"
125
+ def format_fallback_text(event, result)
126
+ "#{event.node_class} (#{event.family}) @ #{location_text(event.location, result)}"
127
127
  end
128
128
 
129
- def location_text(location, file)
129
+ def location_text(location, result)
130
130
  return "<no location>" unless location.respond_to?(:start_line)
131
131
 
132
- "#{file}:#{location.start_line}:#{location.start_column + 1}"
132
+ line, column = fallback_position(location, result)
133
+ column ? "#{result.file}:#{line}:#{column}" : "#{result.file}:#{line}"
133
134
  end
134
135
 
135
- def fallback_to_h(event)
136
+ # A template unit's result (#1040) maps the compiled location back; a column that names nothing in the
137
+ # template is left out rather than printed as a column of bytes the user never wrote.
138
+ def fallback_position(location, result)
139
+ mapper = result.location_mapper
140
+ return [location.start_line, location.start_column + 1] if mapper.nil?
141
+
142
+ mapper.call(location)
143
+ end
144
+
145
+ def fallback_to_h(event, result)
136
146
  hash = {
137
147
  node_class: event.node_class.name,
138
148
  family: event.family,
@@ -140,8 +150,9 @@ module Rigor
140
150
  }
141
151
  location = event.location
142
152
  if location.respond_to?(:start_line)
143
- hash[:line] = location.start_line
144
- hash[:column] = location.start_column + 1
153
+ line, column = fallback_position(location, result)
154
+ hash[:line] = line
155
+ hash[:column] = column if column
145
156
  end
146
157
  hash
147
158
  end
@@ -0,0 +1,189 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "prism"
4
+
5
+ require_relative "../analysis/template_unit_paths"
6
+ require_relative "../analysis/template_unit_positions"
7
+ require_relative "../analysis/template_units"
8
+ require_relative "../inference/precision_scanner"
9
+ require_relative "../inference/scope_indexer"
10
+ require_relative "../source/node_locator"
11
+
12
+ module Rigor
13
+ class CLI
14
+ # #1040 — `rigor type-of` against a **template unit**: a file a loaded plugin compiles into Ruby.
15
+ #
16
+ # Before this the command read the template from disk and parsed its bytes as Ruby, so an ERB view
17
+ # exited 1 on a parse error and an identity-transformed one answered about the wrong node under an
18
+ # unseeded scope. A template path now takes the path `rigor check` takes: the run's
19
+ # {Analysis::TemplateUnits} index is built, the COMPILED source is parsed with the unit's `scopes:`, the
20
+ # file scope is seeded with the unit's `self`, locals and ivars, and the requested template position is
21
+ # resolved through {Analysis::TemplateUnitPositions} — which declines, with a message, whenever the
22
+ # position does not denote exactly one compiled node.
23
+ #
24
+ # Mixed into {TypeOfCommand}; a path no loaded plugin claims never reaches anything here, so a `.rb`
25
+ # probe does not build the index or change a byte of its output.
26
+ module TypeOfTemplateProbe
27
+ # Why a template position has no answer, keyed by {Analysis::TemplateUnitPositions#node_at}'s reasons.
28
+ DECLINE_MESSAGES = {
29
+ no_compiled_line: "the template compiler emitted no Ruby for this line",
30
+ not_verbatim: "the position is in template markup, or in code the template compiler rewrote " \
31
+ "rather than copied, so no compiled expression is made of the bytes there",
32
+ ambiguous: "the bytes there are template markup, or code the compiler copied to more than one " \
33
+ "place, so the position does not name a single compiled expression"
34
+ }.freeze
35
+ private_constant :DECLINE_MESSAGES
36
+ Ractor.make_shareable(DECLINE_MESSAGES)
37
+
38
+ private
39
+
40
+ # The unit `file` compiles to, `nil` for a path that is not a template unit (the caller probes it as
41
+ # Ruby), or an exit status after reporting a template the claiming plugin could not compile.
42
+ def template_unit_probe(file, environment, buffer)
43
+ registry = environment.plugin_registry
44
+ return nil unless template_claimed?(registry, file)
45
+
46
+ units = (@template_units ||= Analysis::TemplateUnits.collect(registry: registry, buffer: buffer))
47
+ entry = units[file]
48
+ return [units, entry] if entry
49
+
50
+ # A template the plugin DECLINED is not a unit, and is probed as the command always probed it —
51
+ # which for a template that is not Ruby is a Prism error about bytes the user did not write, so
52
+ # the decline is stated first rather than left to be inferred from the error.
53
+ failures = template_failures(units, file)
54
+ if failures.empty?
55
+ @err.puts("type-of: #{file}: plugin declined the template; probing its bytes as Ruby")
56
+ return nil
57
+ end
58
+
59
+ failures.each do |failure|
60
+ @err.puts("type-of: #{file}: plugin #{failure.plugin_id} could not compile the template: " \
61
+ "#{failure.message}")
62
+ end
63
+ 1
64
+ end
65
+
66
+ # Only a claimed path builds the index — a `.rb` probe pays no glob expansion and no transform.
67
+ def template_claimed?(registry, file)
68
+ return false if registry.nil? || registry.empty?
69
+
70
+ relative = Analysis::TemplateUnitPaths.relative(file, Dir.pwd)
71
+ registry.plugins.any? do |plugin|
72
+ globs = begin
73
+ plugin.manifest.template_globs
74
+ rescue StandardError
75
+ []
76
+ end
77
+ !globs.empty? && Analysis::TemplateUnitPaths.claims?(globs, relative)
78
+ end
79
+ end
80
+
81
+ def template_failures(units, file)
82
+ relative = Analysis::TemplateUnitPaths.relative(file, Dir.pwd)
83
+ units.failures.select { |failure| failure.path == relative }
84
+ end
85
+
86
+ # Every result for one template: the compiled source parsed and indexed once under the unit's seeded
87
+ # scope, then each requested template position resolved through the inverse map.
88
+ def resolve_template(file, physical, unit_probe, indexed_targets, context)
89
+ units, entry = unit_probe
90
+ parse_result = Prism.parse(entry.source, filepath: file, version: context[:configuration].target_ruby,
91
+ scopes: units.parse_scopes(file))
92
+ return 1 if compiled_parse_errors?(parse_result, file, entry)
93
+
94
+ scope = units.seed(context[:base_scope].with_source_path(file), file)
95
+ scope_index = Inference::ScopeIndexer.index(parse_result.value, default_scope: scope)
96
+ positions = Analysis::TemplateUnitPositions.new(
97
+ entry: entry, template: File.binread(physical), root: parse_result.value,
98
+ skip_node_types: Inference::PrecisionScanner::NON_EXPRESSION_NODE_TYPES
99
+ )
100
+ probe = { file: file, entry: entry, positions: positions, scope_index: scope_index,
101
+ options: context[:options] }
102
+ indexed_targets.map do |target, index|
103
+ resolved = resolve_template_target(probe, target)
104
+ return resolved if resolved.is_a?(Integer)
105
+
106
+ [index, resolved]
107
+ end
108
+ end
109
+
110
+ def resolve_template_target(probe, target)
111
+ file = probe[:file]
112
+ positions = probe[:positions]
113
+ return CLI::EXIT_USAGE unless template_position_in_range?(positions, target)
114
+ return enumerate_template_line(probe, target.line) if target.column.nil?
115
+
116
+ node = positions.node_at(line: target.line, column: target.column)
117
+ if node.is_a?(Symbol)
118
+ @err.puts("type-of: no expression found at #{file}:#{target.line}:#{target.column}: " \
119
+ "#{DECLINE_MESSAGES.fetch(node)}")
120
+ return []
121
+ end
122
+
123
+ [type_result(file, target.line, target.column, node, probe[:scope_index], probe[:options],
124
+ location_mapper: template_location_mapper(probe))]
125
+ end
126
+
127
+ def enumerate_template_line(probe, line)
128
+ unless line.between?(1, probe[:positions].template_line_count)
129
+ @err.puts("type-of: no expression found on #{probe[:file]}:#{line}")
130
+ return []
131
+ end
132
+
133
+ rows, total = probe[:positions].line_nodes(line)
134
+ if rows.empty?
135
+ @err.puts("type-of: no expression found on #{probe[:file]}:#{line}: no compiled expression maps to " \
136
+ "a single column of this line (markup, code the template compiler rewrote, or code " \
137
+ "copied to more than one place)")
138
+ return []
139
+ end
140
+
141
+ enumeration = TypeOfCommand::LineEnumeration.new(total: total, shown: rows.length)
142
+ mapper = template_location_mapper(probe)
143
+ rows.map do |column, node|
144
+ type_result(probe[:file], line, column, node, probe[:scope_index], probe[:options],
145
+ enumeration: enumeration, location_mapper: mapper)
146
+ end
147
+ end
148
+
149
+ # The same range errors, and the same exit status, a `.rb` probe gives — measured against the
150
+ # TEMPLATE's lines, which are the ones the user named. A bare `FILE:LINE` past the end is "no
151
+ # expression found", exactly as it is for Ruby.
152
+ def template_position_in_range?(positions, target)
153
+ return true if target.column.nil?
154
+
155
+ message = if target.line < 1 then "line must be >= 1, got #{target.line}"
156
+ elsif target.column < 1 then "column must be >= 1, got #{target.column}"
157
+ elsif target.line > positions.template_line_count
158
+ "line #{target.line} is past the end of the source buffer"
159
+ end
160
+ return true if message.nil?
161
+
162
+ @err.puts("type-of: #{message}")
163
+ false
164
+ end
165
+
166
+ # A `--trace` fallback carries a COMPILED location; it is reported at the template line, and at a
167
+ # template column only when the location lies in a verbatim run.
168
+ def template_location_mapper(probe)
169
+ entry = probe[:entry]
170
+ positions = probe[:positions]
171
+ lambda do |location|
172
+ [entry.template_line(location.start_line), positions.template_column_for(location)]
173
+ end
174
+ end
175
+
176
+ # A compiled source that does not parse is the plugin's output, not the user's text; its errors are
177
+ # reported at the template lines they map to, as `rigor check` reports every other finding in a unit.
178
+ def compiled_parse_errors?(result, file, entry)
179
+ return false if result.errors.empty?
180
+
181
+ result.errors.each do |error|
182
+ @err.puts("#{file}:#{entry.template_line(error.location.start_line)}: " \
183
+ "compiled template does not parse: #{error.message}")
184
+ end
185
+ true
186
+ end
187
+ end
188
+ end
189
+ end
@@ -11,7 +11,7 @@ require_relative "../scope"
11
11
  require_relative "type_scan_renderer"
12
12
  require_relative "type_scan_report"
13
13
  require_relative "command"
14
- require_relative "probe_environment"
14
+ require_relative "../project_environment"
15
15
  require_relative "coverage_scan"
16
16
 
17
17
  module Rigor
@@ -26,7 +26,7 @@ module Rigor
26
26
 
27
27
  LocatedEvent = Data.define(:file, :event)
28
28
 
29
- # @return [Integer] CLI exit status.
29
+ # @return CLI exit status.
30
30
  def run
31
31
  options = parse_options
32
32
  paths = collect_paths(@argv, command_name: "type-scan")
@@ -89,9 +89,9 @@ module Rigor
89
89
  # Builds the plugin-aware environment that auto-detects `<cwd>/sig` by default and honours the
90
90
  # configuration's `libraries:` / `signature_paths:` keys when present. The scanned `paths` are threaded as
91
91
  # the plugin `source_rbs_synthesizer` inputs so coverage reflects the same synthesized RBS `rigor check`
92
- # sees (see {ProbeEnvironment}).
92
+ # sees (see {ProjectEnvironment}).
93
93
  def project_environment(configuration, source_files)
94
- ProbeEnvironment.build(configuration: configuration, source_files: source_files)
94
+ ProjectEnvironment.build(configuration: configuration, source_files: source_files)
95
95
  end
96
96
 
97
97
  def scan_one(path, scanner, accumulator, configuration)
@@ -14,7 +14,7 @@ require_relative "../analysis/reachability/project_files"
14
14
  require_relative "../cache/store"
15
15
  require_relative "options"
16
16
  require_relative "command"
17
- require_relative "probe_environment"
17
+ require_relative "../project_environment"
18
18
 
19
19
  module Rigor
20
20
  class CLI
@@ -219,15 +219,23 @@ module Rigor
219
219
  # declaration, which produced three of redmine's artifacts from a single initializer. A name the bundled
220
220
  # (non-project) environment already knows is not ours to call unused. The project's own `sig/` is
221
221
  # deliberately excluded from this environment, so a project class that ships a signature stays owned.
222
+ # That exclusion is the ONLY thing withheld: the dependency-discovery axes (issue #882, the shape of
223
+ # #821) must be passed, because "outside the project" is exactly what they describe. Without the
224
+ # bundle's per-gem `sig/` and an installed `rbs collection`, a gem-declared class the project reopens
225
+ # is unknown to this environment, reads as project-owned, and lands in the candidate list — a false
226
+ # positive in a report whose measured precision cannot afford one.
222
227
  #
223
228
  # The cache store makes the environment a Marshal restore instead of a cold RBS build on every
224
229
  # invocation — the same ADR-54 slot `rigor check` reads, keyed apart by this environment's own
225
- # (sig-less) descriptor. The report only ever asks "is this name known", which the cached
230
+ # (sig-less) descriptor. The discovered bundle and collection directories ride in the loader's
231
+ # signature paths, so the descriptor digests them and this slot still cannot be served a run whose
232
+ # dependency sources differ. The report only ever asks "is this name known", which the cached
226
233
  # environment answers identically: the one thing the cache degrades is `RBS::Location`, and no
227
234
  # location is read here.
228
235
  def foreign_predicate(configuration)
229
236
  env = Environment.for_project(libraries: configuration.libraries, signature_paths: [],
230
- cache_store: cache_store(configuration))
237
+ cache_store: cache_store(configuration),
238
+ **ProjectEnvironment.dependency_discovery_options(configuration))
231
239
  ->(fqn) { !env.singleton_for_name(fqn).nil? }
232
240
  rescue StandardError
233
241
  ->(_fqn) { false }
@@ -13,7 +13,7 @@ module Rigor
13
13
  class UpgradeCommand < Command
14
14
  USAGE = "Usage: rigor upgrade [options]"
15
15
 
16
- # @return [Integer] CLI exit status.
16
+ # @return CLI exit status.
17
17
  def run
18
18
  @out.puts("rigor upgrade: No migration target available yet (ADR-50 WD7, queued).")
19
19
  @out.puts("Current version: #{Rigor::VERSION}")
data/lib/rigor/cli.rb CHANGED
@@ -12,6 +12,7 @@ require_relative "analysis/result"
12
12
  require_relative "cli/options"
13
13
  require_relative "cli/diagnostic_formats"
14
14
  require_relative "ci_detector"
15
+ require_relative "analysis/check_rules/rule_ids"
15
16
 
16
17
  module Rigor
17
18
  # The CLI class is a dispatcher: each `run_*` method delegates to a command-specific class once the command grows
@@ -20,6 +21,21 @@ module Rigor
20
21
  class CLI # rubocop:disable Metrics/ClassLength
21
22
  EXIT_USAGE = 64
22
23
 
24
+ # Issue [#609](https://github.com/rigortype/rigor/issues/609) — a run the analyzer could not finish.
25
+ # Distinct from `1` (the analysis ran and reported diagnostics) so a CI step can tell "your code has
26
+ # findings" from "Rigor died and this report is not an account of anything".
27
+ EXIT_INTERNAL_ERROR = 70
28
+
29
+ # The exceptions that mean "the process is out of a resource, and the backtrace names nothing
30
+ # actionable": `SystemStackError` from an ancestry walk that never terminates, `NoMemoryError` from a
31
+ # runaway allocation. Neither is a `StandardError`, so every rescue between here and the raise site
32
+ # declines them by design — the whole point of those rescues is to convert a per-file or per-plugin
33
+ # failure into a diagnostic, and a process running out of stack is not per-file. Left uncaught they
34
+ # abort with Ruby's own ~40-frame dump, which is non-zero (so a CI gate does catch it) but says
35
+ # neither that Rigor is at fault nor that the report on disk is empty. `Interrupt`, `SignalException`
36
+ # and `SystemExit` stay out: those are the user's answer, not a failure.
37
+ FATAL_ERRORS = [SystemStackError, NoMemoryError].freeze
38
+
23
39
  # The published location of `schemas/rigor-config.schema.json`, written into the `.rigor.yml` that
24
40
  # `rigor init` generates so an editor validates the file as the user types.
25
41
  #
@@ -95,10 +111,28 @@ module Rigor
95
111
  # register presets have loaded) would each need their own rescue at their own point.
96
112
  @err.puts("rigor: #{e.message}")
97
113
  EXIT_USAGE
114
+ rescue *FATAL_ERRORS => e
115
+ report_fatal_error(e)
116
+ EXIT_INTERNAL_ERROR
98
117
  end
99
118
 
100
119
  private
101
120
 
121
+ # One `rigor:` line naming the failure, then the innermost frames — enough to route a bug report,
122
+ # without the full dump the raise would otherwise print. The "no report" sentence is the part a user
123
+ # acts on: the crash usually happens with a redirect in place, so the artifact a CI step goes on to
124
+ # read is an empty file rather than a missing one.
125
+ def report_fatal_error(error)
126
+ @err.puts("rigor: analysis aborted: #{error.class}: #{error.message}")
127
+ Array(error.backtrace).first(FATAL_BACKTRACE_FRAMES).each { |frame| @err.puts(" from #{frame}") }
128
+ @err.puts(" Rigor could not finish this run, so its report is EMPTY rather than clean — do not")
129
+ @err.puts(" read this exit status as a passing check.")
130
+ @err.puts(" This is a defect in Rigor; please report it at https://github.com/rigortype/rigor/issues.")
131
+ end
132
+
133
+ FATAL_BACKTRACE_FRAMES = 5
134
+ private_constant :FATAL_BACKTRACE_FRAMES
135
+
102
136
  def dispatch(command)
103
137
  handler = HANDLERS[command]
104
138
  return arm_jit_deadline { send(handler) } if handler
@@ -149,7 +183,7 @@ module Rigor
149
183
  return 1
150
184
  end
151
185
 
152
- File.write(path, init_template)
186
+ File.write(path, init_template(path))
153
187
  @out.puts("Created #{path}")
154
188
  print_init_next_steps(path)
155
189
  0
@@ -169,10 +203,34 @@ module Rigor
169
203
  @out.puts " 3. Run `rigor check` to analyse your code."
170
204
  end
171
205
 
206
+ # Word-wraps `CheckRules::ALL_RULES` into a comma-separated, `#`-commented block for `init_template`, so the
207
+ # listed rule ids cannot drift from the catalogue the way a hand-picked subset did (#920) — every rule and only
208
+ # a rule actually in `ALL_RULES` gets named here.
209
+ def rule_ids_comment
210
+ prefix = "# "
211
+ max_width = 78 - prefix.length
212
+ ids = Analysis::CheckRules::ALL_RULES
213
+ lines = []
214
+ current = +""
215
+ ids.each_with_index do |id, index|
216
+ token = "#{id}#{index == ids.length - 1 ? '.' : ','}"
217
+ if current.empty?
218
+ current = token
219
+ elsif current.length + 1 + token.length <= max_width
220
+ current << " " << token
221
+ else
222
+ lines << current
223
+ current = token
224
+ end
225
+ end
226
+ lines << current unless current.empty?
227
+ lines.map { |line| "#{prefix}#{line}" }.join("\n")
228
+ end
229
+
172
230
  # Renders the starter `.rigor.yml` body. The template serialises `Configuration::DEFAULTS` (so the on-disk file
173
231
  # round-trips through `Configuration.load`) and prepends a short header that points the user at the keys they are
174
232
  # most likely to want to edit.
175
- def init_template
233
+ def init_template(path = ".rigor.dist.yml")
176
234
  <<~YAML
177
235
  # yaml-language-server: $schema=#{CONFIG_SCHEMA_URL}
178
236
  # Rigor configuration. The full key reference is
@@ -187,11 +245,10 @@ module Rigor
187
245
  # See https://github.com/rigortype/rigor/tree/master/plugins
188
246
  # for production plugins (rigor-activerecord, rigor-sorbet, …).
189
247
  # - disable: list of `rigor check` rule identifiers to
190
- # silence project-wide. The shipped rules are
191
- # call.undefined-method, call.wrong-arity,
192
- # call.argument-type-mismatch,
193
- # call.possible-nil-receiver, dump.type,
194
- # assert.type-mismatch, flow.always-raises.
248
+ # silence project-wide. Every rule id Rigor
249
+ # ships (run `rigor explain <rule>` for what
250
+ # each one catches):
251
+ #{rule_ids_comment}
195
252
  # A bare family token (`call`, `flow`,
196
253
  # `assert`, `dump`, `def`) wildcards every
197
254
  # rule under that prefix. Legacy unprefixed
@@ -209,6 +266,12 @@ module Rigor
209
266
  # Leave unset (or `null`) to auto-detect
210
267
  # `<root>/sig`. Use `[]` to disable
211
268
  # project-RBS loading entirely.
269
+ # - test_paths: the directories holding your tests.
270
+ # `rigor sig-gen --params=observed` reads
271
+ # their call sites to type parameters.
272
+ # `rigor init` writes the spec/ and test/
273
+ # it finds; unset auto-detects them, and
274
+ # `[]` declares the project has none.
212
275
  # - cache.path: where Rigor will eventually persist
213
276
  # analysis results across runs.
214
277
  #
@@ -218,10 +281,22 @@ module Rigor
218
281
  # tempfile, uri, logger, date, prism, rbs). Adding a
219
282
  # `sig/<gem>.rbs` file under `sig/` is the simplest way
220
283
  # to extend type coverage today.
221
- #{YAML.dump(Configuration::DEFAULTS).sub(/\A---\n/, '')}
284
+ #{YAML.dump(init_settings(path)).sub(/\A---\n/, '')}
222
285
  YAML
223
286
  end
224
287
 
288
+ # The settings `rigor init` writes: the defaults, with `test_paths:` spelled out as the test roots found now, so
289
+ # the project's config says where its tests are rather than leaving it to auto-detection. The roots are written
290
+ # relative to the config file's directory, which is what {Configuration.load} resolves them against.
291
+ def init_settings(path)
292
+ test_paths = Configuration.new.resolved_test_paths
293
+ return Configuration::DEFAULTS if test_paths.empty?
294
+
295
+ config_dir = Pathname(File.expand_path(File.dirname(path)))
296
+ relative = test_paths.map { |root| Pathname(root).relative_path_from(config_dir).to_s }
297
+ Configuration::DEFAULTS.merge("test_paths" => relative)
298
+ end
299
+
225
300
  def run_annotate
226
301
  require_relative "cli/annotate_command"
227
302
 
@@ -320,7 +395,10 @@ module Rigor
320
395
  @err.puts "Install it with: gem install rigor-playground"
321
396
  return EXIT_USAGE
322
397
  end
323
- Rigor::CLI::PlaygroundCommand.new(@argv[1..], @out, @err).run
398
+ # `run` shifts the verb off `@argv` before dispatching, so the handler receives the arguments
399
+ # already. Slicing here dropped the first one — `rigor playground --port=4000` served the
400
+ # default port, and a bare `rigor playground` passed `nil` into a command that iterates it.
401
+ Rigor::CLI::PlaygroundCommand.new(@argv, @out, @err).run
324
402
  end
325
403
 
326
404
  def run_skill
@@ -383,11 +461,13 @@ module Rigor
383
461
  (opt-in; effects update/check/diff/explain)
384
462
  explain Print the description of one or all CheckRules
385
463
  diff Compare current diagnostics to a saved baseline JSON
464
+ baseline Manage the baseline file (baseline generate/regenerate/dump/drift/prune)
386
465
  sig-gen Emit RBS skeletons inferred from .rb sources
387
466
  lsp Run the Rigor Language Server (LSP) over stdio
388
467
  mcp Run the Rigor MCP server over stdio
389
468
  triage Summarise diagnostics: distribution, hotspots, hints
390
469
  coverage Report type-precision coverage (precise vs Dynamic ratio)
470
+ unused Report unreferenced classes, modules and constants as removal candidates
391
471
  plugins Report activation status of every configured plugin
392
472
  plugin Browse bundled plugin source as worked examples (list/path/print/root)
393
473
  playground Start the browser playground (requires rigor-playground gem)
@@ -18,6 +18,11 @@ module Rigor
18
18
  # never written. This module surfaces each such entry up front so the cause is visible
19
19
  # instead of inferred.
20
20
  #
21
+ # {.bundled_plugin_signature_path_warnings} widens that by one step, to a value that
22
+ # resolves to *part* of what it looks like. It belongs here because the symptom is the
23
+ # same one — a cluster of `call.undefined-method` on working code, traceable to a config
24
+ # line nothing in the output names.
25
+ #
21
26
  # Every check is held to the same bar that {SignaturePathAudit} set: it mirrors the
22
27
  # loader's own acceptance test, so a warning means the loader really did load nothing, and
23
28
  # it never fires on a setup that works. In particular the rule-token check only flags a
@@ -27,8 +32,8 @@ module Rigor
27
32
  # a typo.
28
33
  module ConfigAudit
29
34
  # One config-level finding. `kind` discriminates the source key (`:signature_path`,
30
- # `:library`, `:disabled_rule`, `:severity_override`, `:bundler_bundle_path`,
31
- # `:bundler_lockfile`, `:rbs_collection_lockfile`); `fields` carries the kind-specific
35
+ # `:bundled_plugin_signature_path`, `:library`, `:disabled_rule`, `:severity_override`,
36
+ # `:bundler_bundle_path`, `:bundler_lockfile`, `:rbs_collection_lockfile`); `fields` carries the kind-specific
32
37
  # structured data merged into {#to_h} for JSON consumers.
33
38
  Warning = Data.define(:kind, :message, :fields) do
34
39
  def to_h
@@ -36,13 +41,12 @@ module Rigor
36
41
  end
37
42
  end
38
43
 
39
- # @param configuration [Rigor::Configuration]
40
- # @param project_root [String] the directory the run's relative bundler / collection
44
+ # @param project_root — the directory the run's relative bundler / collection
41
45
  # paths resolve against (the CLI's CWD), used only by the explicit-path checks.
42
- # @return [Array<Warning>]
43
46
  def self.warnings(configuration, project_root: Dir.pwd)
44
47
  unknown_key_warnings(configuration) +
45
48
  signature_path_warnings(configuration) +
49
+ bundled_plugin_signature_path_warnings(configuration) +
46
50
  library_warnings(configuration) +
47
51
  rule_token_warnings(configuration) +
48
52
  explicit_path_warnings(configuration, project_root)
@@ -100,6 +104,27 @@ module Rigor
100
104
  end
101
105
  end
102
106
 
107
+ # The one member of this module whose subject is a configured value that resolves to
108
+ # *something*: a `signature_paths:` entry loading a bundled plugin's own `sig/` while
109
+ # `plugins:` never names that plugin (issue #697). The RBS arrives, its manifest does not,
110
+ # and the declarations that manifest marks deliberately partial (ADR-26 `open_receivers:`)
111
+ # are then read as complete — so the symptom is this module's usual one, a cluster of
112
+ # high-confidence `call.undefined-method` firings on code that runs, with nothing pointing
113
+ # at the config line that caused them.
114
+ #
115
+ # It is a warning and not a fix on purpose. Teaching the check rules to recognise this
116
+ # route would be a fourth way for a class to be open-receiver protected, and settling
117
+ # where that membership belongs is [#660](https://github.com/rigortype/rigor/issues/660)'s
118
+ # question; until it is answered, the honest move is to make the misconfiguration loud
119
+ # rather than to grow the mechanism. The false positive itself still fires.
120
+ def self.bundled_plugin_signature_path_warnings(configuration)
121
+ routes = SignaturePathAudit.bundled_plugin_routes(configuration.signature_paths, configuration.plugins)
122
+ routes.map do |route|
123
+ Warning.new(kind: :bundled_plugin_signature_path, message: route.message,
124
+ fields: { "path" => route.path, "gem" => route.gem })
125
+ end
126
+ end
127
+
103
128
  # `libraries:` entries RBS does not recognise. Uses the same
104
129
  # `RBS::EnvironmentLoader#has_library?` guard the loader filters through
105
130
  # ({Environment::RbsLoader} `build_env_for`), so a flagged name is exactly one the loader