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
@@ -65,8 +65,7 @@ module Rigor
65
65
 
66
66
  VALID_PRODUCER_ID = /\A[a-z][a-z0-9._-]*\z/
67
67
 
68
- # @param root [String] cache root directory.
69
- # @param read_only [Boolean] when true, every disk-side side-effect is suppressed: `fetch_or_compute`
68
+ # @param read_only — when true, every disk-side side-effect is suppressed: `fetch_or_compute`
70
69
  # still reads existing entries (hits, gated on a current `schema_version.txt` marker — see
71
70
  # {#ensure_schema_version!}) and still runs the producer block on miss, but it does NOT write the
72
71
  # produced value to disk, does NOT update the marker, and does NOT touch the on-disk root directory.
@@ -107,7 +106,7 @@ module Rigor
107
106
 
108
107
  attr_reader :root
109
108
 
110
- # @return [Boolean] whether this Store suppresses disk writes (`schema_version.txt`, entry creation).
109
+ # @return whether this Store suppresses disk writes (`schema_version.txt`, entry creation).
111
110
  # Reads are unaffected.
112
111
  def read_only?
113
112
  @read_only
@@ -118,7 +117,7 @@ module Rigor
118
117
  # specific instance rather than the on-disk cache state. Disk-level state is reported separately by
119
118
  # {.disk_inventory}.
120
119
  #
121
- # @return [Hash] `{ hits:, misses:, writes:, by_producer: { id => { hits:, misses:, writes: } } }`
120
+ # @return `{ hits:, misses:, writes:, by_producer: { id => { hits:, misses:, writes: } } }`
122
121
  def stats
123
122
  @monitor.synchronize do
124
123
  per_producer = @by_producer.transform_values { |counts| counts.dup.freeze }.freeze
@@ -130,7 +129,7 @@ module Rigor
130
129
  # `rigor check --cache-stats` to surface cache size and per-producer entry counts without depending on
131
130
  # in-process counters (which only reflect the current run).
132
131
  #
133
- # @return [Hash] `{ root:, schema_version:, total_entries:, total_bytes:, producers: [{ id:, entries:,
132
+ # @return `{ root:, schema_version:, total_entries:, total_bytes:, producers: [{ id:, entries:,
134
133
  # bytes: }, ...] }`. When the root does not exist or has no schema-version marker, `schema_version` is
135
134
  # nil and the producer list is empty.
136
135
  #
@@ -176,21 +175,21 @@ module Rigor
176
175
  end
177
176
  private_class_method :collect_producers
178
177
 
179
- # @param producer_id [String] stable cache namespace; only `[a-z][a-z0-9._-]*` is accepted.
180
- # @param generation_cap [Integer, Symbol] how many generations of this producer survive a compaction
178
+ # @param producer_id — stable cache namespace; only `[a-z][a-z0-9._-]*` is accepted.
179
+ # @param generation_cap — how many generations of this producer survive a compaction
181
180
  # pass — a positive `Integer` for a whole-project producer (one live entry, older ones unreachable),
182
181
  # or {UNBOUNDED_GENERATIONS} for a producer with many simultaneously-live entries. REQUIRED, and
183
182
  # sourced from the producer's own declaration (`RbsCacheProducer.generation_cap`,
184
183
  # `RunCacheKey::GENERATION_CAP`, `Plugin::Base.producer generation_cap:`) rather than invented at the
185
184
  # call site. See {#evict!}.
186
- # @param params [Hash] producer inputs; mixed into the cache key via {Descriptor#cache_key_for}.
187
- # @param descriptor [Rigor::Cache::Descriptor] the invalidation descriptor for the value being cached.
188
- # @param serialize [#call, nil] optional callable that turns the producer's return value into a binary
185
+ # @param params — producer inputs; mixed into the cache key via {Descriptor#cache_key_for}.
186
+ # @param descriptor — the invalidation descriptor for the value being cached.
187
+ # @param serialize — optional callable that turns the producer's return value into a binary
189
188
  # `String`. Defaults to `Marshal.dump(value).b`. Producers whose return values are not
190
189
  # `Marshal`-clean (RBS-native objects with `RBS::Location` members, raw `IO`, …) MUST provide a
191
190
  # serialiser. The pair `(serialize, deserialize)` MUST round-trip — a producer that reads with one
192
191
  # strategy and writes with another corrupts its own cache slice.
193
- # @param deserialize [#call, nil] optional callable that turns bytes back into the producer's value.
192
+ # @param deserialize — optional callable that turns bytes back into the producer's value.
194
193
  # Defaults to `Marshal.load`. Any exception (`StandardError`) raised by the deserialiser is treated as
195
194
  # a cache miss — the entry is considered corrupt, the producer block reruns, and the next write
196
195
  # overwrites it. This is consistent with the fault-tolerance contract for the default `Marshal.load`
@@ -394,10 +393,22 @@ module Rigor
394
393
 
395
394
  # Reads and validates one entry file. Any failure (missing, short, bad magic, bad version, bad
396
395
  # checksum, unmarshal-able) returns nil so the caller treats it as a cache miss.
396
+ #
397
+ # The `ENOENT` rescue covers the gap between the existence check and the open: a concurrent process
398
+ # compacting or clearing the root can unlink the file in between, and a file that is gone reads as the
399
+ # same miss the existence check above already reports for one that was never there (issue #807). It is
400
+ # deliberately narrow — every other `SystemCallError` (a permission error, an I/O error) still raises,
401
+ # so a genuinely broken filesystem stays visible instead of degrading into an endless recompute. A
402
+ # present-but-corrupt file is not touched by this either: it still goes through the envelope and
403
+ # payload checks below.
397
404
  def read_entry(path, deserialize: nil)
398
405
  return nil unless File.file?(path)
399
406
 
400
- bytes = File.binread(path)
407
+ begin
408
+ bytes = File.binread(path)
409
+ rescue Errno::ENOENT
410
+ return nil
411
+ end
401
412
  return nil unless envelope_valid?(bytes)
402
413
 
403
414
  body = bytes.byteslice(HEADER.bytesize, bytes.bytesize - HEADER.bytesize - 32)
@@ -486,22 +497,34 @@ module Rigor
486
497
  def atomically_replace(path, body)
487
498
  File.open(path, File::RDWR | File::CREAT, 0o644) do |lock_fd|
488
499
  lock_fd.flock(File::LOCK_EX)
489
- tmp = "#{path}.tmp.#{Process.pid}.#{SecureRandom.hex(4)}"
490
- begin
491
- File.open(tmp, "wb") do |f|
492
- f.write(body)
493
- f.fsync
494
- end
495
- File.rename(tmp, path)
496
- ensure
497
- # A failed write/rename must not leak its temp file — rely on the 1-hour `cleanup_stale_temp_files`
498
- # sweep only as a backstop for crashes that skip this ensure entirely.
499
- unlink_entry(tmp) if File.exist?(tmp)
500
- end
500
+ publish_by_rename(path, body)
501
501
  fsync_directory(File.dirname(path))
502
502
  end
503
503
  end
504
504
 
505
+ # The rename-into-place half of {#atomically_replace}, without the destination lock: writes `body` to a
506
+ # sibling temp file, fsyncs it, and renames it over `path`. POSIX makes the rename atomic within one
507
+ # filesystem, so an unlocked concurrent reader sees either the previous contents in full or the new
508
+ # ones in full — never a truncated or half-written file.
509
+ #
510
+ # {#repair_writable_marker!} uses this half alone. It needs the atomicity (its reader is another
511
+ # process's constructor, which does not lock) but not the ordering a lock would buy: every writer of
512
+ # `schema_version.txt` writes the same value, so there is no last-writer-wins question to settle.
513
+ def publish_by_rename(path, body)
514
+ tmp = "#{path}.tmp.#{Process.pid}.#{SecureRandom.hex(4)}"
515
+ begin
516
+ File.open(tmp, "wb") do |f|
517
+ f.write(body)
518
+ f.fsync
519
+ end
520
+ File.rename(tmp, path)
521
+ ensure
522
+ # A failed write/rename must not leak its temp file — rely on the 1-hour `cleanup_stale_temp_files`
523
+ # sweep only as a backstop for crashes that skip this ensure entirely.
524
+ unlink_entry(tmp) if File.exist?(tmp)
525
+ end
526
+ end
527
+
505
528
  # Checks (and, for a writable store, repairs) the `schema_version.txt` marker, and reports whether the
506
529
  # disk tier is usable for the rest of this Store's lifetime. The result is memoized in `@disk_available`
507
530
  # — one check per Store is enough; a benign double-check under a thread race would just repeat
@@ -515,8 +538,6 @@ module Rigor
515
538
  # (permission, disk full, deleted root) also reports unavailable, degrading this Store instance to
516
539
  # in-memory-memo-only for the rest of its lifetime — the producer block still runs, its result still
517
540
  # lands in `@memo`, and disk reads/writes are simply skipped.
518
- #
519
- # @return [Boolean]
520
541
  def ensure_schema_version!
521
542
  return @disk_available unless @disk_available.nil?
522
543
 
@@ -532,25 +553,48 @@ module Rigor
532
553
  false
533
554
  end
534
555
 
556
+ # The marker is PUBLISHED BY RENAME rather than written in place. Its reader is another process's
557
+ # constructor, which takes no lock, so an `O_TRUNC`-then-write would expose a window in which the file
558
+ # exists and is empty; a reader landing there concludes the marker disagrees and clears the whole cache
559
+ # root — under, among others, a sibling that is between its own `File.file?` and `File.binread`, which
560
+ # is the `Errno::ENOENT` of issue #807. Two `rigor check` processes over one fresh `.rigor/cache` are
561
+ # enough to hit it.
562
+ #
563
+ # An EMPTY marker is treated as an ABSENT one — rewritten, never cleared on. Emptiness carries no
564
+ # information: it is what a torn write by an older Rigor looks like, what a crash between the create
565
+ # and the write leaves, and what this method's own clear-then-rewrite passes through. Answering an
566
+ # ambiguous signal by destroying a cache root is the wrong trade, and declining grants the entries
567
+ # below no trust they did not already have — a marker that is simply MISSING over existing entries is
568
+ # already kept and served (see the spec's writable-store rules), and `clear_cache_root!` removes the
569
+ # marker before rewriting it, so it produces that state itself.
535
570
  def repair_writable_marker!
536
571
  FileUtils.mkdir_p(@root)
537
572
  marker = File.join(@root, "schema_version.txt")
538
573
  current = self.class.schema_marker_value
539
574
 
540
- if File.file?(marker)
541
- on_disk = File.read(marker).strip
542
- return true if on_disk == current
575
+ on_disk = read_marker(marker)
576
+ return true if on_disk == current
543
577
 
544
- clear_cache_root!
545
- end
578
+ clear_cache_root! unless on_disk.nil? || on_disk.empty?
546
579
 
547
580
  FileUtils.mkdir_p(@root)
548
- File.write(marker, "#{current}\n")
581
+ publish_by_rename(marker, "#{current}\n")
582
+ fsync_directory(@root)
549
583
  true
550
584
  rescue StandardError
551
585
  false
552
586
  end
553
587
 
588
+ # @return the marker's content, or nil when there is none to read — including the case of a concurrent
589
+ # {#clear_cache_root!} removing it between the check and the open.
590
+ def read_marker(path)
591
+ return nil unless File.file?(path)
592
+
593
+ File.read(path).strip
594
+ rescue Errno::ENOENT
595
+ nil
596
+ end
597
+
554
598
  def clear_cache_root!
555
599
  Dir.children(@root).each do |entry|
556
600
  FileUtils.rm_rf(File.join(@root, entry))
@@ -61,6 +61,7 @@ module Rigor
61
61
  { id: "ci", name: "CI", format: nil, tier: :reviewdog,
62
62
  var: "CI", match: :truthy }
63
63
  ].freeze
64
+ Ractor.make_shareable(PROVIDERS)
64
65
 
65
66
  module_function
66
67
 
@@ -15,7 +15,7 @@ require_relative "../inference/scope_indexer"
15
15
  require_relative "../inference/statement_evaluator"
16
16
  require_relative "prism_colorizer"
17
17
  require_relative "command"
18
- require_relative "probe_environment"
18
+ require_relative "../project_environment"
19
19
 
20
20
  module Rigor
21
21
  class CLI
@@ -43,7 +43,7 @@ module Rigor
43
43
  # paging stays off because the CLI may itself sit in a pipeline.
44
44
  BAT_ARGS = %w[--language=ruby --style=plain --paging=never --color=always].freeze
45
45
 
46
- # @return [Integer] CLI exit status.
46
+ # @return CLI exit status.
47
47
  def run
48
48
  options = parse_options
49
49
  file = @argv.shift
@@ -132,10 +132,10 @@ module Rigor
132
132
 
133
133
  # The plugin-aware environment for the annotated file, so a `#=> <type>` matches what `rigor check`
134
134
  # infers on that line — including types synthesized from the file's own inline RBS annotations (the file
135
- # is threaded as the synthesizer's `source_files:`; see {ProbeEnvironment}).
135
+ # is threaded as the synthesizer's `source_files:`; see {ProjectEnvironment}).
136
136
  def base_scope(configuration, file)
137
137
  Scope.empty(
138
- environment: ProbeEnvironment.build(configuration: configuration, source_files: [file])
138
+ environment: ProjectEnvironment.build(configuration: configuration, source_files: [file])
139
139
  )
140
140
  end
141
141
 
@@ -220,8 +220,7 @@ module Rigor
220
220
  @scope_index = scope_index
221
221
  end
222
222
 
223
- # @param program [Prism::ProgramNode]
224
- # @return [Hash{Integer => Rigor::Type}] 1-indexed line => type.
223
+ # @return 1-indexed line => type.
225
224
  def collect(program)
226
225
  by_line = {}
227
226
  each_statement(program) do |statement|
@@ -32,14 +32,21 @@ module Rigor
32
32
  # The class-length budget is relaxed (as on `Rigor::CLI` itself) because `check` aggregates several independent
33
33
  # concerns that are clearer read together than split across micro-classes.
34
34
  class CheckCommand < Command # rubocop:disable Metrics/ClassLength
35
- # @return [Integer] CLI exit status.
35
+ # Issue #812 — `--fail-on=SEVERITY` raises the exit-status bar above the default (`:error`-only).
36
+ # Orders the three severities a diagnostic can carry once it survives {SeverityStamp} (`:off` rows are
37
+ # dropped upstream and never reach a `Result`, so they are not a member here).
38
+ FAIL_ON_RANK = { info: 0, warning: 1, error: 2 }.freeze
39
+
40
+ # @return CLI exit status.
36
41
  #
37
42
  # Deferred YJIT enablement (Runtime::Jit) is armed by `CLI#dispatch` for every command, this
38
43
  # one included, before any analysis work runs.
39
- def run # rubocop:disable Metrics/AbcSize
44
+ def run # rubocop:disable Metrics/AbcSize,Metrics/MethodLength
40
45
  # ADR-87 WD4 — parse options + resolve config WITHOUT the inference engine, so the run-cache hit probe
41
46
  # can run first. The heavy engine (`load_check_dependencies`) loads only on a miss / non-cacheable run.
42
47
  options = parse_check_options
48
+ return CLI::EXIT_USAGE unless valid_fail_on_option?(options)
49
+
43
50
  buffer = Options.resolve_buffer_binding(options, err: @err)
44
51
  return CLI::EXIT_USAGE if buffer == :usage_error
45
52
 
@@ -68,7 +75,8 @@ module Rigor
68
75
  result = apply_baseline_filter(raw_result, configuration, options)
69
76
 
70
77
  coverage = compute_coverage(runner, configuration, options)
71
- write_result(result, options.fetch(:format), coverage: coverage, config_warnings: config_warnings)
78
+ write_result(result, options.fetch(:format), coverage: coverage, config_warnings: config_warnings,
79
+ fail_on: options.fetch(:fail_on))
72
80
  emit_ci_detected_output(result, options)
73
81
  write_run_stats(result.stats) if result.stats
74
82
  write_trace_appendices
@@ -77,11 +85,34 @@ module Rigor
77
85
 
78
86
  exit_code = result.success? ? 0 : 1
79
87
  exit_code = 1 if baseline_strict_violation?(raw_result.diagnostics, configuration, options)
88
+ exit_code = 1 if fail_on_violation?(result.diagnostics, options)
80
89
  exit_code
81
90
  end
82
91
 
83
92
  private
84
93
 
94
+ # True when `diagnostics` (already baseline-filtered and severity-resolved — the same list
95
+ # `Result#success?` reads) carries a row at or above the `--fail-on` threshold. With the default
96
+ # `:error` threshold this is exactly `!diagnostics.any?(&:error?)`, i.e. a no-op over the existing exit
97
+ # code — `--fail-on` only ever makes a run *stricter*, never masks an `:error` the old exit code caught.
98
+ def fail_on_violation?(diagnostics, options)
99
+ threshold = FAIL_ON_RANK.fetch(options.fetch(:fail_on))
100
+ diagnostics.any? { |diagnostic| FAIL_ON_RANK.fetch(diagnostic.severity, FAIL_ON_RANK[:error]) >= threshold }
101
+ end
102
+
103
+ # True when `options[:fail_on]` (an arbitrary user-typed Symbol at this point — parsing only lower-cases
104
+ # and interns it, see `parse_check_options`) is one of {FAIL_ON_RANK}'s keys; writes a usage note to
105
+ # `@err` and returns false otherwise. Validated as a plain post-parse check rather than raised from
106
+ # inside the `opts.on` block: OptionParser rescues a `ParseError` raised from a switch's own block and
107
+ # rebuilds it as a generic `"invalid argument: --fail-on=<value>"`, discarding any custom message — this
108
+ # way the usage note actually names the accepted values.
109
+ def valid_fail_on_option?(options)
110
+ return true if FAIL_ON_RANK.key?(options.fetch(:fail_on))
111
+
112
+ @err.puts("rigor: invalid --fail-on value: #{options.fetch(:fail_on)} (expected error, warning, or info)")
113
+ false
114
+ end
115
+
85
116
  # ADR-87 WD4 — attempt the boot-slimming run-cache hit. Returns the cached {Analysis::Result} (severity
86
117
  # profile applied, no stats — matching a cache-served `Runner#run`) on a hit, or nil to fall through to
87
118
  # the full engine path. Only an ordinary sequential check whose result IS cache-served the same way is
@@ -129,11 +160,13 @@ module Rigor
129
160
  # gate reads the same diagnostics the full path would.
130
161
  def finalize_cache_hit(raw_result, configuration, options, config_warnings)
131
162
  result = apply_baseline_filter(raw_result, configuration, options)
132
- write_result(result, options.fetch(:format), config_warnings: config_warnings)
163
+ write_result(result, options.fetch(:format), config_warnings: config_warnings,
164
+ fail_on: options.fetch(:fail_on))
133
165
  emit_ci_detected_output(result, options)
134
166
 
135
167
  exit_code = result.success? ? 0 : 1
136
168
  exit_code = 1 if baseline_strict_violation?(raw_result.diagnostics, configuration, options)
169
+ exit_code = 1 if fail_on_violation?(result.diagnostics, options)
137
170
  exit_code
138
171
  end
139
172
 
@@ -233,8 +266,8 @@ module Rigor
233
266
 
234
267
  result = apply_baseline_filter(Analysis::Result.new(diagnostics: diagnostics, stats: nil), configuration,
235
268
  options)
236
- write_result(result, options.fetch(:format))
237
- result.success? ? 0 : 1
269
+ write_result(result, options.fetch(:format), fail_on: options.fetch(:fail_on))
270
+ !result.success? || fail_on_violation?(result.diagnostics, options) ? 1 : 0
238
271
  end
239
272
 
240
273
  # Editor mode option B (#146) — `--incremental` plus an editor buffer. The whole project is in scope with
@@ -260,8 +293,8 @@ module Rigor
260
293
  filtered = apply_baseline_filter(
261
294
  Analysis::Result.new(diagnostics: result.diagnostics, stats: nil), configuration, options
262
295
  )
263
- write_result(filtered, options.fetch(:format))
264
- filtered.success? ? 0 : 1
296
+ write_result(filtered, options.fetch(:format), fail_on: options.fetch(:fail_on))
297
+ !filtered.success? || fail_on_violation?(filtered.diagnostics, options) ? 1 : 0
265
298
  end
266
299
 
267
300
  # ADR-88 WD1 — a one-line stderr note when the plugin fact surface (an ADR-9 fact, an ADR-60 producer
@@ -439,6 +472,10 @@ module Rigor
439
472
  baseline: :unset,
440
473
  # ADR-22 slice 5 — `--baseline-strict` CI gate: fail the run on any baseline drift, in either direction.
441
474
  baseline_strict: false,
475
+ # Issue #812 — `--fail-on=SEVERITY` raises the exit-status bar: the run exits non-zero when a
476
+ # diagnostic AT OR ABOVE this severity survives baseline filtering and severity resolution, not only
477
+ # on `:error` (today's default and behaviour). See `FAIL_ON_RANK`.
478
+ fail_on: :error,
442
479
  # ADR-32 WD10 carry-over — `--treat-all-as-inline-rbs` forces the `rigor-rbs-inline` plugin into the loaded
443
480
  # plugin set with `require_magic_comment: false` so a single ad-hoc `rigor check` invocation treats every
444
481
  # analysed file as inline-RBS without the user editing `.rigor.yml`. Intended for single-file / ad-hoc CI use;
@@ -508,6 +545,12 @@ module Rigor
508
545
  "fail the run on any baseline drift (CI gate)") do
509
546
  options[:baseline_strict] = true
510
547
  end
548
+ opts.on("--fail-on=SEVERITY",
549
+ "exit non-zero on a diagnostic at or above SEVERITY: error (default), warning, or info") do |value|
550
+ # Validated post-parse (`valid_fail_on_option?`, called from `run`) rather than here — see its
551
+ # comment for why an unrecognised value is not raised from inside this block.
552
+ options[:fail_on] = value.to_s.downcase.to_sym
553
+ end
511
554
  opts.on("--treat-all-as-inline-rbs",
512
555
  "force-load rigor-rbs-inline with require_magic_comment: false") do
513
556
  options[:treat_all_as_inline_rbs] = true
@@ -661,7 +704,6 @@ module Rigor
661
704
  @err.puts(" body evals: #{counts[bt::MEMO_BODY_EVALS]}")
662
705
  @err.puts(" non-stored: on-stack #{counts[bt::MEMO_REFUSE_ON_STACK]} " \
663
706
  "unroll-in-flight #{counts[bt::MEMO_REFUSE_UNROLL]} " \
664
- "consult-tainted #{counts[bt::MEMO_REFUSE_CONSULT_TAINTED]} " \
665
707
  "transient-tainted #{counts[bt::MEMO_REFUSE_TRANSIENT]}")
666
708
  write_memo_signature_table
667
709
  end
@@ -841,10 +883,15 @@ module Rigor
841
883
  format == "text" ? @out : @err
842
884
  end
843
885
 
844
- def write_result(result, format, coverage: nil, config_warnings: [])
886
+ def write_result(result, format, coverage: nil, config_warnings: [], fail_on: :error)
845
887
  case format
846
888
  when "json"
847
889
  payload = enrich_json(result.to_h)
890
+ # Issue #812 — the exit code can be stricter than `payload["success"]` (which stays the `:error`-only
891
+ # reading `Result#success?` always gave, per the other consumers already reading it); a JSON
892
+ # consumer that wants to know WHY a run exited non-zero reads this alongside it rather than
893
+ # re-deriving `--fail-on`'s value from the invocation.
894
+ payload["fail_on"] = fail_on.to_s
848
895
  payload["coverage"] = coverage_payload(coverage) if coverage
849
896
  payload["config_warnings"] = config_warnings.map(&:to_h) unless config_warnings.empty?
850
897
  @out.puts(JSON.pretty_generate(payload))
@@ -22,7 +22,7 @@ module Rigor
22
22
  # The outcome of {.attempt} — the best-effort variant. Exactly one of the two is non-nil, so a caller can never
23
23
  # read "the check could not run" as "the check ran clean": `result` is nil precisely when `error` is set.
24
24
  Outcome = Data.define(:result, :error) do
25
- # @return [Boolean] true when an analysis actually completed.
25
+ # @return true when an analysis actually completed.
26
26
  def ran?
27
27
  !result.nil?
28
28
  end
@@ -42,12 +42,9 @@ module Rigor
42
42
 
43
43
  # Runs the analysis the way `rigor check` does and returns the runner + its raw result.
44
44
  #
45
- # @param configuration [Rigor::Configuration]
46
- # @param options [Hash] at least `:no_cache`, `:explain`, `:stats`, `:workers` (see {CheckRunnerFactory.build}).
47
- # @param paths [Array<String>, nil] analysed paths; nil falls back to the configuration's `paths:`.
48
- # @param buffer [Rigor::Analysis::BufferBinding, nil]
49
- # @param cache_root [String, nil] nil falls back to the configuration's cache path.
50
- # @return [Invocation]
45
+ # @param options — at least `:no_cache`, `:explain`, `:stats`, `:workers` (see {CheckRunnerFactory.build}).
46
+ # @param paths — analysed paths; nil falls back to the configuration's `paths:`.
47
+ # @param cache_root — nil falls back to the configuration's cache path.
51
48
  def run(configuration:, options:, paths: nil, buffer: nil, cache_root: nil)
52
49
  require_relative "check_runner_factory"
53
50
  runner = CheckRunnerFactory.build(
@@ -68,10 +65,8 @@ module Rigor
68
65
  # routing hint is outside the false-positive envelope, but a crash in it is not — `describe` must stay a command
69
66
  # an agent can run freely.
70
67
  #
71
- # @param config_path [String, nil] path to the config file; nil uses {Configuration.discover}.
72
- # @param options [Hash] runner options (defaults to {DEEP_OPTIONS}).
73
- # @param paths [Array<String>, nil]
74
- # @return [Outcome]
68
+ # @param config_path — path to the config file; nil uses {Configuration.discover}.
69
+ # @param options — runner options (defaults to {DEEP_OPTIONS}).
75
70
  def attempt(config_path: nil, options: DEEP_OPTIONS, paths: nil)
76
71
  require_relative "../configuration"
77
72
  configuration = Configuration.load(config_path)
@@ -14,12 +14,8 @@ module Rigor
14
14
 
15
15
  # Builds a runner matching the configuration/plugin/cache resolution that `rigor check` itself uses.
16
16
  #
17
- # @param configuration [Rigor::Configuration]
18
- # @param options [Hash] parsed CLI options (must contain at least
17
+ # @param options — parsed CLI options (must contain at least
19
18
  # `:no_cache`, `:explain`, `:stats`, `:workers`)
20
- # @param buffer [Rigor::Analysis::BufferBinding, nil]
21
- # @param cache_root [String]
22
- # @return [Rigor::Analysis::Runner]
23
19
  def build(configuration:, options:, buffer:, cache_root:)
24
20
  cache_store = if options.fetch(:no_cache)
25
21
  nil
@@ -53,7 +53,7 @@ module Rigor
53
53
  # `--test-command`.
54
54
  DEFAULT_TEST_COMMAND = %w[bundle exec rake].freeze
55
55
 
56
- # @return [Integer] CLI exit status.
56
+ # @return CLI exit status.
57
57
  #
58
58
  # Deferred YJIT enablement (Runtime::Jit) is armed by `CLI#dispatch` for every command, this
59
59
  # one included: a scan long enough to amortize JIT compile cost enables mid-flight; a short one
@@ -48,8 +48,6 @@ module Rigor
48
48
  # Both measurement-integrity warnings, and the escalation rule between them, live in
49
49
  # {MeasurementIntegrityWarning}. Only the floor stays here: it is this command's policy, and
50
50
  # `coverage_command_spec` reads it off this module.
51
- #
52
- # @param report [MutationProtectionReport, FusedProtectionReport]
53
51
  def warn_harness_errors(report)
54
52
  MeasurementIntegrityWarning.emit(report, err: @err, floor: HARNESS_ERROR_WARN_FLOOR)
55
53
  end
@@ -113,10 +111,21 @@ module Rigor
113
111
  cache_store: context.cache_store, workers: workers
114
112
  ),
115
113
  seed_bundles: Protection::DiscoverySeed.bundles(paths: paths),
116
- discovery_seed: seed
114
+ discovery_seed: seed,
115
+ rebuild_session: -> { rebuilt_session(context) }
117
116
  )
118
117
  end
119
118
 
119
+ # Issue #790 — how the oracle replaces an Environment a mutant degraded. {LanguageServer::ProjectContext}
120
+ # already has the operation: `invalidate!` drops the cached environment AND the project scan together,
121
+ # which is the pair the oracle measures over, and the next reader rebuilds both. Under
122
+ # {MutationForkScan} this runs in a forked child against its copy-on-write context, so one file's
123
+ # defect never costs the parent or its siblings a rebuild.
124
+ def rebuilt_session(context)
125
+ context.invalidate!
126
+ [context.environment, context.project_scan]
127
+ end
128
+
120
129
  # The scope {Protection::Mutator} judges a mutation site's receiver against, derived from the same seed
121
130
  # the oracle gets. nil (the gate off, or an empty seed) keeps the bare `Scope.empty` per file.
122
131
  def mutation_base_scope(seed, environment)
@@ -21,9 +21,7 @@ module Rigor
21
21
  module CoverageScan
22
22
  module_function
23
23
 
24
- # @param files [Array<String>] explicit `.rb` file paths to scan.
25
- # @param configuration [Rigor::Configuration]
26
- # @return [Rigor::CLI::CoverageReport]
24
+ # @param files — explicit `.rb` file paths to scan.
27
25
  #
28
26
  # Issue #513 — the environment is PLUGIN-AWARE (`LanguageServer::ProjectContext`, the same builder
29
27
  # `--protection` uses), not the bare RBS environment this path carried until 2026-09-01. Without the
@@ -57,8 +55,6 @@ module Rigor
57
55
  # the precision lens must mirror the walk it describes (`check` leaves the table empty unless
58
56
  # `parameter_inference:` is on, ADR-67 WD6a), while `--protection` seeds it unconditionally by ADR-67's own
59
57
  # wiring — it only ever reclassifies sites it already counted.
60
- #
61
- # @return [Rigor::Scope]
62
58
  def discovery_seeded_scope(files:, configuration:, environment:, parameter_inference:, workers: nil)
63
59
  base = Scope.empty(environment: environment)
64
60
  seed = Protection::DiscoverySeed.discovery_tables(files).dup
@@ -26,7 +26,7 @@ module Rigor
26
26
  class DiffCommand < Command
27
27
  USAGE = "Usage: rigor diff [options] <baseline.json> [paths...]"
28
28
 
29
- # @return [Integer] CLI exit status.
29
+ # @return CLI exit status.
30
30
  def run
31
31
  options = parse_options
32
32
 
@@ -31,14 +31,14 @@ module Rigor
31
31
  private_constant :MARKDOWN_LINK
32
32
 
33
33
  # Where the gem's `docs/` tree sits, so a rendered path can be made repository-relative.
34
- GEM_ROOT = File.expand_path("../../..", __dir__)
34
+ GEM_ROOT = File.expand_path("../../..", __dir__).freeze
35
35
  private_constant :GEM_ROOT
36
36
 
37
37
  module_function
38
38
 
39
- # @param body [String] the page as it is stored.
40
- # @param from [String] its absolute path, so relative targets resolve.
41
- # @return [String] the page with every relative link turned into a `rigor docs` key.
39
+ # @param body — the page as it is stored.
40
+ # @param from — its absolute path, so relative targets resolve.
41
+ # @return the page with every relative link turned into a `rigor docs` key.
42
42
  def rewrite(body, from:)
43
43
  dir = File.dirname(from)
44
44
  body.gsub(MARKDOWN_LINK) do
@@ -47,12 +47,12 @@ module Rigor
47
47
  USAGE
48
48
 
49
49
  # The bundled docs live at `<gem_root>/docs/`. From `lib/rigor/cli/docs_command.rb` that is three directories up.
50
- DOCS_ROOT = File.expand_path("../../../docs", __dir__)
51
- MANUAL_ROOT = File.join(DOCS_ROOT, "manual")
52
- HANDBOOK_ROOT = File.join(DOCS_ROOT, "handbook")
53
- LLMS_INDEX = File.join(DOCS_ROOT, "llms.txt")
50
+ DOCS_ROOT = File.expand_path("../../../docs", __dir__).freeze
51
+ MANUAL_ROOT = File.join(DOCS_ROOT, "manual").freeze
52
+ HANDBOOK_ROOT = File.join(DOCS_ROOT, "handbook").freeze
53
+ LLMS_INDEX = File.join(DOCS_ROOT, "llms.txt").freeze
54
54
 
55
- # @return [Integer] CLI exit status.
55
+ # @return CLI exit status.
56
56
  def run
57
57
  case @argv.first
58
58
  when nil
@@ -136,7 +136,7 @@ module Rigor
136
136
  # Resolve a query to a single doc. Exact relative-path and prefixed-basename aliases are unique; a short
137
137
  # (prefix-stripped) name is accepted only when one category owns it.
138
138
  #
139
- # @return [Hash, Integer] the doc entry, or an error exit status
139
+ # @return the doc entry, or an error exit status
140
140
  # after the error has been written to `@err`.
141
141
  def resolve_doc(query)
142
142
  # A key printed by a rendered page may carry the section it pointed at; resolve the page and say