rigortype 0.3.8 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (634) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/data/capability_roles/capability_roles.rbs +36 -0
  4. data/data/core_overlay/enumerable.rbs +51 -0
  5. data/data/core_overlay/enumerator.rbs +84 -0
  6. data/data/core_overlay/hash_rbs3.rbs +41 -0
  7. data/data/core_overlay/process.rbs +40 -0
  8. data/data/core_overlay/string_io.rbs +33 -0
  9. data/data/effects/core.yml +3 -3
  10. data/data/gem_overlay/activesupport/core_ext.rbs +236 -17
  11. data/docs/handbook/02-everyday-types.md +17 -14
  12. data/docs/handbook/03-narrowing.md +132 -14
  13. data/docs/handbook/04-tuples-and-shapes.md +8 -6
  14. data/docs/handbook/05-methods-and-blocks.md +1 -1
  15. data/docs/handbook/06-classes.md +2 -2
  16. data/docs/handbook/07-rbs-and-extended.md +19 -1
  17. data/docs/handbook/10-sorbet.md +9 -10
  18. data/docs/handbook/11-sig-gen.md +525 -13
  19. data/docs/handbook/README.md +1 -1
  20. data/docs/handbook/appendix-elixir.md +2 -2
  21. data/docs/handbook/appendix-go.md +2 -2
  22. data/docs/handbook/appendix-java-csharp.md +2 -2
  23. data/docs/handbook/appendix-mypy.md +2 -2
  24. data/docs/handbook/appendix-phpstan.md +1 -1
  25. data/docs/handbook/appendix-rust.md +1 -1
  26. data/docs/handbook/appendix-type-theory.md +4 -4
  27. data/docs/handbook/appendix-typescript.md +1 -1
  28. data/docs/llms.txt +2 -0
  29. data/docs/manual/02-cli-reference.md +95 -6
  30. data/docs/manual/03-configuration.md +18 -0
  31. data/docs/manual/04-diagnostics.md +15 -0
  32. data/docs/manual/07-plugins.md +15 -5
  33. data/docs/manual/08-skills.md +23 -2
  34. data/docs/manual/10-mcp-server.md +3 -2
  35. data/docs/manual/11-ci.md +9 -0
  36. data/docs/manual/15-type-protection-coverage.md +8 -0
  37. data/docs/manual/16-rbs-extended-annotations.md +46 -8
  38. data/docs/manual/18-removing-dead-code.md +10 -8
  39. data/docs/manual/19-effect-labels.md +10 -0
  40. data/docs/manual/plugins/README.md +7 -0
  41. data/docs/manual/plugins/rigor-actioncable.md +8 -1
  42. data/docs/manual/plugins/rigor-actionmailer.md +7 -0
  43. data/docs/manual/plugins/rigor-actionpack.md +261 -0
  44. data/docs/manual/plugins/rigor-active-model-serializers.md +153 -0
  45. data/docs/manual/plugins/rigor-activejob.md +7 -0
  46. data/docs/manual/plugins/rigor-activerecord.md +138 -4
  47. data/docs/manual/plugins/rigor-activestorage.md +8 -1
  48. data/docs/manual/plugins/rigor-activesupport-core-ext.md +43 -7
  49. data/docs/manual/plugins/rigor-grape.md +106 -0
  50. data/docs/manual/plugins/rigor-graphql.md +23 -2
  51. data/docs/manual/plugins/rigor-pundit.md +8 -1
  52. data/docs/manual/plugins/rigor-rails-i18n.md +10 -5
  53. data/docs/manual/plugins/rigor-rails-routes.md +7 -0
  54. data/docs/manual/plugins/rigor-rbs-inline.md +229 -1
  55. data/docs/manual/plugins/rigor-sidekiq.md +8 -1
  56. data/docs/manual/plugins/rigor-sorbet.md +21 -5
  57. data/exe/rigor +19 -4
  58. data/lib/rigor/analysis/baseline.rb +3 -3
  59. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +1 -1
  60. data/lib/rigor/analysis/check_rules/dead_version_guard_arms.rb +1 -5
  61. data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +167 -2
  62. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +10 -2
  63. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +189 -0
  64. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +6 -4
  65. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +15 -6
  66. data/lib/rigor/analysis/check_rules/rule_ids.rb +21 -6
  67. data/lib/rigor/analysis/check_rules/rule_walk.rb +41 -5
  68. data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +1 -1
  69. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +1 -1
  70. data/lib/rigor/analysis/check_rules/source_arity.rb +323 -0
  71. data/lib/rigor/analysis/check_rules/special_global_setters.rb +146 -0
  72. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +38 -4
  73. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +0 -1
  74. data/lib/rigor/analysis/check_rules.rb +718 -79
  75. data/lib/rigor/analysis/crash_signature.rb +8 -24
  76. data/lib/rigor/analysis/dependency_recorder.rb +23 -0
  77. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +1 -1
  78. data/lib/rigor/analysis/dependency_source_inference/builder.rb +0 -2
  79. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +0 -2
  80. data/lib/rigor/analysis/dependency_source_inference/index.rb +5 -5
  81. data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +1 -2
  82. data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
  83. data/lib/rigor/analysis/effects_cache_probe.rb +3 -4
  84. data/lib/rigor/analysis/erb_template_detector.rb +1 -2
  85. data/lib/rigor/analysis/fact_store.rb +15 -1
  86. data/lib/rigor/analysis/incremental.rb +42 -0
  87. data/lib/rigor/analysis/incremental_session.rb +117 -26
  88. data/lib/rigor/analysis/plugin_fact_fingerprint.rb +0 -1
  89. data/lib/rigor/analysis/project_scan.rb +11 -1
  90. data/lib/rigor/analysis/reachability/graph.rb +3 -5
  91. data/lib/rigor/analysis/reachability/plugin_roots.rb +5 -5
  92. data/lib/rigor/analysis/reachability/project_files.rb +2 -2
  93. data/lib/rigor/analysis/reachability/scan.rb +5 -5
  94. data/lib/rigor/analysis/reachability/scan_cache.rb +3 -2
  95. data/lib/rigor/analysis/reachability/signature_scan.rb +2 -2
  96. data/lib/rigor/analysis/result.rb +1 -3
  97. data/lib/rigor/analysis/rule_catalog.rb +131 -1
  98. data/lib/rigor/analysis/run_cache_key.rb +21 -12
  99. data/lib/rigor/analysis/run_cache_probe.rb +3 -5
  100. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -8
  101. data/lib/rigor/analysis/runner/declaration_position.rb +1 -2
  102. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +340 -33
  103. data/lib/rigor/analysis/runner/effect_annotation_residual_pass.rb +2 -3
  104. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +18 -14
  105. data/lib/rigor/analysis/runner/pool_coordinator.rb +357 -97
  106. data/lib/rigor/analysis/runner/project_pre_passes.rb +38 -13
  107. data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
  108. data/lib/rigor/analysis/runner.rb +407 -51
  109. data/lib/rigor/analysis/template_unit_collector.rb +303 -0
  110. data/lib/rigor/analysis/template_unit_paths.rb +91 -0
  111. data/lib/rigor/analysis/template_unit_positions.rb +293 -0
  112. data/lib/rigor/analysis/template_units.rb +399 -0
  113. data/lib/rigor/analysis/worker_session.rb +62 -20
  114. data/lib/rigor/bleeding_edge.rb +10 -24
  115. data/lib/rigor/builtins/hkt_builtins.rb +3 -2
  116. data/lib/rigor/builtins/imported_refinements.rb +159 -21
  117. data/lib/rigor/builtins/predefined_constant_refinements.rb +5 -7
  118. data/lib/rigor/builtins/regex_refinement.rb +21 -13
  119. data/lib/rigor/builtins/static_return_refinements.rb +7 -6
  120. data/lib/rigor/cache/annotation_location.rb +2 -4
  121. data/lib/rigor/cache/descriptor.rb +63 -20
  122. data/lib/rigor/cache/engine_source.rb +33 -5
  123. data/lib/rigor/cache/file_digest.rb +9 -1
  124. data/lib/rigor/cache/incremental_snapshot.rb +72 -6
  125. data/lib/rigor/cache/rbs_cache_producer.rb +4 -0
  126. data/lib/rigor/cache/rbs_class_ancestor_table.rb +0 -3
  127. data/lib/rigor/cache/rbs_class_type_param_names.rb +0 -3
  128. data/lib/rigor/cache/rbs_constant_table.rb +0 -3
  129. data/lib/rigor/cache/rbs_descriptor.rb +112 -14
  130. data/lib/rigor/cache/rbs_environment.rb +8 -4
  131. data/lib/rigor/cache/rbs_known_class_names.rb +0 -3
  132. data/lib/rigor/cache/store.rb +76 -32
  133. data/lib/rigor/ci_detector.rb +1 -0
  134. data/lib/rigor/cli/annotate_command.rb +5 -6
  135. data/lib/rigor/cli/check_command.rb +57 -10
  136. data/lib/rigor/cli/check_invocation.rb +6 -11
  137. data/lib/rigor/cli/check_runner_factory.rb +1 -5
  138. data/lib/rigor/cli/coverage_command.rb +1 -1
  139. data/lib/rigor/cli/coverage_mutation.rb +12 -3
  140. data/lib/rigor/cli/coverage_scan.rb +1 -5
  141. data/lib/rigor/cli/diff_command.rb +1 -1
  142. data/lib/rigor/cli/doc_links.rb +4 -4
  143. data/lib/rigor/cli/docs_command.rb +6 -6
  144. data/lib/rigor/cli/doctor_command.rb +85 -38
  145. data/lib/rigor/cli/effects_command.rb +2 -2
  146. data/lib/rigor/cli/effects_diff_renderer.rb +3 -3
  147. data/lib/rigor/cli/effects_snapshot_command.rb +1 -1
  148. data/lib/rigor/cli/explain_command.rb +34 -1
  149. data/lib/rigor/cli/fused_protection_report.rb +7 -1
  150. data/lib/rigor/cli/lsp_command.rb +1 -1
  151. data/lib/rigor/cli/mcp_command.rb +1 -1
  152. data/lib/rigor/cli/measurement_integrity_warning.rb +4 -5
  153. data/lib/rigor/cli/mutation_fork_scan.rb +6 -6
  154. data/lib/rigor/cli/mutation_protection_report.rb +7 -1
  155. data/lib/rigor/cli/plugin_command.rb +4 -4
  156. data/lib/rigor/cli/plugins_command.rb +2 -1
  157. data/lib/rigor/cli/prism_colorizer.rb +1 -2
  158. data/lib/rigor/cli/protection_fork_scan.rb +6 -6
  159. data/lib/rigor/cli/show_bleedingedge_command.rb +1 -1
  160. data/lib/rigor/cli/sig_gen_command.rb +212 -31
  161. data/lib/rigor/cli/skill_command.rb +2 -2
  162. data/lib/rigor/cli/skill_deep_probe.rb +4 -4
  163. data/lib/rigor/cli/skill_describe.rb +35 -35
  164. data/lib/rigor/cli/trace_command.rb +4 -4
  165. data/lib/rigor/cli/trace_renderer.rb +5 -6
  166. data/lib/rigor/cli/triage_command.rb +1 -1
  167. data/lib/rigor/cli/type_of_command.rb +29 -11
  168. data/lib/rigor/cli/type_of_renderer.rb +20 -9
  169. data/lib/rigor/cli/type_of_template_probe.rb +189 -0
  170. data/lib/rigor/cli/type_scan_command.rb +4 -4
  171. data/lib/rigor/cli/unused_command.rb +11 -3
  172. data/lib/rigor/cli/upgrade_command.rb +1 -1
  173. data/lib/rigor/cli.rb +89 -9
  174. data/lib/rigor/config_audit.rb +30 -5
  175. data/lib/rigor/configuration/severity_profile.rb +25 -9
  176. data/lib/rigor/configuration.rb +109 -7
  177. data/lib/rigor/effects/ancestry_recorder.rb +191 -0
  178. data/lib/rigor/effects/attribution.rb +12 -5
  179. data/lib/rigor/effects/callee_rule.rb +368 -0
  180. data/lib/rigor/effects/catalog.rb +7 -4
  181. data/lib/rigor/effects/collector.rb +11 -5
  182. data/lib/rigor/effects/config_envelopes.rb +15 -10
  183. data/lib/rigor/effects/definition_context.rb +179 -0
  184. data/lib/rigor/effects/definition_lines.rb +25 -6
  185. data/lib/rigor/effects/effect_table.rb +11 -4
  186. data/lib/rigor/effects/entry_points.rb +0 -2
  187. data/lib/rigor/effects/envelope_check.rb +9 -9
  188. data/lib/rigor/effects/envelope_index.rb +20 -8
  189. data/lib/rigor/effects/file_collection.rb +59 -5
  190. data/lib/rigor/effects/framework_units.rb +5 -7
  191. data/lib/rigor/effects/identity.rb +20 -6
  192. data/lib/rigor/effects/inline_anchor.rb +7 -7
  193. data/lib/rigor/effects/label_intent.rb +3 -4
  194. data/lib/rigor/effects/liskov_check.rb +8 -8
  195. data/lib/rigor/effects/local_ownership.rb +38 -12
  196. data/lib/rigor/effects/method_key.rb +22 -1
  197. data/lib/rigor/effects/mutation_classifier.rb +23 -12
  198. data/lib/rigor/effects/plugin_facts.rb +47 -37
  199. data/lib/rigor/effects/propagator.rb +297 -19
  200. data/lib/rigor/effects/registry.rb +1 -1
  201. data/lib/rigor/effects/scanner.rb +122 -73
  202. data/lib/rigor/effects/signature_sources.rb +4 -6
  203. data/lib/rigor/effects/snapshot.rb +12 -11
  204. data/lib/rigor/effects/snapshot_diff.rb +1 -1
  205. data/lib/rigor/effects/summary.rb +27 -4
  206. data/lib/rigor/effects/unit_scan.rb +393 -38
  207. data/lib/rigor/effects/unknown_label_check.rb +3 -8
  208. data/lib/rigor/effects/unknown_label_report.rb +3 -4
  209. data/lib/rigor/effects/visibility.rb +101 -0
  210. data/lib/rigor/environment/bundle_sig_discovery.rb +6 -6
  211. data/lib/rigor/environment/class_registry.rb +3 -1
  212. data/lib/rigor/environment/failure_slot.rb +2 -2
  213. data/lib/rigor/environment/installed_gem_set.rb +85 -0
  214. data/lib/rigor/environment/lockfile_resolver.rb +68 -4
  215. data/lib/rigor/environment/member_consistency/comparator.rb +708 -0
  216. data/lib/rigor/environment/member_consistency.rb +298 -0
  217. data/lib/rigor/environment/missing_gem_constant_index.rb +4 -4
  218. data/lib/rigor/environment/rbs_collection_discovery.rb +5 -5
  219. data/lib/rigor/environment/rbs_coverage_report.rb +5 -5
  220. data/lib/rigor/environment/rbs_hierarchy.rb +3 -1
  221. data/lib/rigor/environment/rbs_loader.rb +674 -79
  222. data/lib/rigor/environment.rb +174 -24
  223. data/lib/rigor/flow_contribution/merger.rb +0 -2
  224. data/lib/rigor/flow_contribution.rb +11 -13
  225. data/lib/rigor/hashing/xxh3.rb +264 -0
  226. data/lib/rigor/inference/acceptance.rb +292 -17
  227. data/lib/rigor/inference/block_auto_splat.rb +216 -0
  228. data/lib/rigor/inference/block_call_timing.rb +338 -0
  229. data/lib/rigor/inference/block_parameter_binder.rb +75 -30
  230. data/lib/rigor/inference/block_repetition.rb +71 -0
  231. data/lib/rigor/inference/body_fixpoint.rb +7 -6
  232. data/lib/rigor/inference/budget_trace.rb +3 -6
  233. data/lib/rigor/inference/builtins/method_catalog.rb +2 -1
  234. data/lib/rigor/inference/builtins/string_catalog.rb +1 -1
  235. data/lib/rigor/inference/captured_locals.rb +389 -18
  236. data/lib/rigor/inference/closure_escape_analyzer.rb +159 -17
  237. data/lib/rigor/inference/content_join.rb +200 -27
  238. data/lib/rigor/inference/coverage_scanner.rb +4 -6
  239. data/lib/rigor/inference/def_return_typer.rb +11 -7
  240. data/lib/rigor/inference/define_method_block_self.rb +64 -0
  241. data/lib/rigor/inference/dynamic_origin.rb +1 -1
  242. data/lib/rigor/inference/element_read_widening.rb +180 -0
  243. data/lib/rigor/inference/error_info.rb +196 -0
  244. data/lib/rigor/inference/expression_typer.rb +2124 -468
  245. data/lib/rigor/inference/external_ancestor_resolution.rb +267 -0
  246. data/lib/rigor/inference/fork_map.rb +5 -7
  247. data/lib/rigor/inference/fresh_frame_blocks.rb +127 -0
  248. data/lib/rigor/inference/global_write_census.rb +239 -0
  249. data/lib/rigor/inference/guard_rebinding.rb +447 -0
  250. data/lib/rigor/inference/hash_lookup_mutation.rb +88 -0
  251. data/lib/rigor/inference/hkt_reducer.rb +2 -3
  252. data/lib/rigor/inference/hkt_registry.rb +4 -7
  253. data/lib/rigor/inference/index_write_widening.rb +16 -7
  254. data/lib/rigor/inference/indexed_narrowing.rb +61 -9
  255. data/lib/rigor/inference/jump_targets.rb +82 -0
  256. data/lib/rigor/inference/last_line/implicit_self.rb +210 -0
  257. data/lib/rigor/inference/last_line/self_evidence.rb +329 -0
  258. data/lib/rigor/inference/last_line.rb +340 -0
  259. data/lib/rigor/inference/last_status.rb +144 -0
  260. data/lib/rigor/inference/macro_block_self_type.rb +168 -21
  261. data/lib/rigor/inference/match_rebinding/calls.rb +281 -0
  262. data/lib/rigor/inference/match_rebinding/frame.rb +148 -0
  263. data/lib/rigor/inference/match_rebinding/operands.rb +236 -0
  264. data/lib/rigor/inference/match_rebinding/self_calls.rb +83 -0
  265. data/lib/rigor/inference/match_rebinding.rb +392 -0
  266. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +36 -0
  267. data/lib/rigor/inference/method_dispatcher/block_folding.rb +87 -33
  268. data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +1 -1
  269. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +305 -19
  270. data/lib/rigor/inference/method_dispatcher/data_folding.rb +1 -1
  271. data/lib/rigor/inference/method_dispatcher/facet_distribution.rb +147 -0
  272. data/lib/rigor/inference/method_dispatcher/file_folding.rb +1 -1
  273. data/lib/rigor/inference/method_dispatcher/hash_transform_keys_folding.rb +191 -0
  274. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +6 -2
  275. data/lib/rigor/inference/method_dispatcher/json_folding.rb +1 -1
  276. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -1
  277. data/lib/rigor/inference/method_dispatcher/match_data_folding.rb +159 -0
  278. data/lib/rigor/inference/method_dispatcher/math_folding.rb +41 -3
  279. data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -5
  280. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +165 -111
  281. data/lib/rigor/inference/method_dispatcher/process_folding.rb +56 -0
  282. data/lib/rigor/inference/method_dispatcher/proven_overload.rb +72 -0
  283. data/lib/rigor/inference/method_dispatcher/random_folding.rb +82 -0
  284. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +1046 -69
  285. data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +25 -4
  286. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +2 -8
  287. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +1 -1
  288. data/lib/rigor/inference/method_dispatcher/self_substitute.rb +142 -0
  289. data/lib/rigor/inference/method_dispatcher/set_folding.rb +1 -1
  290. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +129 -54
  291. data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +1 -1
  292. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +1 -2
  293. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -6
  294. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +85 -4
  295. data/lib/rigor/inference/method_dispatcher/time_folding.rb +1 -1
  296. data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +1 -2
  297. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  298. data/lib/rigor/inference/method_dispatcher.rb +225 -25
  299. data/lib/rigor/inference/method_parameter_binder.rb +12 -9
  300. data/lib/rigor/inference/multi_target_binder.rb +341 -55
  301. data/lib/rigor/inference/mutation_rejoin.rb +171 -0
  302. data/lib/rigor/inference/mutation_widening.rb +156 -85
  303. data/lib/rigor/inference/narrowing.rb +835 -162
  304. data/lib/rigor/inference/operand_effects.rb +167 -0
  305. data/lib/rigor/inference/operand_walk.rb +88 -0
  306. data/lib/rigor/inference/optimistic_origin.rb +153 -16
  307. data/lib/rigor/inference/origin_lookup.rb +2 -3
  308. data/lib/rigor/inference/parameter_inference_collector.rb +7 -7
  309. data/lib/rigor/inference/pre_eval_constants.rb +6 -5
  310. data/lib/rigor/inference/precision_scanner.rb +3 -4
  311. data/lib/rigor/inference/project_method_ownership.rb +136 -0
  312. data/lib/rigor/inference/project_patched_methods.rb +9 -4
  313. data/lib/rigor/inference/project_patched_scanner.rb +10 -6
  314. data/lib/rigor/inference/protection_scanner.rb +1 -2
  315. data/lib/rigor/inference/range_constant.rb +57 -0
  316. data/lib/rigor/inference/rbs_type_translator.rb +49 -11
  317. data/lib/rigor/inference/receiver_alias.rb +93 -4
  318. data/lib/rigor/inference/receiver_blind_block.rb +219 -0
  319. data/lib/rigor/inference/refinement_mutation.rb +76 -0
  320. data/lib/rigor/inference/repeated_or_writes.rb +463 -0
  321. data/lib/rigor/inference/return_barrier.rb +54 -0
  322. data/lib/rigor/inference/rewrite_mutation.rb +120 -0
  323. data/lib/rigor/inference/scope_indexer.rb +5228 -627
  324. data/lib/rigor/inference/statement_evaluator.rb +3452 -595
  325. data/lib/rigor/inference/stored_block_call.rb +54 -0
  326. data/lib/rigor/inference/string_mutation.rb +97 -0
  327. data/lib/rigor/inference/struct_fold_safety.rb +6 -7
  328. data/lib/rigor/inference/synthetic_method_index.rb +1 -2
  329. data/lib/rigor/inference/synthetic_method_scanner.rb +4 -6
  330. data/lib/rigor/inference/unknown_store_widening.rb +200 -0
  331. data/lib/rigor/inference/unthreaded_rebinds.rb +282 -0
  332. data/lib/rigor/inference/version_guard.rb +28 -21
  333. data/lib/rigor/inference/void_origin.rb +3 -3
  334. data/lib/rigor/inference/void_tail_summary.rb +2 -4
  335. data/lib/rigor/language_server/buffer_table.rb +4 -4
  336. data/lib/rigor/language_server/completion_provider.rb +1 -1
  337. data/lib/rigor/language_server/debouncer.rb +0 -1
  338. data/lib/rigor/language_server/diagnostic_publisher.rb +7 -6
  339. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  340. data/lib/rigor/language_server/folding_range_provider.rb +1 -1
  341. data/lib/rigor/language_server/hover_provider.rb +1 -1
  342. data/lib/rigor/language_server/hover_renderer.rb +4 -4
  343. data/lib/rigor/language_server/incremental_sync.rb +6 -6
  344. data/lib/rigor/language_server/project_context.rb +7 -8
  345. data/lib/rigor/language_server/publish_batcher.rb +2 -2
  346. data/lib/rigor/language_server/selection_range_provider.rb +2 -2
  347. data/lib/rigor/language_server/server.rb +9 -9
  348. data/lib/rigor/language_server/signature_help_provider.rb +1 -1
  349. data/lib/rigor/language_server/uri.rb +1 -1
  350. data/lib/rigor/mcp/server.rb +2 -1
  351. data/lib/rigor/plugin/base.rb +258 -26
  352. data/lib/rigor/plugin/box_probe.rb +91 -0
  353. data/lib/rigor/plugin/bundled_catalog.rb +167 -0
  354. data/lib/rigor/plugin/effect_attribution.rb +65 -11
  355. data/lib/rigor/plugin/effect_entry_points.rb +3 -3
  356. data/lib/rigor/plugin/fact_store.rb +6 -6
  357. data/lib/rigor/plugin/io_boundary.rb +93 -8
  358. data/lib/rigor/plugin/isolation.rb +29 -6
  359. data/lib/rigor/plugin/loader.rb +33 -13
  360. data/lib/rigor/plugin/macro/block_as_method.rb +45 -6
  361. data/lib/rigor/plugin/macro/heredoc_template.rb +1 -1
  362. data/lib/rigor/plugin/macro/trait_registry.rb +1 -1
  363. data/lib/rigor/plugin/manifest.rb +94 -11
  364. data/lib/rigor/plugin/registry.rb +76 -12
  365. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +21 -4
  366. data/lib/rigor/plugin/template_unit.rb +196 -0
  367. data/lib/rigor/plugin/trust_policy.rb +2 -4
  368. data/lib/rigor/plugin/type_node_resolver.rb +3 -3
  369. data/lib/rigor/plugin.rb +1 -0
  370. data/lib/rigor/plugin_gap_advisory.rb +96 -0
  371. data/lib/rigor/project_environment.rb +138 -0
  372. data/lib/rigor/protection/analysis_guard.rb +93 -14
  373. data/lib/rigor/protection/closure_kill_oracle.rb +67 -23
  374. data/lib/rigor/protection/dependency_closure.rb +5 -8
  375. data/lib/rigor/protection/diagnostic_oracle.rb +2 -2
  376. data/lib/rigor/protection/discovery_seed.rb +14 -12
  377. data/lib/rigor/protection/kill_signature.rb +1 -4
  378. data/lib/rigor/protection/measurement_integrity.rb +1 -3
  379. data/lib/rigor/protection/mutation_cache.rb +7 -11
  380. data/lib/rigor/protection/mutation_scanner.rb +12 -15
  381. data/lib/rigor/protection/mutator.rb +2 -1
  382. data/lib/rigor/protection/test_suite_oracle.rb +5 -5
  383. data/lib/rigor/rbs_extended/conformance_checker.rb +4 -3
  384. data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
  385. data/lib/rigor/rbs_extended/reporter.rb +36 -10
  386. data/lib/rigor/rbs_extended.rb +88 -21
  387. data/lib/rigor/reflection/constant_ancestors.rb +97 -0
  388. data/lib/rigor/reflection/constant_path.rb +139 -0
  389. data/lib/rigor/reflection.rb +93 -110
  390. data/lib/rigor/runtime/jit.rb +6 -8
  391. data/lib/rigor/scope/discovery_index.rb +165 -2
  392. data/lib/rigor/scope.rb +1041 -32
  393. data/lib/rigor/sig_gen/alias_index.rb +289 -0
  394. data/lib/rigor/sig_gen/classification.rb +22 -5
  395. data/lib/rigor/sig_gen/declaration_equivalence.rb +127 -0
  396. data/lib/rigor/sig_gen/effect_annotation.rb +202 -0
  397. data/lib/rigor/sig_gen/generator.rb +673 -53
  398. data/lib/rigor/sig_gen/inline_declarations.rb +184 -0
  399. data/lib/rigor/sig_gen/layout_index.rb +3 -4
  400. data/lib/rigor/sig_gen/meta_class_shape.rb +3 -5
  401. data/lib/rigor/sig_gen/method_candidate.rb +47 -3
  402. data/lib/rigor/sig_gen/observation_collector.rb +18 -16
  403. data/lib/rigor/sig_gen/path_mapper.rb +4 -6
  404. data/lib/rigor/sig_gen/rbs_validity.rb +4 -4
  405. data/lib/rigor/sig_gen/renderer.rb +128 -14
  406. data/lib/rigor/sig_gen/skip_reason_catalog.rb +154 -0
  407. data/lib/rigor/sig_gen/superclass_spelling.rb +27 -0
  408. data/lib/rigor/sig_gen/type_elaborator.rb +1 -3
  409. data/lib/rigor/sig_gen/write_result.rb +19 -3
  410. data/lib/rigor/sig_gen/writer.rb +189 -41
  411. data/lib/rigor/sig_gen.rb +3 -0
  412. data/lib/rigor/signature_path_audit.rb +153 -6
  413. data/lib/rigor/source/literals.rb +0 -23
  414. data/lib/rigor/source/node_children.rb +0 -2
  415. data/lib/rigor/source/node_locator.rb +5 -11
  416. data/lib/rigor/source/node_walker.rb +0 -6
  417. data/lib/rigor/source/parameter_envelope.rb +72 -0
  418. data/lib/rigor/source.rb +1 -0
  419. data/lib/rigor/triage/catalogue.rb +1 -3
  420. data/lib/rigor/triage.rb +3 -5
  421. data/lib/rigor/type/accepts_result.rb +24 -6
  422. data/lib/rigor/type/combinator.rb +144 -5
  423. data/lib/rigor/type/data_class.rb +2 -2
  424. data/lib/rigor/type/data_instance.rb +4 -4
  425. data/lib/rigor/type/difference.rb +1 -0
  426. data/lib/rigor/type/float_range.rb +128 -0
  427. data/lib/rigor/type/hash_shape.rb +6 -6
  428. data/lib/rigor/type/integer_range.rb +13 -8
  429. data/lib/rigor/type/nominal.rb +8 -2
  430. data/lib/rigor/type/refined.rb +4 -3
  431. data/lib/rigor/type/struct_class.rb +3 -3
  432. data/lib/rigor/type/struct_instance.rb +4 -4
  433. data/lib/rigor/type.rb +1 -0
  434. data/lib/rigor/type_node/generic.rb +1 -1
  435. data/lib/rigor/type_node/range_literal.rb +25 -0
  436. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  437. data/lib/rigor/type_node.rb +1 -0
  438. data/lib/rigor/version.rb +1 -1
  439. data/lib/rigor.rb +1 -0
  440. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +0 -4
  441. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +0 -1
  442. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +0 -2
  443. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/effects.rb +1 -0
  444. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +10 -5
  445. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +0 -4
  446. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +3 -5
  447. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +1 -4
  448. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +14 -4
  449. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +32 -30
  450. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +0 -1
  451. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -3
  452. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +96 -0
  453. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/effects.rb +65 -8
  454. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +270 -0
  455. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/render_locals.rb +402 -0
  456. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +370 -0
  457. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_units.rb +132 -0
  458. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +264 -3
  459. data/plugins/rigor-actionpack/sig/action_controller.rbs +71 -0
  460. data/plugins/rigor-actionpack/sig/action_view.rbs +43 -0
  461. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_discoverer.rb +220 -0
  462. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers/serializer_index.rb +56 -0
  463. data/plugins/rigor-active-model-serializers/lib/rigor/plugin/active_model_serializers.rb +263 -0
  464. data/plugins/rigor-active-model-serializers/lib/rigor-active-model-serializers.rb +7 -0
  465. data/plugins/rigor-active-model-serializers/sig/active_model_serializers.rbs +45 -0
  466. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +0 -4
  467. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +2 -1
  468. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +0 -1
  469. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +0 -2
  470. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +1 -1
  471. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +10 -5
  472. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +35 -3
  473. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +72 -12
  474. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +383 -7
  475. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +14 -6
  476. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +1 -2
  477. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +1 -2
  478. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +322 -41
  479. data/plugins/rigor-activerecord/sig/active_record/framework.rbs +123 -0
  480. data/plugins/rigor-activerecord/sig/active_record/relation.rbs +206 -31
  481. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +26 -14
  482. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +9 -2
  483. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +9 -2
  484. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +290 -19
  485. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +1 -0
  486. data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +3 -0
  487. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +9 -7
  488. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +7 -3
  489. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +1 -0
  490. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +8 -6
  491. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +11 -7
  492. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -11
  493. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +9 -4
  494. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +2 -0
  495. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +0 -5
  496. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +0 -1
  497. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +0 -1
  498. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +8 -1
  499. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/types.rb +1 -1
  500. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +36 -4
  501. data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +1 -0
  502. data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +141 -0
  503. data/plugins/rigor-grape/lib/rigor-grape.rb +6 -0
  504. data/plugins/rigor-grape/sig/grape.rbs +263 -0
  505. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +8 -6
  506. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +78 -5
  507. data/plugins/rigor-graphql/sig/graphql.rbs +485 -0
  508. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +1 -0
  509. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -1
  510. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +2 -3
  511. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +1 -0
  512. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +0 -5
  513. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -2
  514. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +0 -1
  515. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +0 -1
  516. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +12 -7
  517. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +0 -12
  518. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +1 -3
  519. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +0 -1
  520. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +36 -37
  521. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/acronyms.rb +5 -5
  522. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +0 -10
  523. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +5 -3
  524. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +3 -2
  525. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +2 -2
  526. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +2 -2
  527. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +9 -10
  528. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +2 -3
  529. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +10 -17
  530. data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +1 -0
  531. data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +13 -1
  532. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline/same_line_annotations.rb +149 -0
  533. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +324 -18
  534. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +0 -3
  535. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +0 -3
  536. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -5
  537. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +2 -3
  538. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +1 -0
  539. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +0 -3
  540. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +1 -0
  541. data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +1 -0
  542. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +1 -9
  543. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +1 -0
  544. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +0 -4
  545. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/effects.rb +1 -1
  546. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +2 -1
  547. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +0 -1
  548. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +0 -2
  549. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +10 -5
  550. data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -0
  551. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +2 -5
  552. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +1 -4
  553. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +3 -9
  554. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +4 -4
  555. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +1 -2
  556. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +3 -5
  557. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +7 -5
  558. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +51 -6
  559. data/plugins/rigor-sorbet/sig/sorbet.rbs +277 -0
  560. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +1 -0
  561. data/sig/rigor/analysis/baseline.rbs +69 -7
  562. data/sig/rigor/analysis/fact_store.rbs +1 -1
  563. data/sig/rigor/analysis/project_scan.rbs +74 -0
  564. data/sig/rigor/analysis/reachability/scan_cache.rbs +12 -0
  565. data/sig/rigor/cache.rbs +2 -3
  566. data/sig/rigor/effects/config_envelopes.rbs +100 -0
  567. data/sig/rigor/effects/effect_table.rbs +60 -0
  568. data/sig/rigor/effects/envelope.rbs +97 -0
  569. data/sig/rigor/effects/envelope_index.rbs +33 -0
  570. data/sig/rigor/effects/file_collection.rbs +81 -0
  571. data/sig/rigor/effects/label.rbs +26 -0
  572. data/sig/rigor/effects/label_set.rbs +46 -0
  573. data/sig/rigor/effects/method_key.rbs +24 -0
  574. data/sig/rigor/effects/origin.rbs +50 -0
  575. data/sig/rigor/effects/plugin_facts.rbs +141 -0
  576. data/sig/rigor/effects/registry.rbs +68 -0
  577. data/sig/rigor/effects/summary.rbs +50 -0
  578. data/sig/rigor/effects/taint_cause.rbs +12 -0
  579. data/sig/rigor/environment.rbs +17 -10
  580. data/sig/rigor/inference/optimistic_origin.rbs +10 -0
  581. data/sig/rigor/inference.rbs +9 -14
  582. data/sig/rigor/plugin/additional_initializer.rbs +36 -0
  583. data/sig/rigor/plugin/base.rbs +30 -7
  584. data/sig/rigor/plugin/effect_ancestry.rbs +27 -0
  585. data/sig/rigor/plugin/effect_attribution.rbs +66 -0
  586. data/sig/rigor/plugin/effect_edge.rbs +33 -0
  587. data/sig/rigor/plugin/effect_entry_points.rbs +25 -0
  588. data/sig/rigor/plugin/io_boundary.rbs +4 -0
  589. data/sig/rigor/plugin/loader.rbs +3 -3
  590. data/sig/rigor/plugin/manifest.rbs +51 -11
  591. data/sig/rigor/plugin/protocol_contract.rbs +68 -0
  592. data/sig/rigor/plugin/registry.rbs +62 -1
  593. data/sig/rigor/plugin.rbs +1 -1
  594. data/sig/rigor/rbs_extended.rbs +1 -1
  595. data/sig/rigor/reflection.rbs +9 -6
  596. data/sig/rigor/scope.rbs +148 -12
  597. data/sig/rigor/sig_gen/skip_reason_catalog.rbs +21 -0
  598. data/sig/rigor/source.rbs +4 -4
  599. data/sig/rigor/testing.rbs +10 -4
  600. data/sig/rigor/type.rbs +33 -1
  601. data/sig/rigor.rbs +39 -20
  602. data/skills/rigor-ask/SKILL.md +16 -11
  603. data/skills/rigor-baseline-reduce/SKILL.md +4 -6
  604. data/skills/rigor-ci-setup/SKILL.md +17 -21
  605. data/skills/rigor-doctor/SKILL.md +24 -22
  606. data/skills/rigor-doctor/references/01-checks.md +97 -33
  607. data/skills/rigor-editor-setup/SKILL.md +6 -4
  608. data/skills/rigor-mcp-setup/SKILL.md +5 -4
  609. data/skills/rigor-monkeypatch-resolve/SKILL.md +5 -3
  610. data/skills/rigor-next-steps/SKILL.md +4 -2
  611. data/skills/rigor-plugin-author/SKILL.md +19 -23
  612. data/skills/rigor-plugin-author/references/01-plan-and-scaffold.md +9 -8
  613. data/skills/rigor-plugin-author/references/02-walker-and-types.md +25 -27
  614. data/skills/rigor-plugin-author/references/03-test-and-ship.md +6 -8
  615. data/skills/rigor-plugin-review/SKILL.md +6 -4
  616. data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +2 -1
  617. data/skills/rigor-plugin-tune/SKILL.md +4 -2
  618. data/skills/rigor-project-init/SKILL.md +15 -10
  619. data/skills/rigor-project-init/references/01-detect.md +13 -9
  620. data/skills/rigor-project-init/references/02-configure.md +43 -14
  621. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +14 -14
  622. data/skills/rigor-project-init/references/04-sig-uplift.md +27 -14
  623. data/skills/rigor-project-init/references/06-agent-contract.md +67 -0
  624. data/skills/rigor-protection-uplift/SKILL.md +4 -6
  625. data/skills/rigor-rbs-setup/SKILL.md +4 -2
  626. data/skills/rigor-type-oracle/SKILL.md +226 -0
  627. data/skills/rigor-type-oracle/references/01-oracle-commands.md +265 -0
  628. data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +52 -0
  629. data/skills/rigor-type-oracle/references/03-gap-protocol.md +125 -0
  630. data/skills/rigor-unused-adjudicate/SKILL.md +12 -7
  631. data/skills/rigor-upgrade/SKILL.md +13 -8
  632. metadata +133 -3
  633. data/lib/rigor/cli/probe_environment.rb +0 -85
  634. data/sig/rigor/inference/builtins/numeric_catalog.rbs +0 -3
@@ -0,0 +1,196 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+
5
+ module Rigor
6
+ module Plugin
7
+ # One **template unit** — a non-Ruby source file a plugin compiled into Ruby, handed to the engine to
8
+ # be parsed and typed like a file (ADR-16 Tier D, revived by #392 as this seam; design note
9
+ # `docs/design/20260816-effect-labels.md` § 11.3).
10
+ #
11
+ # ADR-16's Tier D declared `external_files:` — "files evaluated as if their body were pasted at a
12
+ # declared call site, with `self` typed as a declared class" — and ADR-60 WD1 removed it for want of a
13
+ # consumer. What Tier D lacked, and what this carries, is a **source transform with a line map** ahead
14
+ # of parsing: the file on disk is not Ruby, so the engine analyses `ruby_source` and reports at the
15
+ # template's own lines.
16
+ #
17
+ # A unit is pure data. The plugin's transform runs ONCE, on the parent, before any analysis; nothing
18
+ # here is a callable, so the unit survives `Marshal` into a fork-pool worker exactly as the macro
19
+ # substrate's value objects do.
20
+ #
21
+ # - `logical_name` — the name the unit is keyed by, independent of the handler: `users/show.html`, not
22
+ # `users/show.html.erb`. Becomes the `view:<logical_name>` effect-unit key, so an ERB → Haml rewrite
23
+ # is not a rename.
24
+ # - `path` — the template file as the user wrote it. Every diagnostic and every source trace names it.
25
+ # - `ruby_source` — the compiled Ruby the engine parses.
26
+ # - `line_map` — `{ ruby_source line => template line }`, 1-based. A line the map does not mention
27
+ # anchors at the nearest mapped line before it, and at line 1 when there is none. **An EMPTY map is
28
+ # the identity, not "no positions"**: every compiled line reports at its own number and the columns
29
+ # pass through untouched. That is correct only for a transform that emits nothing the template did
30
+ # not contain — a transform that prepends so much as one preamble line and omits the map reports
31
+ # every finding off by that many lines. Omit it only for a byte-preserving transform.
32
+ # - `self_type` — the fully-qualified class the body's `self` is typed as, or nil for a bare body.
33
+ # - `locals` — `{ name => type name }`, the parameters the render site passes.
34
+ # - `ivar_seeds` — `{ "@name" => type name }`, the assigns the rendering action set.
35
+ # - `transform_id` — the compiler's identity (`"erubi-1.13"`). Joins the unit digest, so re-compiling
36
+ # with a different compiler re-analyses. Defaults to the declaring plugin's `id@version`.
37
+ #
38
+ # - `suppressed_rules` — rule-id PREFIXES the engine drops for this unit's diagnostics (#393). The
39
+ # per-unit rule posture: a compiled template is not code the user wrote, and a plugin that knows its
40
+ # compiler's idioms is the only party that can say which families of finding are meaningful there.
41
+ # `["call."]` is what an ERB plugin declares while the synthesised receivers are still coarse — the
42
+ # unit still contributes its EFFECTS, which is what the view layer is analysed for first, without
43
+ # the typing half costing the project a diagnostic per line. Empty means "report everything", which
44
+ # is what a plugin whose synthesis it trusts should declare.
45
+ #
46
+ # A type name that resolves to nothing is not an error here: the engine falls back to `Dynamic`, which
47
+ # taints honestly rather than fabricating a type the plugin could not justify (ADR-5).
48
+ class TemplateUnit
49
+ # Bumped whenever the engine changes what it synthesises from a unit — the seeding rules, the
50
+ # scope binding, the effect-unit key spelling. It rides the unit digest, so a change here
51
+ # invalidates every cached run that analysed a unit, exactly as a changed transform does.
52
+ SYNTHESIS_VERSION = 2
53
+
54
+ # The prefix an effect-unit key carries for a template unit. Deliberately not a {Effects::MethodKey}
55
+ # shape: a view has no owner class and no selector, and spelling one would put a method in the
56
+ # snapshot that no call site can name.
57
+ KEY_PREFIX = "view:"
58
+
59
+ IVAR_NAME = /\A@[A-Za-z_][A-Za-z0-9_]*\z/
60
+ private_constant :IVAR_NAME
61
+
62
+ LOCAL_NAME = /\A[a-z_][A-Za-z0-9_]*\z/
63
+ private_constant :LOCAL_NAME
64
+
65
+ # A rule prefix is a dotted family (`call.`) or a whole rule id (`call.undefined-method`); the match
66
+ # is `String#start_with?`, so the former covers the latter. A bare `""` would suppress the unit
67
+ # entirely and is refused.
68
+ RULE_PREFIX = /\A[a-z][a-z0-9]*(?:[.-][a-z0-9]+)*\.?\z/
69
+ private_constant :RULE_PREFIX
70
+
71
+ attr_reader :logical_name, :path, :ruby_source, :line_map, :self_type, :locals, :ivar_seeds,
72
+ :transform_id, :suppressed_rules
73
+
74
+ # Every field is one declared property of the unit; grouping them behind a context object would only
75
+ # move the same list into a second value class.
76
+ def initialize(logical_name:, path:, ruby_source:, line_map: {}, self_type: nil, locals: {}, # rubocop:disable Metrics/ParameterLists
77
+ ivar_seeds: {}, transform_id: nil, suppressed_rules: [])
78
+ @logical_name = validate_string!("logical_name", logical_name)
79
+ @path = validate_string!("path", path)
80
+ @ruby_source = validate_source!(ruby_source)
81
+ @line_map = validate_line_map!(line_map)
82
+ @self_type = self_type.nil? ? nil : validate_string!("self_type", self_type)
83
+ @locals = validate_bindings!("locals", locals, LOCAL_NAME)
84
+ @ivar_seeds = validate_bindings!("ivar_seeds", ivar_seeds, IVAR_NAME)
85
+ @transform_id = transform_id.nil? ? nil : validate_string!("transform_id", transform_id)
86
+ @suppressed_rules = validate_rule_prefixes!(suppressed_rules)
87
+ freeze
88
+ end
89
+
90
+ # The effect-unit key and the snapshot key: `view:users/show.html`.
91
+ def unit_key
92
+ "#{KEY_PREFIX}#{@logical_name}"
93
+ end
94
+
95
+ # The unit's cache identity — **source bytes + transform id + synthesis version**, the triple
96
+ # `docs/internal-spec/cache.md` § computed-value keys asks a synthesised input to carry. The
97
+ # template's own bytes are deliberately NOT in it: the engine analyses `ruby_source`, and two
98
+ # templates that compile to the same Ruby have the same answer.
99
+ #
100
+ # @param fallback_transform_id — the declaring plugin's `id@version`, used when the unit
101
+ # named no transform of its own.
102
+ def digest(fallback_transform_id = nil)
103
+ Digest::SHA256.hexdigest(
104
+ [
105
+ "source:#{@ruby_source}",
106
+ "transform:#{@transform_id || fallback_transform_id || 'unknown'}",
107
+ "suppressed:#{@suppressed_rules.join(',')}",
108
+ "synthesis:#{SYNTHESIS_VERSION}"
109
+ ].join("\x00")
110
+ )
111
+ end
112
+
113
+ # The template line a `ruby_source` line reports at. Falls back to the nearest mapped line before it
114
+ # — a synthesised prologue or a buffer-append the compiler emitted between two template lines still
115
+ # points into the template rather than past its end — and to line 1 when the map says nothing at all.
116
+ def template_line(ruby_line)
117
+ return ruby_line if @line_map.empty?
118
+ return @line_map[ruby_line] if @line_map.key?(ruby_line)
119
+
120
+ before = @line_map.keys.select { |line| line < ruby_line }
121
+ before.empty? ? 1 : @line_map[before.max]
122
+ end
123
+
124
+ def to_h
125
+ {
126
+ "logical_name" => @logical_name, "path" => @path, "ruby_source" => @ruby_source,
127
+ "line_map" => @line_map, "self_type" => @self_type, "locals" => @locals,
128
+ "ivar_seeds" => @ivar_seeds, "transform_id" => @transform_id,
129
+ "suppressed_rules" => @suppressed_rules
130
+ }
131
+ end
132
+
133
+ def ==(other)
134
+ other.is_a?(TemplateUnit) && other.to_h == to_h
135
+ end
136
+ alias eql? ==
137
+
138
+ def hash
139
+ [self.class, to_h].hash
140
+ end
141
+
142
+ private
143
+
144
+ def validate_string!(field, value)
145
+ text = value.to_s
146
+ raise ArgumentError, "TemplateUnit #{field} must be a non-empty String" if text.empty?
147
+
148
+ text.dup.freeze
149
+ end
150
+
151
+ def validate_source!(value)
152
+ raise ArgumentError, "TemplateUnit ruby_source must be a String" unless value.is_a?(String)
153
+
154
+ value.dup.freeze
155
+ end
156
+
157
+ # 1-based on both sides. A zero or negative line is a compiler bug the engine would turn into a
158
+ # diagnostic positioned off the top of the file, so it is refused at construction.
159
+ def validate_line_map!(value)
160
+ raise ArgumentError, "TemplateUnit line_map must be a Hash" unless value.is_a?(Hash)
161
+
162
+ value.to_h do |from, to|
163
+ unless from.is_a?(Integer) && to.is_a?(Integer) && from.positive? && to.positive?
164
+ raise ArgumentError, "TemplateUnit line_map must map positive Integer to positive Integer"
165
+ end
166
+
167
+ [from, to]
168
+ end.freeze
169
+ end
170
+
171
+ def validate_rule_prefixes!(value)
172
+ raise ArgumentError, "TemplateUnit suppressed_rules must be an Array" unless value.is_a?(Array)
173
+
174
+ value.map do |prefix|
175
+ text = prefix.to_s
176
+ unless text.match?(RULE_PREFIX)
177
+ raise ArgumentError, "TemplateUnit suppressed_rules entry #{text.inspect} is malformed"
178
+ end
179
+
180
+ text.dup.freeze
181
+ end.uniq.freeze
182
+ end
183
+
184
+ def validate_bindings!(field, value, pattern)
185
+ raise ArgumentError, "TemplateUnit #{field} must be a Hash" unless value.is_a?(Hash)
186
+
187
+ value.to_h do |name, type_name|
188
+ key = name.to_s
189
+ raise ArgumentError, "TemplateUnit #{field} key #{key.inspect} is malformed" unless key.match?(pattern)
190
+
191
+ [key.dup.freeze, validate_string!("#{field} value", type_name)]
192
+ end.freeze
193
+ end
194
+ end
195
+ end
196
+ end
@@ -45,8 +45,7 @@ module Rigor
45
45
  freeze
46
46
  end
47
47
 
48
- # @param path [String]
49
- # @return [Boolean] true when the absolute path falls inside any allowed read root. Symlinks are
48
+ # @return true when the absolute path falls inside any allowed read root. Symlinks are
50
49
  # resolved through `File.expand_path` only (no `realpath`); plugins with adversarial intent are out of
51
50
  # scope per ADR-2.
52
51
  def allow_read?(path)
@@ -58,8 +57,7 @@ module Rigor
58
57
  @network_policy != :disabled
59
58
  end
60
59
 
61
- # @param url [String, URI]
62
- # @return [Boolean] true when the URL scheme is `https` and the parsed hostname is in
60
+ # @return true when the URL scheme is `https` and the parsed hostname is in
63
61
  # `allowed_url_hosts`. Always `false` while `network_policy` is `:disabled`.
64
62
  def allow_url?(url)
65
63
  return false if @network_policy == :disabled
@@ -28,11 +28,11 @@ module Rigor
28
28
  # Resolvers SHOULD be stateless and re-entrant; the registry builds the chain once per
29
29
  # `Analysis::Runner.run` and may consult any resolver multiple times for the same node.
30
30
  class TypeNodeResolver
31
- # @param node [Rigor::TypeNode::Identifier, Rigor::TypeNode::Generic] the parser-emitted node the chain is
31
+ # @param node — the parser-emitted node the chain is
32
32
  # asking about.
33
- # @param scope [Rigor::TypeNode::NameScope] companion value object (slice 3); slice 2 invocations MAY pass
33
+ # @param scope — companion value object (slice 3); slice 2 invocations MAY pass
34
34
  # `nil` because the chain doesn't exist yet.
35
- # @return [Rigor::Type::Base, nil] resolved type, or `nil` to fall through.
35
+ # @return resolved type, or `nil` to fall through.
36
36
  def resolve(node, scope) # rubocop:disable Lint/UnusedMethodArgument
37
37
  nil
38
38
  end
data/lib/rigor/plugin.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative "plugin/type_node_resolver"
4
4
  require_relative "plugin/macro"
5
+ require_relative "plugin/template_unit"
5
6
  require_relative "plugin/manifest"
6
7
  require_relative "plugin/access_denied_error"
7
8
  require_relative "plugin/trust_policy"
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "plugin/bundled_catalog"
4
+ require_relative "environment/lockfile_resolver"
5
+
6
+ module Rigor
7
+ # ADR-96 WD2 — the plugin-gap advisory: a gem the project itself depends on that a bundled plugin declares
8
+ # in `target_gems:`, with that plugin absent from `plugins:`.
9
+ #
10
+ # One home for a fact that ADR-96 found in four hand-maintained copies, none of which was the plugin.
11
+ # `rigor doctor` and `rigor skill describe`'s project probe both route through here, so the two can no
12
+ # longer disagree — they were byte-identical Rails tables by luck, not by construction.
13
+ #
14
+ # The advisory is deliberately advice, not activation: naming a plugin never loads it (ADR-96 Criterion 2),
15
+ # and declining a plugin is a legitimate choice, which is why a gap is a warning and never a failure.
16
+ module PluginGapAdvisory
17
+ # One unenabled plugin and the depended-on gems that make it relevant.
18
+ Gap = Data.define(:plugin_gem, :plugin_id, :locked_gems)
19
+
20
+ # A gem whose whole point is to pull its constituents in. A Rails application's `Gemfile` says `rails`
21
+ # and never `activerecord`, so a direct-dependency match alone would silence the entire Rails family for
22
+ # the projects the advisory exists for. The table is explicit rather than derived: expanding whatever a
23
+ # gem happens to resolve to is the transitive read this advisory is here to avoid, and the set of gems
24
+ # that are genuinely umbrellas is small and known.
25
+ UMBRELLA_GEMS = {
26
+ "rails" => %w[
27
+ actioncable actionmailer actionpack actiontext actionview activejob activemodel
28
+ activerecord activestorage activesupport railties
29
+ ].freeze
30
+ }.freeze
31
+
32
+ class << self
33
+ # @param project_root — the directory whose `Gemfile.lock` and `plugins:` list are read.
34
+ # @param plugins — the configuration's raw `plugins:` entries (String or Hash form).
35
+ # @return the gaps, sorted by plugin gem name; empty when the project declares no dependency.
36
+ def gaps(project_root:, plugins:)
37
+ depended = depended_gems(project_root)
38
+ return [] if depended.empty?
39
+
40
+ listed = listed_plugin_names(plugins)
41
+ Plugin::BundledCatalog.entries.filter_map do |entry|
42
+ next if listed?(entry, listed)
43
+
44
+ matched = entry.target_gems.select { |gem| depended.include?(gem) }
45
+ next if matched.empty?
46
+
47
+ Gap.new(plugin_gem: entry.gem_name, plugin_id: entry.plugin_id, locked_gems: matched.freeze)
48
+ end.sort_by(&:plugin_gem)
49
+ end
50
+
51
+ # Whether the project enables no bundled plugin at all for the gems it depends on — ADR-96's preserved
52
+ # `:fail` case, generalised off the Rails-only table it replaces. A project that enables *some* of the
53
+ # matching plugins has made choices; one that enables none of them is unconfigured for its own stack.
54
+ def unconfigured?(project_root:, plugins:)
55
+ found = gaps(project_root: project_root, plugins: plugins)
56
+ return false if found.empty?
57
+
58
+ listed = listed_plugin_names(plugins)
59
+ Plugin::BundledCatalog.entries.none? { |entry| listed?(entry, listed) }
60
+ end
61
+
62
+ private
63
+
64
+ # The gems the project chose, which is the `DEPENDENCIES` section and never the resolved graph. A
65
+ # transitive `minitest` / `i18n` / `ffi` is somebody else's dependency, and advising on it fires on
66
+ # correct configuration — including, through {unconfigured?}, as a hard failure.
67
+ def depended_gems(project_root)
68
+ direct = Environment::LockfileResolver.direct_dependency_names(
69
+ lockfile_path: nil, project_root: project_root
70
+ )
71
+ return direct if direct.empty?
72
+
73
+ direct + direct.flat_map { |gem| UMBRELLA_GEMS.fetch(gem, []) }
74
+ end
75
+
76
+ # Both spellings a `plugins:` entry may use for the same plugin — the gem name and the manifest id.
77
+ # An `enabled: false` entry still counts as naming it: the user has demonstrably found the plugin, so
78
+ # advising them to add what is already there would be worse than staying quiet.
79
+ def listed_plugin_names(plugins)
80
+ Array(plugins).flat_map do |raw|
81
+ case raw
82
+ when String then [raw]
83
+ when Hash
84
+ keyed = raw.to_h { |key, value| [key.to_s, value] }
85
+ [keyed["gem"], keyed["id"]].compact.map(&:to_s)
86
+ else []
87
+ end
88
+ end.to_set
89
+ end
90
+
91
+ def listed?(entry, listed)
92
+ listed.include?(entry.gem_name) || listed.include?(entry.plugin_id)
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "environment"
4
+ require_relative "plugin"
5
+ require_relative "plugin/loader"
6
+ require_relative "plugin/services"
7
+ require_relative "reflection"
8
+ require_relative "type/combinator"
9
+
10
+ module Rigor
11
+ # Shared construction of the project environment for every command that types against the project WITHOUT
12
+ # driving a full {Analysis::Runner}: the single-shot probes (`type-of`, `type-scan`, `trace`, `annotate`) and
13
+ # the `sig-gen` generator / observation collector. Each of these answers "what does the engine see here?" for
14
+ # a hand-picked file, position or class, so the type universe they read MUST match the one `rigor check`
15
+ # analyses against — otherwise the command reports, or declines to emit, against a universe the real run never
16
+ # had.
17
+ #
18
+ # Two gaps this closes, both the same shape:
19
+ #
20
+ # - The probes historically built their environment with `Environment.for_project(libraries:,
21
+ # signature_paths:)` only — no plugin registry, no `source_files:`. The `source_rbs_synthesizer` plugin hook
22
+ # (ADR-32 / ADR-93's auto-wired `rigor-rbs-inline`) therefore never ran there, so a class whose only
23
+ # signature comes from an inline `#: () -> void` annotation typed as `Dynamic[top]` under a probe while
24
+ # `check` resolved it through the synthesized RBS. That divergence misattributed a dispatch tier during the
25
+ # #162 transitive-void design pass (see the 2026-07-19 addendum in
26
+ # docs/adr/100-static-diagnostic-family-and-void-origins.md).
27
+ # - Neither the probes nor `sig-gen` passed the dependency-discovery axes — the `rbs collection` lockfile, the
28
+ # bundle's per-gem `sig/`, the Gemfile.lock gating them — that `check` reads straight off the same
29
+ # configuration. On a Rails project with `rbs collection install` run, `sig-gen` therefore could not see
30
+ # `ActiveRecord::Base` at all, and its superclass-without-RBS skip guard declined to emit every model
31
+ # (issue #821). The advice the guard prints — install the collection — had no effect, because the command
32
+ # never looked at it.
33
+ #
34
+ # {.dependency_discovery_options} is the single spelling of those axes; `Runner`'s build path
35
+ # (`Analysis::Runner::PoolCoordinator#build_runner_environment`), the worker session and the LSP project
36
+ # context read it too, so a new axis cannot be added to `check` and silently missed here.
37
+ #
38
+ # A non-`check` command legitimately simplifies in two places: no synthesis-failure reporter (there is no
39
+ # diagnostic pipeline) and no synthesis cache store (a single-position probe recomputes cheaply). Plugin
40
+ # loading itself matches check — same `.rigor.yml` `plugins:` config, same ADR-93 auto-wire (already applied
41
+ # by `Configuration.load`), same `enabled:` / `require_magic_comment` semantics via `Plugin::Loader.load`.
42
+ #
43
+ # Fail-soft is the invariant: a project with no plugins, an unresolvable plugin gem, or any error during the
44
+ # plugin-aware build degrades — never crashes. `Plugin::Loader` already isolates per-entry load failures onto
45
+ # the registry; the `rescue` here is the belt-and-suspenders around anything the loader or the plugin-aware
46
+ # env build itself might raise.
47
+ module ProjectEnvironment
48
+ module_function
49
+
50
+ # Builds the plugin-aware, dependency-aware {Rigor::Environment} a non-`check` command works against.
51
+ #
52
+ # @param configuration — the loaded project configuration (already carries the
53
+ # ADR-93 auto-wired `rigor-rbs-inline` entry when the library is resolvable).
54
+ # @param source_files — the file(s) the command inspects. Threaded so each loaded plugin's
55
+ # `source_rbs_synthesizer` runs over them at env-build time; an empty list contributes no synthesized RBS.
56
+ def build(configuration:, source_files:)
57
+ Environment.for_project(
58
+ libraries: configuration.libraries,
59
+ signature_paths: configuration.signature_paths,
60
+ plugin_registry: load_plugin_registry(configuration),
61
+ source_files: source_files,
62
+ **dependency_discovery_options(configuration)
63
+ )
64
+ rescue StandardError
65
+ bare(configuration)
66
+ end
67
+
68
+ # The dependency-discovery axes `check` reads off the configuration, as a keyword hash. Extracted so the
69
+ # list lives in one place: every command that types against the project's real dependencies passes exactly
70
+ # these, and adding an axis to `Environment.for_project` means adding it here once.
71
+ def dependency_discovery_options(configuration)
72
+ {
73
+ bundler_bundle_path: configuration.bundler_bundle_path,
74
+ bundler_auto_detect: configuration.bundler_auto_detect,
75
+ bundler_lockfile: configuration.bundler_lockfile,
76
+ rbs_collection_lockfile: configuration.rbs_collection_lockfile,
77
+ rbs_collection_auto_detect: configuration.rbs_collection_auto_detect
78
+ }
79
+ end
80
+
81
+ # Loads the project's configured plugins the same way the plugin-inspection commands (`rigor plugins`,
82
+ # `rigor doctor`) do: a `Plugin::Services` with no cache store (these commands recompute) driving
83
+ # `Plugin::Loader.load`. Returns `nil` — so `Environment.for_project` skips the plugin tier entirely — when
84
+ # the project declares no plugins.
85
+ def load_plugin_registry(configuration)
86
+ return nil if configuration.plugins.empty?
87
+
88
+ services = Plugin::Services.new(
89
+ reflection: Reflection,
90
+ type: Type::Combinator,
91
+ configuration: configuration,
92
+ cache_store: nil
93
+ )
94
+ registry = Plugin::Loader.load(configuration: configuration, services: services)
95
+ run_plugin_prepare(registry)
96
+ registry
97
+ end
98
+
99
+ # `Analysis::WorkerSession#run_plugin_prepare` runs every plugin's `#prepare(services)` before the real
100
+ # `check` pipeline reads `services.fact_store` — an ADR-9 fact published there (#921's `:factory_index`,
101
+ # `rigor-activerecord`'s `:model_index`, …) is otherwise invisible. The probe commands built on
102
+ # {ProjectEnvironment} share the same plugin registry and the same `read_fact` call sites, so they need
103
+ # the same step; skipping it made every cross-plugin fact silently read nil under `type-of` / `annotate`
104
+ # / `trace` / `sig-gen` even once a plugin published it correctly. Per-plugin `rescue` keeps one plugin's
105
+ # `#prepare` failure from costing the rest — the probes have no diagnostic pipeline to report it through,
106
+ # so it is swallowed the same way the rest of this module fails soft.
107
+ def run_plugin_prepare(registry)
108
+ return if registry.nil? || registry.empty?
109
+
110
+ registry.plugins.each do |plugin|
111
+ plugin.prepare(plugin.services)
112
+ rescue StandardError
113
+ next
114
+ end
115
+ end
116
+
117
+ # The first fail-soft floor: no plugin tier and no synthesized RBS, but still the project's own dependency
118
+ # sources — dropping those is what issue #821 was, so a plugin-loading failure must not cost them.
119
+ def bare(configuration)
120
+ Environment.for_project(
121
+ libraries: configuration.libraries,
122
+ signature_paths: configuration.signature_paths,
123
+ **dependency_discovery_options(configuration)
124
+ )
125
+ rescue StandardError
126
+ minimal(configuration)
127
+ end
128
+
129
+ # The last floor: RBS core plus the project's own signature paths, which cannot fail on anything outside
130
+ # the project itself.
131
+ def minimal(configuration)
132
+ Environment.for_project(
133
+ libraries: configuration.libraries,
134
+ signature_paths: configuration.signature_paths
135
+ )
136
+ end
137
+ end
138
+ end
@@ -10,7 +10,24 @@ module Rigor
10
10
  # A named class, not a bare RuntimeError: {MutationScanner#classify} already folds a per-mutant
11
11
  # `StandardError` into the `:harness_error` bucket, and the scanner's file-level rescue must catch THIS
12
12
  # and nothing else, so the two failure modes stay distinguishable at every site that handles either.
13
- class AnalyzerCrashed < StandardError; end
13
+ #
14
+ # Issue #790 — it says WHICH of the two refusals below fired, because they want different recovery. A
15
+ # crashed check rule left the Environment as it found it; an analyzer defect (the #784 HKT registry
16
+ # build) recorded itself ON that Environment and is memoised there, so the caller that owns the
17
+ # Environment has to replace it before the next mutant is scored. {#analyzer_defect?} is how a caller
18
+ # tells the two apart without re-deriving the classification from the message it was handed.
19
+ class AnalyzerCrashed < StandardError
20
+ def initialize(message = nil, analyzer_defect: false)
21
+ super(message)
22
+ @analyzer_defect = analyzer_defect
23
+ end
24
+
25
+ # True when the refusal was an analyzer defect rather than a crashed check rule — the shape whose
26
+ # cause outlives the run that observed it.
27
+ def analyzer_defect?
28
+ @analyzer_defect
29
+ end
30
+ end
14
31
 
15
32
  # The kill oracles' guard against judging a mutant by a run that never happened.
16
33
  #
@@ -27,34 +44,96 @@ module Rigor
27
44
  # let {MutationScanner} put the mutant in the `harness_errors` bucket (#264), which is already excluded
28
45
  # from `killed + survived` and already surfaced by the CLI. A crash then READS as a crash.
29
46
  #
30
- # Only {Analysis::Result#crashed?} is armed — the check-rule rescue, the one shape that REPLACES a
31
- # file's whole diagnostic list. Refusing more than that is the same error pointed the other way, and it
47
+ # Two shapes are armed, and only two.
48
+ #
49
+ # {Analysis::Result#crashed?} — the check-rule rescue, the one shape that REPLACES a file's whole
50
+ # diagnostic list. Refusing more than the shapes below is the same error pointed the other way, and it
32
51
  # was measured: on a project whose plugin `prepare` raises, refusing the `:plugin` row took a file from
33
52
  # `killed=1 survived=6` to `killed=0 survived=0 harness_errors=7` — the builtin rules had run, their
34
53
  # diagnostics were all present, and a real measurement was thrown away. `invoke_plugin_prepare` appends
35
54
  # its row to every sequential run, so arming it would have refused every run for the life of the
36
- # process. `:rbs_build` is excluded for the same reason plus one more: the site filter that admits
37
- # mutations already drops receivers whose type did not resolve, so a class whose definition failed to
38
- # build contributes no measured sites to begin with.
55
+ # process. The user-caused `:rbs_build` rungs are excluded for the same reason plus one more: the site
56
+ # filter that admits mutations already drops receivers whose type did not resolve, so a class whose
57
+ # definition failed to build contributes no measured sites to begin with.
58
+ #
59
+ # {Analysis::CrashSignature.analyzer_defect?} (issue #790) — the one `:rbs_build` rung whose cause is
60
+ # Rigor rather than the user's `sig/`: post-#788 a mutant that makes the implicit HKT scan raise no
61
+ # longer produces a per-file `internal analyzer error` but ONE readable `rbs.coverage.hkt-scan-failed`
62
+ # row, so `Result#crashed?` stays false and the mutant was scored as a measurement over a silently
63
+ # degraded universe — issue #776 was exactly such a mutant. The run is readable (every rule fired), so
64
+ # it stays out of `#crashed?`, which answers the user-facing "may I read these diagnostics?"; what this
65
+ # guard asks is the narrower "is this a valid measurement OF RIGOR?", and it is not.
66
+ #
67
+ # The refusal is asked off {Analysis::CrashSignature} rather than off a second string match here, so a
68
+ # reworded row or a fourth rung added to `ANALYZER_DEFECT_RULES` cannot disarm one copy and leave the
69
+ # others armed (#696).
70
+ #
71
+ # The defect half is asked TWICE, of two surfaces, because each covers the other's blind spot. The row
72
+ # is severity-stamped like every other diagnostic, so `severity_overrides:` resolving `rbs` (or the
73
+ # exact rule id) to `off` drops it from the run entirely — and a harness that exists to find analyzer
74
+ # defects must not be the one thing a project's config can hide one from. {Rigor::Environment} records
75
+ # the failure BEFORE any severity resolution, so `#hkt_scan_failure` still answers. Conversely that slot
76
+ # only ever describes THIS process's Environment: under the fork pool the defect happens in a worker's
77
+ # own Environment and reaches the parent as the row alone.
39
78
  module AnalysisGuard
40
79
  module_function
41
80
 
42
- # Takes the whole {Analysis::Result} rather than its diagnostics so the question is asked through
43
- # {Analysis::Result#crashed?} — the same predicate the spec-side guard reads, off the same
81
+ # Takes the whole {Analysis::Result} rather than its diagnostics so the crash question is asked
82
+ # through {Analysis::Result#crashed?} — the same predicate the spec-side guard reads, off the same
44
83
  # {Analysis::CrashSignature} table. Hands back the diagnostics, which is all a kill comparison wants.
45
84
  #
46
- # @param result [Rigor::Analysis::Result] one analysis run.
47
- # @param context [String] which oracle call produced it, so the raise points at the right seam.
48
- # @return [Array<Rigor::Analysis::Diagnostic>] the run's diagnostics, when the run was healthy.
49
- # @raise [AnalyzerCrashed]
50
- def checked(result, context:)
51
- return result.diagnostics unless result.crashed?
85
+ # @param result — one analysis run.
86
+ # @param context — which oracle call produced it, so the raise points at the right seam.
87
+ # @param environment — the Environment the run was made over, read for the pre-severity record of a
88
+ # shared-build failure. Optional: a caller that has none loses only the severity-override case.
89
+ # @return the run's diagnostics, when the run was healthy.
90
+ # @raise AnalyzerCrashed
91
+ def checked(result, context:, environment: nil)
92
+ refuse_crash(result, context) if result.crashed?
93
+ defect = result.diagnostics.find { |diagnostic| Analysis::CrashSignature.analyzer_defect?(diagnostic) }
94
+ refuse_defect(context, Analysis::CrashSignature.describe(defect)) if defect
52
95
 
96
+ failure = shared_build_failure(environment)
97
+ refuse_defect(context, failure) if failure
98
+
99
+ result.diagnostics
100
+ end
101
+
102
+ # The pre-severity record, described the way {Analysis::CrashSignature.describe} describes a row, so
103
+ # the two refusals read alike whichever surface saw the defect.
104
+ def shared_build_failure(environment)
105
+ return nil unless environment.respond_to?(:hkt_scan_failure)
106
+
107
+ failure = environment.hkt_scan_failure
108
+ return nil if failure.nil?
109
+
110
+ error_class, message, frame, stage = failure
111
+ "#{stage} HKT registry build raised #{error_class}: #{message}#{" at #{frame}" if frame}"
112
+ end
113
+ private_class_method :shared_build_failure
114
+
115
+ def refuse_crash(result, context)
53
116
  raise AnalyzerCrashed,
54
117
  "#{context}: the analyzer crashed, so this run's diagnostics say nothing about the code " \
55
118
  "(#{Analysis::CrashSignature.describe(result.crash_diagnostics.first)}). A kill comparison " \
56
119
  "against it would score the mutant a survivor it was never measured against. See issue #686."
57
120
  end
121
+ private_class_method :refuse_crash
122
+
123
+ # The run is readable, so the message says what is wrong with it instead: the universe it was produced
124
+ # over is missing the type constructors the RBS `type` aliases and the loaded plugins would have
125
+ # contributed, and the BASELINE this mutant is compared against carries the identical degradation — so
126
+ # the set difference is empty for a reason that has nothing to do with the mutation.
127
+ def refuse_defect(context, description)
128
+ refusal = AnalyzerCrashed.new(
129
+ "#{context}: the analyzer degraded its own type universe, so this run is readable but is not a " \
130
+ "measurement of Rigor (#{description}). Scoring a mutant against it reports the analyzer's own " \
131
+ "defect as the mutant's survival. See issue #790.",
132
+ analyzer_defect: true
133
+ )
134
+ raise refusal
135
+ end
136
+ private_class_method :refuse_defect
58
137
  end
59
138
  end
60
139
  end