rigortype 0.3.7 → 0.3.9

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 (450) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/data/capability_roles/capability_roles.rbs +36 -0
  4. data/data/gem_overlay/activesupport/core_ext.rbs +109 -13
  5. data/docs/handbook/02-everyday-types.md +17 -14
  6. data/docs/handbook/03-narrowing.md +37 -4
  7. data/docs/handbook/05-methods-and-blocks.md +1 -1
  8. data/docs/handbook/06-classes.md +2 -2
  9. data/docs/handbook/07-rbs-and-extended.md +14 -1
  10. data/docs/handbook/11-sig-gen.md +96 -15
  11. data/docs/handbook/README.md +1 -1
  12. data/docs/handbook/appendix-elixir.md +2 -2
  13. data/docs/handbook/appendix-go.md +2 -2
  14. data/docs/handbook/appendix-java-csharp.md +2 -2
  15. data/docs/handbook/appendix-mypy.md +2 -2
  16. data/docs/handbook/appendix-phpstan.md +1 -1
  17. data/docs/handbook/appendix-rust.md +1 -1
  18. data/docs/handbook/appendix-type-theory.md +4 -4
  19. data/docs/handbook/appendix-typescript.md +1 -1
  20. data/docs/llms.txt +2 -0
  21. data/docs/manual/02-cli-reference.md +39 -4
  22. data/docs/manual/03-configuration.md +11 -0
  23. data/docs/manual/04-diagnostics.md +12 -1
  24. data/docs/manual/07-plugins.md +15 -5
  25. data/docs/manual/08-skills.md +23 -2
  26. data/docs/manual/11-ci.md +9 -0
  27. data/docs/manual/15-type-protection-coverage.md +8 -0
  28. data/docs/manual/16-rbs-extended-annotations.md +6 -1
  29. data/docs/manual/18-removing-dead-code.md +10 -8
  30. data/docs/manual/plugins/rigor-actionpack.md +18 -0
  31. data/docs/manual/plugins/rigor-activerecord.md +28 -0
  32. data/docs/manual/plugins/rigor-activesupport-core-ext.md +32 -2
  33. data/docs/manual/plugins/rigor-rbs-inline.md +42 -1
  34. data/lib/rigor/analysis/baseline.rb +2 -2
  35. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +1 -1
  36. data/lib/rigor/analysis/check_rules/dead_version_guard_arms.rb +1 -5
  37. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +10 -2
  38. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +1 -1
  39. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +15 -6
  40. data/lib/rigor/analysis/check_rules/rule_ids.rb +1 -1
  41. data/lib/rigor/analysis/check_rules/rule_walk.rb +39 -4
  42. data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +1 -1
  43. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +1 -1
  44. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
  45. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +0 -1
  46. data/lib/rigor/analysis/check_rules.rb +200 -32
  47. data/lib/rigor/analysis/crash_signature.rb +69 -13
  48. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +1 -1
  49. data/lib/rigor/analysis/dependency_source_inference/builder.rb +0 -2
  50. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +0 -2
  51. data/lib/rigor/analysis/dependency_source_inference/index.rb +5 -5
  52. data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +1 -2
  53. data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
  54. data/lib/rigor/analysis/effects_cache_probe.rb +3 -4
  55. data/lib/rigor/analysis/erb_template_detector.rb +1 -2
  56. data/lib/rigor/analysis/fact_store.rb +6 -1
  57. data/lib/rigor/analysis/incremental.rb +16 -0
  58. data/lib/rigor/analysis/incremental_session.rb +100 -29
  59. data/lib/rigor/analysis/plugin_fact_fingerprint.rb +0 -1
  60. data/lib/rigor/analysis/reachability/graph.rb +3 -5
  61. data/lib/rigor/analysis/reachability/plugin_roots.rb +6 -5
  62. data/lib/rigor/analysis/reachability/project_files.rb +2 -2
  63. data/lib/rigor/analysis/reachability/scan.rb +5 -5
  64. data/lib/rigor/analysis/reachability/scan_cache.rb +2 -2
  65. data/lib/rigor/analysis/reachability/signature_scan.rb +2 -2
  66. data/lib/rigor/analysis/result.rb +1 -3
  67. data/lib/rigor/analysis/rule_catalog.rb +4 -1
  68. data/lib/rigor/analysis/run_cache_key.rb +1 -1
  69. data/lib/rigor/analysis/run_cache_probe.rb +3 -5
  70. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -8
  71. data/lib/rigor/analysis/runner/declaration_position.rb +1 -2
  72. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +316 -40
  73. data/lib/rigor/analysis/runner/effect_annotation_residual_pass.rb +2 -3
  74. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +9 -10
  75. data/lib/rigor/analysis/runner/pool_coordinator.rb +430 -77
  76. data/lib/rigor/analysis/runner/project_pre_passes.rb +12 -6
  77. data/lib/rigor/analysis/runner/run_snapshots.rb +13 -3
  78. data/lib/rigor/analysis/runner.rb +200 -43
  79. data/lib/rigor/analysis/worker_session.rb +36 -12
  80. data/lib/rigor/bleeding_edge.rb +12 -24
  81. data/lib/rigor/builtins/hkt_builtins.rb +2 -2
  82. data/lib/rigor/builtins/imported_refinements.rb +166 -30
  83. data/lib/rigor/builtins/predefined_constant_refinements.rb +5 -7
  84. data/lib/rigor/builtins/regex_refinement.rb +4 -4
  85. data/lib/rigor/builtins/static_return_refinements.rb +5 -6
  86. data/lib/rigor/cache/annotation_location.rb +70 -0
  87. data/lib/rigor/cache/descriptor.rb +58 -20
  88. data/lib/rigor/cache/engine_source.rb +4 -4
  89. data/lib/rigor/cache/file_digest.rb +9 -1
  90. data/lib/rigor/cache/incremental_snapshot.rb +40 -6
  91. data/lib/rigor/cache/rbs_class_ancestor_table.rb +0 -3
  92. data/lib/rigor/cache/rbs_class_type_param_names.rb +0 -3
  93. data/lib/rigor/cache/rbs_constant_table.rb +0 -3
  94. data/lib/rigor/cache/rbs_descriptor.rb +79 -11
  95. data/lib/rigor/cache/rbs_environment.rb +8 -4
  96. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +39 -0
  97. data/lib/rigor/cache/rbs_known_class_names.rb +0 -3
  98. data/lib/rigor/cache/store.rb +85 -33
  99. data/lib/rigor/cli/annotate_command.rb +5 -6
  100. data/lib/rigor/cli/check_command.rb +57 -10
  101. data/lib/rigor/cli/check_invocation.rb +6 -11
  102. data/lib/rigor/cli/check_runner_factory.rb +1 -5
  103. data/lib/rigor/cli/coverage_command.rb +1 -1
  104. data/lib/rigor/cli/coverage_mutation.rb +12 -3
  105. data/lib/rigor/cli/coverage_scan.rb +1 -5
  106. data/lib/rigor/cli/diff_command.rb +1 -1
  107. data/lib/rigor/cli/doc_links.rb +3 -3
  108. data/lib/rigor/cli/docs_command.rb +2 -2
  109. data/lib/rigor/cli/doctor_command.rb +85 -38
  110. data/lib/rigor/cli/effects_command.rb +2 -2
  111. data/lib/rigor/cli/effects_diff_renderer.rb +3 -3
  112. data/lib/rigor/cli/effects_snapshot_command.rb +1 -1
  113. data/lib/rigor/cli/explain_command.rb +34 -1
  114. data/lib/rigor/cli/fused_protection_report.rb +7 -1
  115. data/lib/rigor/cli/lsp_command.rb +1 -1
  116. data/lib/rigor/cli/mcp_command.rb +1 -1
  117. data/lib/rigor/cli/measurement_integrity_warning.rb +4 -5
  118. data/lib/rigor/cli/mutation_fork_scan.rb +6 -6
  119. data/lib/rigor/cli/mutation_protection_report.rb +7 -1
  120. data/lib/rigor/cli/plugin_command.rb +1 -1
  121. data/lib/rigor/cli/plugins_command.rb +2 -1
  122. data/lib/rigor/cli/prism_colorizer.rb +2 -2
  123. data/lib/rigor/cli/protection_fork_scan.rb +6 -6
  124. data/lib/rigor/cli/show_bleedingedge_command.rb +1 -1
  125. data/lib/rigor/cli/sig_gen_command.rb +31 -2
  126. data/lib/rigor/cli/skill_command.rb +1 -1
  127. data/lib/rigor/cli/skill_deep_probe.rb +4 -4
  128. data/lib/rigor/cli/skill_describe.rb +36 -35
  129. data/lib/rigor/cli/trace_command.rb +4 -4
  130. data/lib/rigor/cli/trace_renderer.rb +5 -6
  131. data/lib/rigor/cli/triage_command.rb +1 -1
  132. data/lib/rigor/cli/type_of_command.rb +10 -6
  133. data/lib/rigor/cli/type_scan_command.rb +4 -4
  134. data/lib/rigor/cli/unused_command.rb +11 -3
  135. data/lib/rigor/cli/upgrade_command.rb +1 -1
  136. data/lib/rigor/cli.rb +68 -6
  137. data/lib/rigor/config_audit.rb +30 -5
  138. data/lib/rigor/configuration/severity_profile.rb +6 -6
  139. data/lib/rigor/configuration.rb +43 -4
  140. data/lib/rigor/effects/attribution.rb +1 -3
  141. data/lib/rigor/effects/config_envelopes.rb +6 -8
  142. data/lib/rigor/effects/definition_lines.rb +25 -6
  143. data/lib/rigor/effects/effect_table.rb +0 -1
  144. data/lib/rigor/effects/entry_points.rb +0 -2
  145. data/lib/rigor/effects/envelope_check.rb +8 -8
  146. data/lib/rigor/effects/envelope_index.rb +5 -8
  147. data/lib/rigor/effects/framework_units.rb +4 -6
  148. data/lib/rigor/effects/identity.rb +4 -6
  149. data/lib/rigor/effects/inline_anchor.rb +7 -7
  150. data/lib/rigor/effects/label_intent.rb +3 -4
  151. data/lib/rigor/effects/liskov_check.rb +8 -8
  152. data/lib/rigor/effects/method_key.rb +1 -1
  153. data/lib/rigor/effects/plugin_facts.rb +4 -6
  154. data/lib/rigor/effects/propagator.rb +2 -3
  155. data/lib/rigor/effects/scanner.rb +1 -1
  156. data/lib/rigor/effects/signature_sources.rb +3 -5
  157. data/lib/rigor/effects/snapshot.rb +10 -10
  158. data/lib/rigor/effects/snapshot_diff.rb +1 -1
  159. data/lib/rigor/effects/unit_scan.rb +10 -10
  160. data/lib/rigor/effects/unknown_label_check.rb +3 -8
  161. data/lib/rigor/effects/unknown_label_report.rb +3 -4
  162. data/lib/rigor/environment/bundle_sig_discovery.rb +6 -6
  163. data/lib/rigor/environment/class_registry.rb +3 -1
  164. data/lib/rigor/environment/failure_slot.rb +28 -0
  165. data/lib/rigor/environment/installed_gem_set.rb +85 -0
  166. data/lib/rigor/environment/lockfile_resolver.rb +51 -4
  167. data/lib/rigor/environment/missing_gem_constant_index.rb +4 -4
  168. data/lib/rigor/environment/rbs_collection_discovery.rb +5 -5
  169. data/lib/rigor/environment/rbs_coverage_report.rb +5 -5
  170. data/lib/rigor/environment/rbs_hierarchy.rb +3 -1
  171. data/lib/rigor/environment/rbs_loader.rb +613 -113
  172. data/lib/rigor/environment.rb +189 -27
  173. data/lib/rigor/flow_contribution/merger.rb +0 -2
  174. data/lib/rigor/flow_contribution.rb +11 -13
  175. data/lib/rigor/inference/acceptance.rb +153 -8
  176. data/lib/rigor/inference/block_parameter_binder.rb +2 -3
  177. data/lib/rigor/inference/body_fixpoint.rb +5 -5
  178. data/lib/rigor/inference/budget_trace.rb +1 -5
  179. data/lib/rigor/inference/captured_locals.rb +2 -3
  180. data/lib/rigor/inference/closure_escape_analyzer.rb +2 -4
  181. data/lib/rigor/inference/coverage_scanner.rb +4 -6
  182. data/lib/rigor/inference/dynamic_origin.rb +1 -1
  183. data/lib/rigor/inference/element_read_widening.rb +168 -0
  184. data/lib/rigor/inference/expression_typer.rb +763 -143
  185. data/lib/rigor/inference/fork_map.rb +5 -7
  186. data/lib/rigor/inference/hkt_reducer.rb +2 -3
  187. data/lib/rigor/inference/hkt_registry.rb +19 -10
  188. data/lib/rigor/inference/hkt_sugar_translator.rb +15 -21
  189. data/lib/rigor/inference/index_write_widening.rb +1 -5
  190. data/lib/rigor/inference/macro_block_self_type.rb +1 -4
  191. data/lib/rigor/inference/method_dispatcher/block_folding.rb +2 -9
  192. data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +1 -1
  193. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +305 -19
  194. data/lib/rigor/inference/method_dispatcher/data_folding.rb +1 -1
  195. data/lib/rigor/inference/method_dispatcher/file_folding.rb +1 -1
  196. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +2 -2
  197. data/lib/rigor/inference/method_dispatcher/json_folding.rb +1 -1
  198. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -1
  199. data/lib/rigor/inference/method_dispatcher/math_folding.rb +41 -3
  200. data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -5
  201. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +61 -47
  202. data/lib/rigor/inference/method_dispatcher/random_folding.rb +82 -0
  203. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +165 -31
  204. data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +25 -4
  205. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +2 -8
  206. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +1 -1
  207. data/lib/rigor/inference/method_dispatcher/set_folding.rb +1 -1
  208. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +1 -1
  209. data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +1 -1
  210. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +1 -2
  211. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +4 -5
  212. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +85 -4
  213. data/lib/rigor/inference/method_dispatcher/time_folding.rb +1 -1
  214. data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +1 -2
  215. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  216. data/lib/rigor/inference/method_dispatcher.rb +12 -13
  217. data/lib/rigor/inference/method_parameter_binder.rb +4 -6
  218. data/lib/rigor/inference/multi_target_binder.rb +1 -3
  219. data/lib/rigor/inference/mutation_rejoin.rb +168 -0
  220. data/lib/rigor/inference/mutation_widening.rb +100 -51
  221. data/lib/rigor/inference/narrowing.rb +299 -46
  222. data/lib/rigor/inference/optimistic_origin.rb +1 -7
  223. data/lib/rigor/inference/origin_lookup.rb +2 -3
  224. data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
  225. data/lib/rigor/inference/pre_eval_constants.rb +6 -5
  226. data/lib/rigor/inference/precision_scanner.rb +3 -4
  227. data/lib/rigor/inference/project_patched_methods.rb +2 -2
  228. data/lib/rigor/inference/project_patched_scanner.rb +3 -3
  229. data/lib/rigor/inference/protection_scanner.rb +1 -2
  230. data/lib/rigor/inference/range_constant.rb +57 -0
  231. data/lib/rigor/inference/rbs_type_translator.rb +49 -11
  232. data/lib/rigor/inference/receiver_alias.rb +3 -3
  233. data/lib/rigor/inference/refinement_mutation.rb +72 -0
  234. data/lib/rigor/inference/scope_indexer.rb +688 -151
  235. data/lib/rigor/inference/statement_evaluator.rb +221 -50
  236. data/lib/rigor/inference/string_mutation.rb +60 -0
  237. data/lib/rigor/inference/struct_fold_safety.rb +6 -7
  238. data/lib/rigor/inference/synthetic_method_index.rb +1 -2
  239. data/lib/rigor/inference/synthetic_method_scanner.rb +4 -6
  240. data/lib/rigor/inference/version_guard.rb +28 -21
  241. data/lib/rigor/inference/void_origin.rb +3 -3
  242. data/lib/rigor/inference/void_tail_summary.rb +2 -4
  243. data/lib/rigor/language_server/buffer_table.rb +4 -4
  244. data/lib/rigor/language_server/completion_provider.rb +1 -1
  245. data/lib/rigor/language_server/debouncer.rb +1 -1
  246. data/lib/rigor/language_server/diagnostic_publisher.rb +4 -4
  247. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  248. data/lib/rigor/language_server/folding_range_provider.rb +1 -1
  249. data/lib/rigor/language_server/hover_provider.rb +1 -1
  250. data/lib/rigor/language_server/hover_renderer.rb +2 -2
  251. data/lib/rigor/language_server/incremental_sync.rb +6 -6
  252. data/lib/rigor/language_server/project_context.rb +2 -5
  253. data/lib/rigor/language_server/publish_batcher.rb +2 -2
  254. data/lib/rigor/language_server/selection_range_provider.rb +2 -2
  255. data/lib/rigor/language_server/server.rb +9 -9
  256. data/lib/rigor/language_server/signature_help_provider.rb +1 -1
  257. data/lib/rigor/language_server/uri.rb +1 -1
  258. data/lib/rigor/plugin/base.rb +90 -21
  259. data/lib/rigor/plugin/bundled_catalog.rb +167 -0
  260. data/lib/rigor/plugin/effect_attribution.rb +7 -7
  261. data/lib/rigor/plugin/effect_entry_points.rb +3 -3
  262. data/lib/rigor/plugin/fact_store.rb +6 -6
  263. data/lib/rigor/plugin/io_boundary.rb +93 -8
  264. data/lib/rigor/plugin/isolation.rb +29 -6
  265. data/lib/rigor/plugin/loader.rb +31 -12
  266. data/lib/rigor/plugin/macro/heredoc_template.rb +1 -1
  267. data/lib/rigor/plugin/macro/trait_registry.rb +1 -1
  268. data/lib/rigor/plugin/manifest.rb +23 -1
  269. data/lib/rigor/plugin/registry.rb +67 -14
  270. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +12 -1
  271. data/lib/rigor/plugin/trust_policy.rb +2 -4
  272. data/lib/rigor/plugin/type_node_resolver.rb +3 -3
  273. data/lib/rigor/plugin_gap_advisory.rb +96 -0
  274. data/lib/rigor/project_environment.rb +138 -0
  275. data/lib/rigor/protection/analysis_guard.rb +93 -14
  276. data/lib/rigor/protection/closure_kill_oracle.rb +67 -23
  277. data/lib/rigor/protection/dependency_closure.rb +5 -8
  278. data/lib/rigor/protection/diagnostic_oracle.rb +2 -2
  279. data/lib/rigor/protection/discovery_seed.rb +11 -11
  280. data/lib/rigor/protection/kill_signature.rb +2 -4
  281. data/lib/rigor/protection/measurement_integrity.rb +1 -3
  282. data/lib/rigor/protection/mutation_cache.rb +6 -9
  283. data/lib/rigor/protection/mutation_scanner.rb +12 -15
  284. data/lib/rigor/protection/mutator.rb +2 -1
  285. data/lib/rigor/protection/test_suite_oracle.rb +5 -5
  286. data/lib/rigor/rbs_extended/conformance_checker.rb +4 -3
  287. data/lib/rigor/rbs_extended/envelope_scanner.rb +12 -13
  288. data/lib/rigor/rbs_extended/hkt_directives.rb +16 -1
  289. data/lib/rigor/rbs_extended/reporter.rb +122 -16
  290. data/lib/rigor/rbs_extended.rb +67 -22
  291. data/lib/rigor/reflection/constant_path.rb +126 -0
  292. data/lib/rigor/reflection.rb +26 -10
  293. data/lib/rigor/runtime/jit.rb +6 -8
  294. data/lib/rigor/scope/discovery_index.rb +30 -0
  295. data/lib/rigor/scope.rb +203 -4
  296. data/lib/rigor/sig_gen/generator.rb +181 -102
  297. data/lib/rigor/sig_gen/layout_index.rb +3 -4
  298. data/lib/rigor/sig_gen/meta_class_shape.rb +3 -5
  299. data/lib/rigor/sig_gen/observation_collector.rb +17 -15
  300. data/lib/rigor/sig_gen/path_mapper.rb +4 -6
  301. data/lib/rigor/sig_gen/rbs_validity.rb +4 -4
  302. data/lib/rigor/sig_gen/renderer.rb +17 -11
  303. data/lib/rigor/sig_gen/skip_reason_catalog.rb +111 -0
  304. data/lib/rigor/sig_gen/superclass_spelling.rb +27 -0
  305. data/lib/rigor/sig_gen/type_elaborator.rb +1 -3
  306. data/lib/rigor/sig_gen/writer.rb +23 -6
  307. data/lib/rigor/signature_path_audit.rb +153 -6
  308. data/lib/rigor/source/literals.rb +0 -23
  309. data/lib/rigor/source/node_children.rb +0 -2
  310. data/lib/rigor/source/node_locator.rb +5 -11
  311. data/lib/rigor/source/node_walker.rb +2 -5
  312. data/lib/rigor/triage/catalogue.rb +1 -3
  313. data/lib/rigor/triage.rb +3 -5
  314. data/lib/rigor/type/accepts_result.rb +24 -6
  315. data/lib/rigor/type/combinator.rb +68 -5
  316. data/lib/rigor/type/data_class.rb +2 -2
  317. data/lib/rigor/type/data_instance.rb +4 -4
  318. data/lib/rigor/type/float_range.rb +128 -0
  319. data/lib/rigor/type/hash_shape.rb +5 -5
  320. data/lib/rigor/type/integer_range.rb +13 -8
  321. data/lib/rigor/type/nominal.rb +8 -2
  322. data/lib/rigor/type/refined.rb +3 -3
  323. data/lib/rigor/type/struct_class.rb +3 -3
  324. data/lib/rigor/type/struct_instance.rb +4 -4
  325. data/lib/rigor/type.rb +1 -0
  326. data/lib/rigor/type_node/generic.rb +1 -1
  327. data/lib/rigor/type_node/range_literal.rb +25 -0
  328. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  329. data/lib/rigor/type_node.rb +1 -0
  330. data/lib/rigor/version.rb +1 -1
  331. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +0 -4
  332. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +0 -1
  333. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +0 -2
  334. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -0
  335. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +0 -4
  336. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +3 -5
  337. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +1 -4
  338. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +1 -0
  339. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +33 -30
  340. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +0 -1
  341. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -3
  342. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +28 -2
  343. data/plugins/rigor-actionpack/sig/action_controller.rbs +71 -0
  344. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +0 -4
  345. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
  346. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +0 -1
  347. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +0 -2
  348. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +1 -1
  349. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +1 -0
  350. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +3 -4
  351. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +1 -2
  352. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +1 -2
  353. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +42 -7
  354. data/plugins/rigor-activerecord/sig/active_record/framework.rbs +123 -0
  355. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +1 -0
  356. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +9 -2
  357. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +119 -14
  358. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +1 -0
  359. data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +1 -0
  360. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +9 -7
  361. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +7 -3
  362. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +1 -0
  363. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +8 -6
  364. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +11 -7
  365. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -11
  366. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +9 -4
  367. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +1 -0
  368. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +0 -5
  369. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +0 -1
  370. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +0 -1
  371. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +8 -1
  372. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +36 -4
  373. data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +1 -0
  374. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +8 -6
  375. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +6 -3
  376. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +1 -0
  377. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -1
  378. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +0 -3
  379. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +1 -0
  380. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +0 -5
  381. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -2
  382. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +0 -1
  383. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +0 -1
  384. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +1 -0
  385. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +0 -12
  386. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +1 -3
  387. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +0 -1
  388. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +1 -0
  389. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/acronyms.rb +5 -5
  390. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -10
  391. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +3 -3
  392. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +2 -2
  393. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +2 -2
  394. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +2 -2
  395. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +9 -10
  396. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +2 -3
  397. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -0
  398. data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +1 -0
  399. data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +13 -1
  400. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +128 -3
  401. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +0 -3
  402. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +0 -3
  403. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -5
  404. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +2 -3
  405. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +1 -0
  406. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +0 -3
  407. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +1 -0
  408. data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +1 -0
  409. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +1 -9
  410. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +1 -0
  411. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +0 -4
  412. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/effects.rb +1 -1
  413. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +1 -1
  414. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +0 -1
  415. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +0 -2
  416. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +1 -0
  417. data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -0
  418. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +2 -5
  419. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +1 -4
  420. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +3 -9
  421. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +4 -4
  422. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +1 -2
  423. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +4 -5
  424. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +7 -5
  425. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +5 -5
  426. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +1 -0
  427. data/sig/rigor/analysis/reachability/scan_cache.rbs +12 -0
  428. data/sig/rigor/cache.rbs +2 -3
  429. data/sig/rigor/environment.rbs +11 -0
  430. data/sig/rigor/inference.rbs +3 -10
  431. data/sig/rigor/plugin/io_boundary.rbs +4 -0
  432. data/sig/rigor/plugin/manifest.rbs +2 -1
  433. data/sig/rigor/reflection.rbs +3 -1
  434. data/sig/rigor/scope.rbs +24 -3
  435. data/sig/rigor/sig_gen/skip_reason_catalog.rbs +14 -0
  436. data/sig/rigor/type.rbs +27 -1
  437. data/sig/rigor.rbs +1 -0
  438. data/skills/rigor-ask/SKILL.md +8 -6
  439. data/skills/rigor-plugin-author/references/02-walker-and-types.md +13 -2
  440. data/skills/rigor-project-init/SKILL.md +6 -3
  441. data/skills/rigor-project-init/references/02-configure.md +10 -6
  442. data/skills/rigor-project-init/references/06-agent-contract.md +67 -0
  443. data/skills/rigor-type-oracle/SKILL.md +228 -0
  444. data/skills/rigor-type-oracle/references/01-oracle-commands.md +261 -0
  445. data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +52 -0
  446. data/skills/rigor-type-oracle/references/03-gap-protocol.md +128 -0
  447. data/skills/rigor-unused-adjudicate/SKILL.md +8 -5
  448. metadata +28 -3
  449. data/lib/rigor/cli/probe_environment.rb +0 -85
  450. data/sig/rigor/inference/builtins/numeric_catalog.rbs +0 -3
@@ -2,6 +2,8 @@
2
2
 
3
3
  require "yaml"
4
4
 
5
+ require_relative "../plugin_gap_advisory"
6
+
5
7
  module Rigor
6
8
  class CLI
7
9
  # The presence-only project-state probe behind `rigor skill describe` (ADR-73 WD2). It stats files and opens only
@@ -17,19 +19,11 @@ module Rigor
17
19
  # RBS into the signature paths, so its presence is the signal that the gem-RBS gap has been addressed.
18
20
  RBS_COLLECTION_LOCKFILE = "rbs_collection.lock.yaml"
19
21
 
20
- # `Gemfile.lock` substrings that mark a Rails app, and the bundled Rails-family plugin ids — used to spot a
21
- # configured Rails project that has not enabled any Rails plugin (a `rigor-plugin-tune` cue).
22
- RAILS_LOCK_MARKERS = %w[railties actionpack activerecord actioncable].freeze
23
- RAILS_PLUGIN_MARKERS = %w[
24
- rigor-activerecord rigor-actionpack rigor-actionmailer rigor-activejob
25
- rigor-rails-routes rigor-rails-i18n rigor-actioncable rigor-activestorage rigor-rails
26
- ].freeze
27
-
28
22
  def initialize(root)
29
23
  @root = root
30
24
  end
31
25
 
32
- # @return [Hash] the presence-only state the recommendation routes on.
26
+ # @return the presence-only state the recommendation routes on.
33
27
  def to_h
34
28
  config = CONFIG_FILENAMES.find { |name| File.file?(File.join(@root, name)) }
35
29
  {
@@ -38,7 +32,7 @@ module Rigor
38
32
  sig: File.directory?(File.join(@root, "sig")),
39
33
  gems: File.file?(File.join(@root, "Gemfile.lock")),
40
34
  rbs_collection: File.file?(File.join(@root, RBS_COLLECTION_LOCKFILE)),
41
- rails_unconfigured: rails_unconfigured?(config),
35
+ plugins_unconfigured: plugins_unconfigured?(config),
42
36
  ci: ci_state,
43
37
  editor: editor_state,
44
38
  mcp: mcp_state
@@ -47,24 +41,26 @@ module Rigor
47
41
 
48
42
  private
49
43
 
50
- # True when Rails is in `Gemfile.lock` but the (present) config enables no Rails plugin — so `rigor-plugin-tune`
51
- # (wiring the ActiveRecord / routes / i18n plugins) buys more than community RBS would (the 20260620 field trial's
52
- # strap case). Only fires on an already-configured project; an un-configured Rails app routes to
53
- # `rigor-project-init`, which selects the plugins itself.
54
- def rails_unconfigured?(config)
44
+ # ADR-96 WD2 — true when the project depends on gems that bundled plugins model and enables none of those
45
+ # plugins, so `rigor-plugin-tune` buys more than community RBS would (the 20260620 field trial's strap
46
+ # case). Reads each plugin's `target_gems:` rather than the Rails-only table it replaces, so the cue
47
+ # covers every framework Rigor ships a plugin for. Only fires on an already-configured project; an
48
+ # un-configured app routes to `rigor-project-init`, which selects the plugins itself.
49
+ def plugins_unconfigured?(config)
55
50
  return false if config.nil?
56
51
 
57
- lock = File.join(@root, "Gemfile.lock")
58
- return false unless File.file?(lock) && file_mentions_any?(lock, RAILS_LOCK_MARKERS)
59
-
60
- !file_mentions_any?(File.join(@root, config), RAILS_PLUGIN_MARKERS)
52
+ plugins = configured_plugins(File.join(@root, config))
53
+ PluginGapAdvisory.unconfigured?(project_root: @root, plugins: plugins)
61
54
  end
62
55
 
63
- def file_mentions_any?(path, markers)
64
- content = File.read(path)
65
- markers.any? { |marker| content.include?(marker) }
56
+ # The probe reads the config file itself rather than {Rigor::Configuration}: it must stay a
57
+ # presence-only probe, and `Configuration.load` applies defaults (including the ADR-93 auto-wired
58
+ # `rigor-rbs-inline` entry) that have nothing to do with the question asked here.
59
+ def configured_plugins(path)
60
+ data = YAML.safe_load_file(path, permitted_classes: [], aliases: true)
61
+ data.is_a?(Hash) ? Array(data["plugins"]) : []
66
62
  rescue StandardError
67
- false
63
+ []
68
64
  end
69
65
 
70
66
  # `:wired` (a CI config mentions `rigor`), `:unwired` (a CI config exists but does not), or `:none`.
@@ -121,9 +117,9 @@ module Rigor
121
117
  # The entry-point SKILL itself — excluded from the catalogue because it is the skill being run, not a destination.
122
118
  ENTRY_POINT_SKILL = "rigor-next-steps"
123
119
 
124
- # Adoption-journey order for the catalogue and the order the recommendation decision tree walks. `rigor-ask` sits
125
- # last: it is the journey-agnostic "answer a question about Rigor" companion the agent can offer at any point,
126
- # never a presence-recommended step.
120
+ # Adoption-journey order for the catalogue and the order the recommendation decision tree walks. The last two are
121
+ # journey-agnostic companions the agent can offer at any point, never presence-recommended steps:
122
+ # `rigor-type-oracle` fires on an event (being about to write a type), and `rigor-ask` on a question about Rigor.
127
123
  CATALOG_ORDER = %w[
128
124
  rigor-project-init
129
125
  rigor-rbs-setup
@@ -137,12 +133,13 @@ module Rigor
137
133
  rigor-plugin-author
138
134
  rigor-upgrade
139
135
  rigor-doctor
136
+ rigor-type-oracle
140
137
  rigor-ask
141
138
  ].freeze
142
139
 
143
- # @param skills [Array<Hash>] discovered skills, each `{name:, path:}`.
144
- # @param root [String] project root to probe (defaults to the cwd).
145
- # @param deep [Boolean] opt into `--deep`: run a real `rigor check` and let its result pick the headline. Off by
140
+ # @param skills — discovered skills, each `{name:, path:}`.
141
+ # @param root — project root to probe (defaults to the cwd).
142
+ # @param deep — opt into `--deep`: run a real `rigor check` and let its result pick the headline. Off by
146
143
  # default, and off is ADR-73 WD2's contract — see {SkillDeepProbe} for why the whole analysis path lives
147
144
  # behind this one flag and in its own file.
148
145
  def initialize(skills:, root: Dir.pwd, deep: false)
@@ -151,7 +148,7 @@ module Rigor
151
148
  @deep = deep
152
149
  end
153
150
 
154
- # @return [String] the full describe report.
151
+ # @return the full describe report.
155
152
  def render
156
153
  catalog = catalog_skills
157
154
  state = ProjectStateProbe.new(@root).to_h
@@ -202,14 +199,18 @@ module Rigor
202
199
  def recommended_name_and_reason(state)
203
200
  if state.fetch(:config).nil?
204
201
  ["rigor-project-init", "this project has no Rigor configuration yet — start here."]
205
- elsif state.fetch(:rails_unconfigured)
202
+ elsif state.fetch(:plugins_unconfigured)
206
203
  ["rigor-plugin-tune",
207
- "Rails is in your Gemfile.lock but no Rails plugins are enabled — wire them so " \
208
- "ActiveRecord / routes / i18n calls resolve (a bigger win here than community RBS)."]
209
- elsif state.fetch(:gems) && !state.fetch(:rbs_collection)
210
- ["rigor-rbs-setup", "your gems ship no community RBS yet — install it so Rigor stops typing them as Dynamic."]
204
+ "your Gemfile declares gems Rigor ships plugins for and none is enabled — wire them so " \
205
+ "those framework calls resolve (a bigger win here than community RBS)."]
206
+ # ADR-73's second `rbs-setup` priority-softening case (2026-09-10): CI wiring is presence-only and
207
+ # side-effect-free, while community-RBS install is network-bound (`rbs collection install` hits
208
+ # rubygems.org). A configured project earns the cheap, local win first — checked ahead of the RBS
209
+ # gap rather than after it.
211
210
  elsif state.fetch(:ci) != :wired
212
211
  ["rigor-ci-setup", "Rigor is configured but not wired into CI — lock in the regression guard."]
212
+ elsif state.fetch(:gems) && !state.fetch(:rbs_collection)
213
+ ["rigor-rbs-setup", "your gems ship no community RBS yet — install it so Rigor stops typing them as Dynamic."]
213
214
  # A present baseline is deliberately NOT a recommendation trigger. A baseline is a healthy, finished onboarding
214
215
  # state, not a problem to work off; pushing every baselined project to "reduce it" turns a working build into a
215
216
  # chore and tempts scattering `# rigor:disable` through the code to make a number go down — means over ends.
@@ -12,7 +12,7 @@ require_relative "../inference/flow_tracer"
12
12
  require_relative "../inference/scope_indexer"
13
13
  require_relative "command"
14
14
  require_relative "trace_renderer"
15
- require_relative "probe_environment"
15
+ require_relative "../project_environment"
16
16
 
17
17
  module Rigor
18
18
  class CLI
@@ -30,7 +30,7 @@ module Rigor
30
30
  DEFAULT_KINDS = %i[bind union dispatch].freeze
31
31
  VERBOSE_KINDS = (%i[enter result] + DEFAULT_KINDS).freeze
32
32
 
33
- # @return [Integer] CLI exit status.
33
+ # @return CLI exit status.
34
34
  def run
35
35
  options = parse_options
36
36
  file = @argv.first
@@ -92,10 +92,10 @@ module Rigor
92
92
  end
93
93
 
94
94
  # Mirrors the single-file path `rigor type-of` takes: the plugin-aware environment (so a trace replays the
95
- # same dispatch `rigor check` would, including plugin-synthesized RBS — see {ProbeEnvironment}), an empty
95
+ # same dispatch `rigor check` would, including plugin-synthesized RBS — see {ProjectEnvironment}), an empty
96
96
  # seed scope, one statement-level evaluation of the whole program — but recorded under the FlowTracer.
97
97
  def record_events(root, file, configuration)
98
- environment = ProbeEnvironment.build(configuration: configuration, source_files: [file])
98
+ environment = ProjectEnvironment.build(configuration: configuration, source_files: [file])
99
99
  scope = Scope.empty(environment: environment, source_path: file)
100
100
  Inference::FlowTracer.record { scope.evaluate(root) }
101
101
  end
@@ -28,9 +28,8 @@ module Rigor
28
28
  BODY_HEIGHT_MIN = 3 # never shrink the source window below this
29
29
  DEFAULT_SIZE = [24, 80].freeze
30
30
 
31
- # @param out [IO]
32
- # @param source [String] the traced file's source.
33
- # @param file [String] display path.
31
+ # @param source — the traced file's source.
32
+ # @param file — display path.
34
33
  def initialize(out:, source:, file:)
35
34
  @out = out
36
35
  @source = source
@@ -38,11 +37,11 @@ module Rigor
38
37
  @lines = source.lines.map(&:chomp)
39
38
  end
40
39
 
41
- # @param events [Array<Rigor::Inference::FlowTracer::Event>] the
40
+ # @param events — the
42
41
  # pre-filtered frame list (the command owns kind filtering).
43
- # @param delay [Float, nil] seconds between frames (autoplay);
42
+ # @param delay — seconds between frames (autoplay);
44
43
  # nil = step on key press when interactive.
45
- # @param interactive [Boolean] whether to clear/redraw and wait.
44
+ # @param interactive — whether to clear/redraw and wait.
46
45
  def play(events, delay: nil, interactive: false)
47
46
  @rows, @cols = interactive ? terminal_size : DEFAULT_SIZE
48
47
  @interactive = interactive
@@ -22,7 +22,7 @@ module Rigor
22
22
  USAGE = "Usage: rigor triage [options] [paths]"
23
23
  DEFAULT_SECTIONS = %i[distribution selectors hotspots hints].freeze
24
24
 
25
- # @return [Integer] CLI exit status (always 0).
25
+ # @return CLI exit status (always 0).
26
26
  def run
27
27
  options = parse_options
28
28
  configuration = Configuration.load(options.fetch(:config))
@@ -15,7 +15,7 @@ require_relative "../source/node_children"
15
15
  require_relative "type_of_renderer"
16
16
  require_relative "command"
17
17
  require_relative "options"
18
- require_relative "probe_environment"
18
+ require_relative "../project_environment"
19
19
 
20
20
  module Rigor
21
21
  class CLI
@@ -46,7 +46,7 @@ module Rigor
46
46
  LINE_ENUMERATION_CAP = 40
47
47
  private_constant :LINE_ENUMERATION_CAP
48
48
 
49
- # @return [Integer] CLI exit status.
49
+ # @return CLI exit status.
50
50
  def run
51
51
  options = parse_options
52
52
  buffer = Options.resolve_buffer_binding(options, err: @err)
@@ -111,8 +111,12 @@ module Rigor
111
111
  return 1 if parse_errors?(parse_result, file)
112
112
 
113
113
  # 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)
114
+ # `type_of` calls below. `with_source_path(file)` (like `annotate` and `trace` already do) matters
115
+ # beyond diagnostics attribution: a plugin `dynamic_return file_methods:` rule (ADR-52 slice 5a,
116
+ # e.g. rigor-rspec's `let`-binding resolver, #921) gates on `scope.source_path` and fails closed
117
+ # when it is nil — an unset source_path silently declined every such rule under this command.
118
+ scope_index = Inference::ScopeIndexer.index(parse_result.value,
119
+ default_scope: base_scope.with_source_path(file))
116
120
  locator = Source::NodeLocator.new(source: source, root: parse_result.value)
117
121
  lines = indexed_targets.filter_map { |target, _index| target.line if target.column.nil? }
118
122
  line_nodes, line_totals = collect_line_nodes(parse_result.value, lines)
@@ -196,12 +200,12 @@ module Rigor
196
200
 
197
201
  # Builds the plugin-aware environment relative to the probed file, so the reported type matches what `rigor
198
202
  # 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
203
+ # ADR-93 auto-wired `rigor-rbs-inline` plugin (see {ProjectEnvironment} for the #162 misattribution this
200
204
  # closes). The probed file is threaded as the synthesizer's `source_files:`. Project-RBS auto-detection
201
205
  # roots at CWD today; future work will walk parent directories to find the enclosing `Gemfile`/`*.gemspec`
202
206
  # so probes against files outside the current process's CWD still see the right `sig/` tree.
203
207
  def project_environment(files, configuration)
204
- ProbeEnvironment.build(configuration: configuration, source_files: files)
208
+ ProjectEnvironment.build(configuration: configuration, source_files: files)
205
209
  end
206
210
 
207
211
  def file_exists?(file)
@@ -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
@@ -169,6 +203,30 @@ 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.
@@ -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
@@ -320,7 +377,10 @@ module Rigor
320
377
  @err.puts "Install it with: gem install rigor-playground"
321
378
  return EXIT_USAGE
322
379
  end
323
- Rigor::CLI::PlaygroundCommand.new(@argv[1..], @out, @err).run
380
+ # `run` shifts the verb off `@argv` before dispatching, so the handler receives the arguments
381
+ # already. Slicing here dropped the first one — `rigor playground --port=4000` served the
382
+ # default port, and a bare `rigor playground` passed `nil` into a command that iterates it.
383
+ Rigor::CLI::PlaygroundCommand.new(@argv, @out, @err).run
324
384
  end
325
385
 
326
386
  def run_skill
@@ -383,11 +443,13 @@ module Rigor
383
443
  (opt-in; effects update/check/diff/explain)
384
444
  explain Print the description of one or all CheckRules
385
445
  diff Compare current diagnostics to a saved baseline JSON
446
+ baseline Manage the baseline file (baseline generate/regenerate/dump/drift/prune)
386
447
  sig-gen Emit RBS skeletons inferred from .rb sources
387
448
  lsp Run the Rigor Language Server (LSP) over stdio
388
449
  mcp Run the Rigor MCP server over stdio
389
450
  triage Summarise diagnostics: distribution, hotspots, hints
390
451
  coverage Report type-precision coverage (precise vs Dynamic ratio)
452
+ unused Report unreferenced classes, modules and constants as removal candidates
391
453
  plugins Report activation status of every configured plugin
392
454
  plugin Browse bundled plugin source as worked examples (list/path/print/root)
393
455
  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
@@ -167,20 +167,20 @@ module Rigor
167
167
  # Resolves the configured severity for a diagnostic given the active profile and any per-rule
168
168
  # overrides.
169
169
  #
170
- # @param rule [String, nil] canonical rule id (`call.undefined-method`).
171
- # @param authored_severity [Symbol] severity the rule emitted the diagnostic with (`:error`, `:warning`,
170
+ # @param rule — canonical rule id (`call.undefined-method`).
171
+ # @param authored_severity — severity the rule emitted the diagnostic with (`:error`, `:warning`,
172
172
  # `:info`).
173
- # @param profile [Symbol] one of {VALID_PROFILES}; falls back to {DEFAULT_PROFILE} for unknown values.
174
- # @param overrides [Hash{String => Symbol}] per-rule severity overrides from `.rigor.yml`'s
173
+ # @param profile — one of {VALID_PROFILES}; falls back to {DEFAULT_PROFILE} for unknown values.
174
+ # @param overrides — per-rule severity overrides from `.rigor.yml`'s
175
175
  # `severity_overrides:` map. Keys are canonical rule ids; values are {VALID_SEVERITIES} symbols.
176
176
  # Family-wildcard keys (`call`) match every rule under that prefix.
177
- # @param bleeding_edge_overrides [Hash{String => Symbol}] the severity map imposed by the active ADR-50
177
+ # @param bleeding_edge_overrides — the severity map imposed by the active ADR-50
178
178
  # § WD2 bleeding-edge features ({Rigor::BleedingEdge.severity_overrides_for}). Consulted *below* the
179
179
  # user's own `overrides` (so an explicit `severity_overrides:` entry, exact or family wildcard,
180
180
  # always wins) and *above* the profile table. Exact rule ids only — the overlay never carries family
181
181
  # wildcards. Empty while the overlay is unpopulated, so the default leaves resolution bit-for-bit
182
182
  # unchanged.
183
- # @return [Symbol] the resolved severity. Returns `:off` to mean "drop the diagnostic entirely".
183
+ # @return the resolved severity. Returns `:off` to mean "drop the diagnostic entirely".
184
184
  def resolve(rule:, authored_severity:, profile: DEFAULT_PROFILE, overrides: {}, bleeding_edge_overrides: {})
185
185
  return authored_severity if rule.nil?
186
186