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
@@ -87,12 +87,13 @@ module Rigor
87
87
  # user — every rule fired — but NOT a valid measurement of Rigor itself, so a harness that scores
88
88
  # Rigor's behaviour should treat one of these as a crash finding the way it treats `:check_rule`, or
89
89
  # a mutant that re-breaks the HKT scan (#776 was one) scores as a measurement over a silently degraded
90
- # universe. What consults it today: `tool/mutation`'s fuzz crash detector — on the severity-resolved
91
- # stream, so a `severity_overrides: rbs: off` hides the row from it. What does NOT yet: the ADR-69
92
- # kill oracle (`Protection::AnalysisGuard` reads `Result#crashed?`, which excludes every
93
- # `:rbs_build` row), and both harnesses still reuse one Environment across mutants, which memoises
94
- # the degraded registry after the first defect. Arming the oracle, inspecting the pre-severity row,
95
- # and resetting the Environment are issue #790.
90
+ # universe. Both Rigor-measuring harnesses consult it (#790): `tool/mutation`'s fuzz crash detector
91
+ # and {Protection::AnalysisGuard}, the ADR-69 kill oracles' refusal. Neither reads it alone — the row
92
+ # is severity-stamped like any other, so `severity_overrides:` can drop it from a run's diagnostics
93
+ # entirely, and each harness also reads {Rigor::Environment#hkt_scan_failure}, the pre-severity record
94
+ # the row is derived from. Both also replace their Environment after a defect: the registry build is
95
+ # memoised and the failure slot is first-write-wins, so an Environment that degraded once stays
96
+ # degraded for every later mutant.
96
97
  ANALYZER_DEFECT_RULES = %w[
97
98
  rbs.coverage.hkt-scan-failed
98
99
  ].freeze
@@ -103,9 +104,6 @@ module Rigor
103
104
  # into. Built here, not at either rescue site, so the two twins cannot drift (issue #665) — and so
104
105
  # the appended crash frame is derived identically on the sequential and pooled paths. Keeps the
105
106
  # {CHECK_RULE_MESSAGE_PREFIX} prefix every consumer matches on; the frame is a trailing hint.
106
- #
107
- # @param error [StandardError]
108
- # @return [String]
109
107
  def check_rule_message(error)
110
108
  base = "#{CHECK_RULE_MESSAGE_PREFIX}: #{error.class}: #{error.message}"
111
109
  frame = crash_frame(error)
@@ -117,9 +115,6 @@ module Rigor
117
115
  # bundled or third-party plugin also lives under `lib/rigor/<plugin>/`, so a frame here says where
118
116
  # the raise was, never whose defect it is. Falls back to the raw top frame when the crash is entirely
119
117
  # inside a dependency, and to nil when there is no backtrace at all.
120
- #
121
- # @param error [Exception]
122
- # @return [String, nil]
123
118
  def crash_frame(error)
124
119
  frames = error.backtrace
125
120
  return nil if frames.nil? || frames.empty?
@@ -132,15 +127,11 @@ module Rigor
132
127
  # (the fork pool) rather than deriving it fresh from a live `Exception` — can relativize the frame it
133
128
  # already has without re-deriving `.crash_frame`'s "which frame" choice. Nil-safe, and a no-op (`sub`
134
129
  # never matches) on a frame with no `lib/rigor/` segment at all.
135
- #
136
- # @param frame [String, nil]
137
- # @return [String, nil]
138
130
  def relativize_frame(frame)
139
131
  frame&.sub(%r{\A.*/(lib/rigor/)}, '\1')
140
132
  end
141
133
 
142
- # @param diagnostic [Rigor::Analysis::Diagnostic]
143
- # @return [Symbol, nil] `:check_rule`, `:plugin`, `:rbs_build`, or nil for an ordinary diagnostic.
134
+ # @return `:check_rule`, `:plugin`, `:rbs_build`, or nil for an ordinary diagnostic.
144
135
  def reason(diagnostic)
145
136
  return :check_rule if diagnostic.message.to_s.start_with?(CHECK_RULE_MESSAGE_PREFIX)
146
137
  return :plugin if plugin_isolation_row?(diagnostic)
@@ -152,8 +143,6 @@ module Rigor
152
143
  # True when `diagnostic` is the rescue row that REPLACED a file's analysis — the only shape after which
153
144
  # the run's diagnostics say nothing about the code. NOT a general "something went wrong" predicate; see
154
145
  # the class doc for why `:plugin` and `:rbs_build` are excluded.
155
- #
156
- # @param diagnostic [Rigor::Analysis::Diagnostic]
157
146
  def discards_file_analysis?(diagnostic)
158
147
  reason(diagnostic) == DISCARDS_FILE_ANALYSIS_REASON
159
148
  end
@@ -162,17 +151,12 @@ module Rigor
162
151
  # measurement of Rigor — see {ANALYZER_DEFECT_RULES}. Orthogonal to {.discards_file_analysis?}: the
163
152
  # user-facing tier reads the run; the Rigor-measuring tier should refuse it (the mutation fuzz does,
164
153
  # the ADR-69 kill oracle does not yet — #790).
165
- #
166
- # @param diagnostic [Rigor::Analysis::Diagnostic]
167
154
  def analyzer_defect?(diagnostic)
168
155
  ANALYZER_DEFECT_RULES.include?(diagnostic.rule)
169
156
  end
170
157
 
171
158
  # A one-line "<reason> at <path>:<line>: <message>" for a failure message, so whoever reads the raise
172
159
  # sees which shape fired and where without re-deriving it.
173
- #
174
- # @param diagnostic [Rigor::Analysis::Diagnostic]
175
- # @return [String]
176
160
  def describe(diagnostic)
177
161
  "#{reason(diagnostic) || :unknown} at #{diagnostic.path}:#{diagnostic.line}: #{diagnostic.message}"
178
162
  end
@@ -126,6 +126,29 @@ module Rigor
126
126
  [result, capture.snapshot]
127
127
  end
128
128
 
129
+ # Issue #992 — runs the block with its reads DETACHED from the current consumer (and from any enclosing
130
+ # capture) and returns `[block_result, read_set]`, so a caller that learns only afterwards whether its
131
+ # answer depended on those reads can {replay} them or drop them. `call.wrong-arity` reads a class's whole
132
+ # envelope bucket to decide a call that turns out to fit, and recording that as a file-level ancestry
133
+ # edge would make every correct call on a project class re-check on any edit to the class's files.
134
+ # Returns a nil read set when no recording is active.
135
+ def withhold
136
+ previous = Thread.current[KEY]
137
+ return [yield, nil] if previous.nil?
138
+
139
+ previous_captures = Thread.current[CAPTURE_KEY]
140
+ capture = Capture.new
141
+ Thread.current[KEY] = Accumulator.new(previous.consumer)
142
+ Thread.current[CAPTURE_KEY] = [capture]
143
+ begin
144
+ result = yield
145
+ ensure
146
+ Thread.current[KEY] = previous
147
+ Thread.current[CAPTURE_KEY] = previous_captures
148
+ end
149
+ [result, capture.snapshot]
150
+ end
151
+
129
152
  # ADR-84 WD2 — replays a {ReadSet} into the current consumer's accumulator as if each read happened
130
153
  # here, applying the same per-consumer self-read filter as {read_site}, and tees into every active
131
154
  # capture so an enclosing capture window stays transitive when a memo hit substitutes for a body walk.
@@ -25,7 +25,7 @@ module Rigor
25
25
  @mutex = Mutex.new
26
26
  end
27
27
 
28
- # @return [Array<Entry>] frozen snapshot of the recorded boundary-cross events. Each entry is a Data
28
+ # @return frozen snapshot of the recorded boundary-cross events. Each entry is a Data
29
29
  # with `class_name` (String), `method_name` (Symbol), `gem_name` (String), and `rbs_display` (String
30
30
  # — the authoritative-side type's human-facing form, embedded into the diagnostic message).
31
31
  def entries
@@ -17,8 +17,6 @@ module Rigor
17
17
  module Builder
18
18
  module_function
19
19
 
20
- # @param dependencies [Rigor::Configuration::Dependencies]
21
- # @return [Index]
22
20
  def build(dependencies)
23
21
  return Index::EMPTY if dependencies.empty?
24
22
 
@@ -30,8 +30,6 @@ module Rigor
30
30
 
31
31
  module_function
32
32
 
33
- # @param entry [Rigor::Configuration::Dependencies::Entry]
34
- # @return [Resolved, Unresolvable]
35
33
  def resolve(entry)
36
34
  spec = locate_gem_spec(entry.gem)
37
35
  return Unresolvable.new(gem_name: entry.gem, reason: :not_in_bundle) if spec.nil?
@@ -13,19 +13,19 @@ module Rigor
13
13
  attr_reader :resolved_gems, :unresolvable, :method_catalog, :budget_exceeded,
14
14
  :class_to_gem, :budget_overrun_strategy, :gem_modes
15
15
 
16
- # @param method_catalog [Hash{[String, Symbol] => Symbol}] the flat
16
+ # @param method_catalog — => Symbol}] the flat
17
17
  # `(class_name, method_name) → :instance | :singleton` table produced by {Walker.walk}, aggregated
18
18
  # across every resolved gem in the run. The Index itself stays gem-agnostic — the per-gem
19
19
  # attribution that slice 3's cache descriptor needs lives on `Resolved`, not here.
20
- # @param budget_exceeded [Array<String>] gem names whose {Walker} run hit the per-gem catalog cap
20
+ # @param budget_exceeded — gem names whose {Walker} run hit the per-gem catalog cap
21
21
  # (slice 4). The Runner consumes this list to emit one `dynamic.dependency-source.budget-exceeded`
22
22
  # warning per gem.
23
- # @param class_to_gem [Hash<String, String>] reverse lookup `class_name → gem_name` (slice 5b). Built
23
+ # @param class_to_gem — reverse lookup `class_name → gem_name` (slice 5b). Built
24
24
  # first-write-wins: when two opt-in gems re-open the same class, the first gem owns it. The
25
25
  # dispatcher consults this map under the `:dependency_silence` budget overrun strategy so call
26
26
  # sites on a budget-exceeded gem's classes degrade to `Dynamic[top]` instead of falling through to
27
27
  # the user-class fallback.
28
- # @param gem_modes [Hash<String, Symbol>] per-gem mode table (`gem_name → :disabled | :when_missing |
28
+ # @param gem_modes — per-gem mode table (`gem_name → :disabled | :when_missing |
29
29
  # :full`). ADR-10 slice 5c consults this through {#mode_for} to identify call sites where
30
30
  # gem-source and RBS both contribute under `mode: :full`. The map is keyed on `gem_name` (not
31
31
  # class) because re-opened classes belong to the first gem they appeared in per `class_to_gem`;
@@ -56,7 +56,7 @@ module Rigor
56
56
  end
57
57
  private :normalize_catalog
58
58
 
59
- # @return [String, nil] the gem that owns `class_name` (first-write-wins); `nil` when the class isn't
59
+ # @return the gem that owns `class_name` (first-write-wins); `nil` when the class isn't
60
60
  # in any opt-in gem's catalog.
61
61
  def gem_for(class_name)
62
62
  @class_to_gem[class_name]
@@ -28,8 +28,7 @@ module Rigor
28
28
  module ReturnTypeHeuristic
29
29
  module_function
30
30
 
31
- # @param def_node [Prism::DefNode]
32
- # @return [Rigor::Type, nil] heuristic return type, or `nil` when the body's tail expression doesn't
31
+ # @return heuristic return type, or `nil` when the body's tail expression doesn't
33
32
  # match any of the recognised shapes.
34
33
  def extract(def_node)
35
34
  body = def_node.body
@@ -55,12 +55,12 @@ module Rigor
55
55
 
56
56
  module_function
57
57
 
58
- # @param gem_dir [String, Pathname] absolute path to the gem's installation directory.
59
- # @param roots [Array<String>] subdirectory names within the gem to walk (defaults to `["lib"]` per
58
+ # @param gem_dir — absolute path to the gem's installation directory.
59
+ # @param roots — subdirectory names within the gem to walk (defaults to `["lib"]` per
60
60
  # `Configuration::Dependencies::Entry`).
61
- # @param budget [Integer, Float] per-gem catalog cap (method-definition count). When unset, defaults
61
+ # @param budget — per-gem catalog cap (method-definition count). When unset, defaults
62
62
  # to `UNBOUNDED` for backwards-compatible test paths.
63
- # @return [Outcome] frozen wrapper carrying the catalog (`Hash{[class_name, method_name] =>
63
+ # @return frozen wrapper carrying the catalog (`Hash{[class_name, method_name] =>
64
64
  # :instance | :singleton}`) and a `truncated?` flag set when the walker stopped harvesting because
65
65
  # the budget was reached. Methods of identical name on the same class with different kinds (rare;
66
66
  # private API mostly) carry the kind that wins the per-class first walk.
@@ -52,17 +52,16 @@ module Rigor
52
52
  # otherwise rebuild (the probe loaded the plugins to key the entry, so it already knows it).
53
53
  Served = Data.define(:table, :sources, :registry, :plugin_facts)
54
54
 
55
- # @param configuration [Rigor::Configuration] with effects already enabled by the caller.
56
- # @param cache_root [String, nil]
55
+ # @param configuration — with effects already enabled by the caller.
57
56
  def initialize(configuration:, cache_root:)
58
57
  @configuration = configuration
59
58
  @cache_root = cache_root
60
59
  end
61
60
 
62
- # @param paths [Array<String>] the analysed set — `configuration.paths` unioned with any path
61
+ # @param paths — the analysed set — `configuration.paths` unioned with any path
63
62
  # arguments, exactly what {CLI::EffectsCommand#analyze} hands the runner, because the analysed
64
63
  # set is part of the diagnostics key.
65
- # @return [Served, nil] nil to decline, on any failure whatsoever.
64
+ # @return nil to decline, on any failure whatsoever.
66
65
  def serve(paths)
67
66
  return nil if @cache_root.nil? || !@configuration.effects_check?
68
67
 
@@ -19,8 +19,7 @@ module Rigor
19
19
 
20
20
  module_function
21
21
 
22
- # @param parse_result [Prism::ParseResult]
23
- # @return [Boolean] true when the parsed source looks like an ERB template (parse errors expected;
22
+ # @return true when the parsed source looks like an ERB template (parse errors expected;
24
23
  # analysis should skip).
25
24
  def template?(parse_result)
26
25
  source = parse_result.source.source
@@ -18,9 +18,23 @@ module Rigor
18
18
  ].freeze
19
19
 
20
20
  class Target < Data.define(:kind, :name)
21
+ # #775 — a target is an immutable value keyed by its name, and the local target is built on every
22
+ # binding (`Scope#with_local` invalidates through it) and every local-fact read: ~350k times on
23
+ # the lib self-check for a few thousand distinct names. One shared instance per name.
24
+ #
25
+ # #1055 — the table is a class ivar holding an unshareable Hash, and a non-main Ractor may neither
26
+ # write one nor read one. A pool worker therefore pays the allocation instead of sharing the
27
+ # instance: the interning is a per-process ALLOCATION saving, never an identity contract (`Target`
28
+ # is a `Data` value and compares by value), so declining it changes nothing a caller can observe.
29
+ # The guard is a C-level predicate on the hot path, which is what keeps the sequential run's
30
+ # saving intact.
21
31
  def self.local(name)
22
- new(kind: :local, name: name.to_sym)
32
+ name = name.to_sym
33
+ return new(kind: :local, name: name) unless Ractor.main?
34
+
35
+ @local_targets[name] ||= new(kind: :local, name: name)
23
36
  end
37
+ @local_targets = {}
24
38
 
25
39
  def initialize(kind:, name:)
26
40
  super(kind: kind.to_sym, name: name)
@@ -120,6 +120,48 @@ module Rigor
120
120
  appeared.freeze
121
121
  end
122
122
 
123
+ # Issue #1120 — the method names whose refinements moved in this edit: a `[refined class, method, refining
124
+ # module]` entry that appeared in, or vanished from, any file in `paths`. A consumer whose
125
+ # `call.undefined-method` answer consulted the refinement table recorded a `refinement:<method>` edge
126
+ # whichever way it answered, because the answer is a function of the whole project's refinements of that
127
+ # name. `paths` MUST include removed files, whose whole before-table vanished. `before` / `after` map a
128
+ # path to that file's `{refined class => {method => [modules]}}` table (nil when it refines nothing).
129
+ def changed_refinement_names(paths, before, after)
130
+ moved = Set.new
131
+ paths.each do |path|
132
+ entries_before = refinement_entries(before[path])
133
+ entries_after = refinement_entries(after[path])
134
+ (entries_before ^ entries_after).each { |(_refined, method_name, _module)| moved << method_name }
135
+ end
136
+ moved.freeze
137
+ end
138
+
139
+ def refinement_entries(table)
140
+ entries = Set.new
141
+ table&.each do |refined, methods|
142
+ methods.each do |method_name, modules|
143
+ modules.each { |refining| entries << [refined, method_name, refining] }
144
+ end
145
+ end
146
+ entries
147
+ end
148
+
149
+ # Issue #639 — the SYMMETRIC reading of {appeared_classes}: the names whose declaration in a file
150
+ # appeared OR VANISHED. Appearance alone answered the miss side, which was the only side recording a
151
+ # `class:` edge; a bare reference that RESOLVED now records one too, and what invalidates it is the
152
+ # declaration going away. `paths` MUST therefore include REMOVED files — a removed file has no
153
+ # after-state, so its whole before-set vanished — which is the same reason
154
+ # {changed_constant_publications} diffs over `changed + removed`.
155
+ def changed_class_declarations(paths, decls_before, decls_after)
156
+ moved = Set.new
157
+ paths.each do |path|
158
+ before = decls_before[path] || Set.new
159
+ after = decls_after[path] || Set.new
160
+ moved.merge(after ^ before)
161
+ end
162
+ moved.freeze
163
+ end
164
+
123
165
  # Issue #644 — the qualified constant names whose PUBLICATION could have moved in this edit: the names
124
166
  # whose per-file census descriptor differs between the before- and after-state of any file in `paths`.
125
167
  # The value-constant twin of {appeared_classes}, and deliberately NOT its analogue — a class either
@@ -42,22 +42,22 @@ module Rigor
42
42
  end
43
43
  end
44
44
 
45
- # @param paths [Array<String>, nil] explicit analysis roots; nil (the default) uses the configuration's
45
+ # @param paths — explicit analysis roots; nil (the default) uses the configuration's
46
46
  # `paths:`.
47
- # @param environment [Rigor::Environment, nil] optional shared environment to thread into each internal
47
+ # @param environment — optional shared environment to thread into each internal
48
48
  # Runner. Long-lived callers and specs can use this to avoid rebuilding the same RBS universe for
49
49
  # every baseline / recheck / oracle run.
50
- # @param cache_store [Rigor::Cache::Store, nil] ADR-85 WD1 — the persistent cache each internal Runner
50
+ # @param cache_store — ADR-85 WD1 — the persistent cache each internal Runner
51
51
  # exposes to the RBS-env and plugin-producer tiers. A cross-process `--incremental` recheck otherwise
52
52
  # rebuilt a fresh runner with no store, so every plugin `#prepare` producer (the ADR-9/#74/ADR-60 WD3
53
53
  # record-and-validate caches) recomputed per invocation — 86% of a Rails warm incremental. Threading
54
54
  # the store lets those producers serve from disk. `nil` (the default) preserves the pre-#85 behaviour
55
55
  # the specs assert; the whole-run ADR-45 result cache stays disabled on these runs
56
56
  # (`Runner#run_result_cacheable?` excludes `record_dependencies` / `analyze_only`).
57
- # @param plugin_requirer [#call, nil] optional gem-require hook threaded into each internal Runner
57
+ # @param plugin_requirer — optional gem-require hook threaded into each internal Runner
58
58
  # (mirrors {Runner}'s parameter). nil (the default, and what the CLI passes) uses `Kernel.require`;
59
59
  # embedders and specs inject a fake so a test plugin registers without touching the real load path.
60
- # @param workers [Integer] ADR-46 — the resolved fork-pool worker count threaded into every internal
60
+ # @param workers — ADR-46 — the resolved fork-pool worker count threaded into every internal
61
61
  # analyzer, so a `--incremental` recheck's closure re-analysis parallelises exactly like the standard
62
62
  # `check` path (the recon's audit: `--workers` / `RIGOR_RACTOR_WORKERS` / `parallel.workers:` were
63
63
  # silently ignored because `build_runner` passed no `workers:`). 0 (the default, and what the specs and
@@ -112,7 +112,7 @@ module Rigor
112
112
  # and a missing invalidation edge is impossible by construction — the reason this is a table diff
113
113
  # and not the caller→callee edge recording #204 first sketched.
114
114
  @param_table = {}
115
- reset_effect_state
115
+ reset_carried_state
116
116
  # ADR-88 WD1 — the plugin fact-surface digest computed for THIS invocation (nil until a
117
117
  # `#run_incremental` pass runs / a plugin-free project) and the reporting flags a caller (the CLI
118
118
  # banner + `--cache-stats`) reads after `#run_incremental`. `@last_runner` is the analysis runner the
@@ -162,7 +162,11 @@ module Rigor
162
162
  runner = build_runner(record_dependencies: true)
163
163
  diagnostics = run_runner(runner).diagnostics
164
164
  @last_runner = runner # ADR-88 WD1 — the post-hoc fact-surface fingerprint reads this prepared registry.
165
- @analyzed = runner.analyzed_files
165
+ # #392 — the template units are deliberately NOT in the analysed set. `Runner#target_files`
166
+ # re-analyses every unit on every run, so a unit must never be reused from the per-file cache and
167
+ # must never read as a file that VANISHED from the project on the next recheck (which is what
168
+ # `previous - current` would say, `current_files` being a `.rb` expansion).
169
+ @analyzed = runner.analyzed_files - runner.template_unit_paths
166
170
  @seed_bundles = runner.seed_bundles # ADR-85 WD2 — the freshly built bundle set for the next run.
167
171
  absorb_dependency_graph(runner)
168
172
  @return_summaries = runner.return_summaries # ADR-89 WD2 — the full-run behavioural surface.
@@ -174,6 +178,7 @@ module Rigor
174
178
  @effects_identity = current_effects_identity
175
179
  @cache = per_file(runner.per_file_diagnostics)
176
180
  @digests = @analyzed.to_h { |path| [path, pack_digest(path)] }
181
+ @run_level_rows = runner.run_level_rows
177
182
  diagnostics
178
183
  end
179
184
 
@@ -197,7 +202,8 @@ module Rigor
197
202
  # The freshly collected table is handed to the runner so the run seeds from the SAME table the diff
198
203
  # was decided on (and the collector runs once per recheck, not twice).
199
204
  runner = build_runner(analyze_only: analyze_set, record_dependencies: true,
200
- param_inferred_types: fresh_params)
205
+ param_inferred_types: fresh_params,
206
+ restored_run_level_rows: @run_level_rows)
201
207
  fresh = run_runner(runner).diagnostics
202
208
  @last_runner = runner # ADR-88 WD1 — the post-hoc fact-surface fingerprint reads this prepared registry.
203
209
  reused = (current & previous) - affected.to_a
@@ -281,12 +287,24 @@ module Rigor
281
287
  symbol_pairs = behaviourally_unstable_pairs(changed_pairs, unstable, scan_index) | param_pairs
282
288
  base = dependents_base(unstable, symbol_pairs)
283
289
  closure = base | changed.to_set | added.to_set |
284
- negative_affected(scan, removed, new_fps, new_class_decls, new_constant_decls)
290
+ negative_affected(scan, removed, new_fps, new_class_decls, new_constant_decls, summary)
285
291
  closure = param_seed_closure(closure, param_files)
286
292
  removed.each { |path| closure |= @dependents[path] || Set.new }
287
293
  closure.freeze
288
294
  end
289
295
 
296
+ # Issue #1367 — every file whose last result holds a `global.*` diagnostic re-checks on any recheck. What
297
+ # exempts such a write is a fact of the whole program — an alias, or a `write` / `to_str` / `to_int` / hatch
298
+ # defined in any spelling on any receiver, a rewritten ancestor, a mixin — which no name-keyed edge can
299
+ # follow, so a stale report would outlive the edit that exempts it. Such files are rare, so this costs little.
300
+ # A file the census silenced is not re-checked when the exemption goes away: that stale answer misses a
301
+ # report, it never makes one.
302
+ def global_write_reporters
303
+ @cache.each_with_object(Set.new) do |(path, diagnostics), reporters|
304
+ reporters << path if diagnostics.any? { |diagnostic| diagnostic.rule.to_s.start_with?("global.") }
305
+ end
306
+ end
307
+
290
308
  # ADR-67 WD6c lift — the seed-invalidated callees' own contribution to the closure: the files
291
309
  # themselves, plus — on a pre-slice-4 snapshot with no symbol edges, where the pairs' symbol fan-out
292
310
  # found nothing — their file-level dependents (wider, always sound).
@@ -407,10 +425,13 @@ module Rigor
407
425
  end
408
426
 
409
427
  # The current project file set (cheap directory expansion, no analysis), used to detect files added /
410
- # removed since the last run.
428
+ # removed since the last run. Also where an editor buffer's binding is re-spelled onto the set, since
429
+ # this is where the run first learns how it spells its own members.
411
430
  def current_files
412
431
  runner = build_runner
413
- @paths ? runner.analysis_file_set(@paths) : runner.analysis_file_set
432
+ files = @paths ? runner.analysis_file_set(@paths) : runner.analysis_file_set
433
+ rebind_buffer(files)
434
+ files
414
435
  end
415
436
 
416
437
  # Verification engine (the `--verify-incremental` gate): with NO source edit, re-analyze `subset` fresh
@@ -422,7 +443,8 @@ module Rigor
422
443
  affected = subset.to_set
423
444
  # ADR-67 WD6c lift — seed the subset run from the baseline's own table so the verification engine
424
445
  # exercises the exact seeds the served cache entries were computed under (and skips a re-collect).
425
- runner = build_runner(analyze_only: affected, param_inferred_types: @param_table)
446
+ runner = build_runner(analyze_only: affected, param_inferred_types: @param_table,
447
+ restored_run_level_rows: @run_level_rows)
426
448
  fresh = run_runner(runner).diagnostics
427
449
  reused = @analyzed - affected.to_a
428
450
  fresh + reused.flat_map { |path| @cache[path] || [] }
@@ -559,6 +581,9 @@ module Rigor
559
581
  restore_structural_declarations(payload)
560
582
  @return_summaries = payload.return_summaries || {}
561
583
  @param_table = payload.param_table || {} # ADR-67 WD6c lift — the seeds the cache was built under.
584
+ # Issues #796 / #794 — absent (nil) in a snapshot written before schema 22 is impossible (the SCHEMA
585
+ # gate rejects one), so nil here means the writing run genuinely had nothing to say.
586
+ @run_level_rows = payload.run_level_rows
562
587
  # ADR-103 WD13 / #382 — the effects sidecar rides its own identity, so a snapshot whose summaries
563
588
  # were collected under a different vocabulary / catalogue / `effects:` block restores as EMPTY
564
589
  # rather than as stale rows. {#run_incremental} turns that emptiness into a full baseline, because
@@ -592,7 +617,8 @@ module Rigor
592
617
  return_summaries: marshal_safe_return_summaries,
593
618
  param_table: marshal_safe_param_table,
594
619
  effect_collections: marshal_safe_effect_collections,
595
- effects_identity: @effects_identity
620
+ effects_identity: @effects_identity,
621
+ run_level_rows: @run_level_rows
596
622
  )
597
623
  end
598
624
 
@@ -606,6 +632,15 @@ module Rigor
606
632
  @effects_identity = nil
607
633
  end
608
634
 
635
+ # The state this session carries ACROSS processes in the snapshot rather than deriving each run: the
636
+ # effects sidecar above, and (issues #796 / #794) the run-level rows the last FULL run answered — the
637
+ # `definition-build-failed` set and the HKT-scan outcome, nil until a full run has produced them, and
638
+ # handed to every narrowed run this session builds, none of which can derive them from its own files.
639
+ def reset_carried_state
640
+ reset_effect_state
641
+ @run_level_rows = nil
642
+ end
643
+
609
644
  # Whether the effects half of a restored snapshot permits reuse. Collection off is vacuously true —
610
645
  # the session keeps no collections, writes none, and the pre-#382 reuse decision stands unchanged.
611
646
  def effects_reuse_permitted?(payload)
@@ -696,6 +731,9 @@ module Rigor
696
731
  @digests[path] = pack_digest(path)
697
732
  end
698
733
  absorb_dependency_graph(runner)
734
+ # Issues #796 / #794 — the recheck's own snapshots already carry the replayed rows folded together
735
+ # with anything its closure demanded for itself, so this is the same union the next run replays.
736
+ @run_level_rows = runner.run_level_rows
699
737
  refresh_return_summaries(runner, analyze_set)
700
738
  refresh_effect_collections(runner, analyze_set)
701
739
  end
@@ -803,22 +841,42 @@ module Rigor
803
841
  end
804
842
 
805
843
  # ADR-46 slice 3 — the consumers to re-check because a symbol that appeared in a changed file resolves
806
- # a prior missed lookup. Maps each appeared `"ClassName#method"` to the negative-dependency key it
807
- # would satisfy (`toplevel:foo` for a top-level def, `method:C#m` otherwise), then unions the recorded
808
- # negative-dependents of those keys.
809
- def negative_affected(changed, removed, new_fingerprints, new_class_decls, new_constant_decls)
844
+ # a prior missed lookup, and (issue #639) because a class DECLARATION appeared or vanished. Maps each
845
+ # appeared `"ClassName#method"` to the negative-dependency key it would satisfy (`toplevel:foo` for a
846
+ # top-level def, `method:C#m` otherwise), then unions the recorded negative-dependents of those keys.
847
+ # Issue #1120 — plus the consumers of any refinement name the edit moved ({#refinement_affected}). Issue #1367 —
848
+ # and every file whose last result holds a `global.*` diagnostic ({#global_write_reporters}).
849
+ def negative_affected(changed, removed, new_fingerprints, new_class_decls, new_constant_decls, summary = nil)
810
850
  appeared_methods = Incremental.appeared_symbols(changed, @symbol_fingerprints, new_fingerprints)
811
- appeared_classes = Incremental.appeared_classes(changed, @class_decls, new_class_decls)
851
+ # Issue #639 — a class DISAPPEARING satisfies the `class:` kind too, now that a resolved bare
852
+ # reference records the edge and not only a failed one; removed files join the diff for it.
853
+ moved_classes = Incremental.changed_class_declarations(
854
+ changed + removed, @class_decls, new_class_decls
855
+ )
812
856
  # Issue #644 — a constant whose PUBLICATION moved satisfies the `constant:` kind, keyed on the same
813
- # last segment the consumer recorded. REMOVED files join the diff here and nowhere else: deleting a
814
- # second declarer restores the precise answer, and no other producer input can see that.
857
+ # last segment the consumer recorded. Removed files join this diff (as they do the class one above):
858
+ # deleting a second declarer restores the precise answer, and no other producer input can see that.
815
859
  moved_constants = Incremental.changed_constant_publications(
816
860
  changed + removed, @constant_decls, new_constant_decls
817
861
  )
818
862
  keys = appeared_methods.map { |symbol| negative_key_for(symbol) }
819
- keys.concat(appeared_classes.map { |klass| "class:#{klass.split('::').last}" })
863
+ keys.concat(moved_classes.map { |klass| "class:#{klass.split('::').last}" })
820
864
  keys.concat(moved_constants.map { |name| "constant:#{name.split('::').last}" })
821
- Incremental.negative_closure(keys, @negative_dependents)
865
+ Incremental.negative_closure(keys, @negative_dependents) | refinement_affected(changed + removed, summary) |
866
+ global_write_reporters
867
+ end
868
+
869
+ # Issue #1120 — the consumers whose `call.undefined-method` answer read the refinement table for a method
870
+ # name whose refinements moved in this edit. The before-state is each file's seed bundle (the per-file
871
+ # table the last run recorded), the after-state the scan's per-file tables. A refine body is not a class's
872
+ # method, so neither the symbol fingerprints nor the appeared-symbol diff above sees it.
873
+ def refinement_affected(paths, summary)
874
+ after = (summary && summary[:refinements]) || {}
875
+ before = paths.to_h { |path| [path, @seed_bundles.dig(path, :refinements)] }
876
+ names = Incremental.changed_refinement_names(paths, before, after)
877
+ return Set.new if names.empty?
878
+
879
+ Incremental.negative_closure(names.map { |name| "refinement:#{name}" }, @negative_dependents)
822
880
  end
823
881
 
824
882
  # The qualified class/module names declared in the pre-parsed `index` (shared with
@@ -1013,18 +1071,51 @@ module Rigor
1013
1071
  candidates.reject { |path| stat_fresh?(path) }
1014
1072
  end
1015
1073
 
1016
- # A bound buffer's logical path is never fresh: the bytes to analyse live in the editor's temp file, and
1017
- # the recorded entry describes the file on disk. Re-analysing it when the two happen to agree costs one
1018
- # file; trusting the stat tuple would serve the editor its own stale diagnostics.
1019
1074
  def buffer_path?(path)
1020
1075
  !@buffer.nil? && path == @buffer.logical_path
1021
1076
  end
1022
1077
 
1078
+ # Editor mode (#146) — the bound buffer's logical path is fresh exactly when the EDITOR's bytes still
1079
+ # hash to the digest the snapshot recorded for the file they stand in for. The recorded stat tuple
1080
+ # describes the file on disk, which an edit in the editor never moves, so validating through it serves
1081
+ # the editor its own pre-buffer answers (#960); hashing the buffer makes the substituted file changed
1082
+ # whenever it differs, and keeps an untouched buffer out of the closure. Anything unreadable or
1083
+ # unrecorded reads as changed, the conservative direction the rest of this tier takes.
1084
+ def buffer_fresh?(path)
1085
+ recorded = Cache::FileDigest.content_digest(@digests[path])
1086
+ return false if recorded.nil?
1087
+
1088
+ Cache::FileDigest.hexdigest(@buffer.resolve(path)) == recorded
1089
+ rescue StandardError
1090
+ false
1091
+ end
1092
+
1093
+ # Editor mode (#146 / #960) — `--instead-of` is spelled the way the editor spells it (absolute, or
1094
+ # `./`-prefixed), while the analysed set is spelled the way the run's path arguments produce. A binding
1095
+ # whose logical path matches no member of that set substitutes nothing at all: every reader keeps
1096
+ # reading the file on disk and the run answers as if no buffer had been passed. Re-spell the binding
1097
+ # onto the set's own member, identified by real path, once the set is known.
1098
+ def rebind_buffer(files)
1099
+ return if @buffer.nil? || files.include?(@buffer.logical_path)
1100
+
1101
+ target = real_path(@buffer.logical_path)
1102
+ return if target.nil?
1103
+
1104
+ match = files.find { |path| real_path(path) == target }
1105
+ @buffer = BufferBinding.new(logical_path: match, physical_path: @buffer.physical_path) if match
1106
+ end
1107
+
1108
+ def real_path(path)
1109
+ File.realpath(path)
1110
+ rescue StandardError
1111
+ nil
1112
+ end
1113
+
1023
1114
  # True when `path`'s recorded stat entry proves it unchanged since the last analysis. Any stat / parse
1024
1115
  # failure (missing entry, unreadable / vanished file) reads as NOT fresh (→ re-analyse), preserving the
1025
1116
  # prior `digest(path) != recorded` "changed" semantics for a file that cannot be validated.
1026
1117
  def stat_fresh?(path)
1027
- return false if buffer_path?(path)
1118
+ return buffer_fresh?(path) if buffer_path?(path)
1028
1119
 
1029
1120
  entry = @digests[path]
1030
1121
  return false if entry.nil?
@@ -79,7 +79,6 @@ module Rigor
79
79
  #
80
80
  # Keeping the opaque decision here, rather than at each cache's call site, means a new consumer cannot
81
81
  # key on `digest` while forgetting that an opaque surface makes it meaningless.
82
- # @return [String, nil]
83
82
  def self.key_digest(registry)
84
83
  result = from_registry(registry)
85
84
  result.opaque? ? nil : result.digest.to_s
@@ -16,13 +16,23 @@ module Rigor
16
16
  # are NOT visible until the owner invalidates the scan — typically via `workspace/didChangeWatchedFiles`.
17
17
  # This is the same trade-off the LSP made when slice 7 cached only the `Environment`; extending the cache
18
18
  # to the pre-pass outputs preserves the contract.
19
+ #
20
+ # `template_units` (#1038) is the one slot a consumer does not adopt verbatim. The rest of the snapshot
21
+ # is reused as it stands until the owner invalidates it; the template index is handed to
22
+ # `TemplateUnits.collect(previous:)`, which re-expands the claimed globs and revalidates each template
23
+ # against the filesystem, so a template edited, added or deleted on disk is seen on the next publish
24
+ # even though the snapshot itself did not move. What the carry saves is the plugin transform — for ERB
25
+ # (#393) an Erubi compile of every view in the project, otherwise paid per keystroke. The index is
26
+ # always built WITHOUT a buffer binding, so the shared snapshot never holds a unit compiled from an
27
+ # editor's in-flight bytes.
19
28
  ProjectScan = Data.define(
20
29
  :plugin_registry,
21
30
  :dependency_source_index,
22
31
  :synthetic_method_index,
23
32
  :project_patched_methods,
24
33
  :plugin_prepare_diagnostics,
25
- :pre_eval_diagnostics
34
+ :pre_eval_diagnostics,
35
+ :template_units
26
36
  )
27
37
  end
28
38
  end