rigortype 0.3.8 → 0.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (447) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/data/capability_roles/capability_roles.rbs +36 -0
  4. data/data/gem_overlay/activesupport/core_ext.rbs +109 -13
  5. data/docs/handbook/02-everyday-types.md +17 -14
  6. data/docs/handbook/03-narrowing.md +37 -4
  7. data/docs/handbook/05-methods-and-blocks.md +1 -1
  8. data/docs/handbook/06-classes.md +2 -2
  9. data/docs/handbook/07-rbs-and-extended.md +14 -1
  10. data/docs/handbook/11-sig-gen.md +72 -1
  11. data/docs/handbook/README.md +1 -1
  12. data/docs/handbook/appendix-elixir.md +2 -2
  13. data/docs/handbook/appendix-go.md +2 -2
  14. data/docs/handbook/appendix-java-csharp.md +2 -2
  15. data/docs/handbook/appendix-mypy.md +2 -2
  16. data/docs/handbook/appendix-phpstan.md +1 -1
  17. data/docs/handbook/appendix-rust.md +1 -1
  18. data/docs/handbook/appendix-type-theory.md +4 -4
  19. data/docs/handbook/appendix-typescript.md +1 -1
  20. data/docs/llms.txt +2 -0
  21. data/docs/manual/02-cli-reference.md +32 -4
  22. data/docs/manual/03-configuration.md +11 -0
  23. data/docs/manual/04-diagnostics.md +11 -0
  24. data/docs/manual/07-plugins.md +15 -5
  25. data/docs/manual/08-skills.md +23 -2
  26. data/docs/manual/11-ci.md +9 -0
  27. data/docs/manual/15-type-protection-coverage.md +8 -0
  28. data/docs/manual/16-rbs-extended-annotations.md +6 -1
  29. data/docs/manual/18-removing-dead-code.md +10 -8
  30. data/docs/manual/plugins/rigor-actionpack.md +18 -0
  31. data/docs/manual/plugins/rigor-activerecord.md +28 -0
  32. data/docs/manual/plugins/rigor-activesupport-core-ext.md +32 -2
  33. data/docs/manual/plugins/rigor-rbs-inline.md +42 -1
  34. data/lib/rigor/analysis/baseline.rb +2 -2
  35. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +1 -1
  36. data/lib/rigor/analysis/check_rules/dead_version_guard_arms.rb +1 -5
  37. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +10 -2
  38. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +1 -1
  39. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +15 -6
  40. data/lib/rigor/analysis/check_rules/rule_ids.rb +1 -1
  41. data/lib/rigor/analysis/check_rules/rule_walk.rb +39 -4
  42. data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +1 -1
  43. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +1 -1
  44. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
  45. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +0 -1
  46. data/lib/rigor/analysis/check_rules.rb +200 -32
  47. data/lib/rigor/analysis/crash_signature.rb +8 -24
  48. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +1 -1
  49. data/lib/rigor/analysis/dependency_source_inference/builder.rb +0 -2
  50. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +0 -2
  51. data/lib/rigor/analysis/dependency_source_inference/index.rb +5 -5
  52. data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +1 -2
  53. data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
  54. data/lib/rigor/analysis/effects_cache_probe.rb +3 -4
  55. data/lib/rigor/analysis/erb_template_detector.rb +1 -2
  56. data/lib/rigor/analysis/fact_store.rb +6 -1
  57. data/lib/rigor/analysis/incremental.rb +16 -0
  58. data/lib/rigor/analysis/incremental_session.rb +81 -22
  59. data/lib/rigor/analysis/plugin_fact_fingerprint.rb +0 -1
  60. data/lib/rigor/analysis/reachability/graph.rb +3 -5
  61. data/lib/rigor/analysis/reachability/plugin_roots.rb +6 -5
  62. data/lib/rigor/analysis/reachability/project_files.rb +2 -2
  63. data/lib/rigor/analysis/reachability/scan.rb +5 -5
  64. data/lib/rigor/analysis/reachability/scan_cache.rb +2 -2
  65. data/lib/rigor/analysis/reachability/signature_scan.rb +2 -2
  66. data/lib/rigor/analysis/result.rb +1 -3
  67. data/lib/rigor/analysis/rule_catalog.rb +4 -1
  68. data/lib/rigor/analysis/run_cache_key.rb +1 -1
  69. data/lib/rigor/analysis/run_cache_probe.rb +3 -5
  70. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +6 -8
  71. data/lib/rigor/analysis/runner/declaration_position.rb +1 -2
  72. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +177 -26
  73. data/lib/rigor/analysis/runner/effect_annotation_residual_pass.rb +2 -3
  74. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +9 -10
  75. data/lib/rigor/analysis/runner/pool_coordinator.rb +256 -88
  76. data/lib/rigor/analysis/runner/project_pre_passes.rb +12 -6
  77. data/lib/rigor/analysis/runner/run_snapshots.rb +6 -1
  78. data/lib/rigor/analysis/runner.rb +133 -35
  79. data/lib/rigor/analysis/worker_session.rb +9 -10
  80. data/lib/rigor/bleeding_edge.rb +12 -24
  81. data/lib/rigor/builtins/hkt_builtins.rb +2 -2
  82. data/lib/rigor/builtins/imported_refinements.rb +155 -21
  83. data/lib/rigor/builtins/predefined_constant_refinements.rb +5 -7
  84. data/lib/rigor/builtins/regex_refinement.rb +4 -4
  85. data/lib/rigor/builtins/static_return_refinements.rb +5 -6
  86. data/lib/rigor/cache/annotation_location.rb +2 -4
  87. data/lib/rigor/cache/descriptor.rb +58 -20
  88. data/lib/rigor/cache/engine_source.rb +4 -4
  89. data/lib/rigor/cache/file_digest.rb +9 -1
  90. data/lib/rigor/cache/incremental_snapshot.rb +40 -6
  91. data/lib/rigor/cache/rbs_class_ancestor_table.rb +0 -3
  92. data/lib/rigor/cache/rbs_class_type_param_names.rb +0 -3
  93. data/lib/rigor/cache/rbs_constant_table.rb +0 -3
  94. data/lib/rigor/cache/rbs_descriptor.rb +79 -11
  95. data/lib/rigor/cache/rbs_environment.rb +8 -4
  96. data/lib/rigor/cache/rbs_known_class_names.rb +0 -3
  97. data/lib/rigor/cache/store.rb +77 -32
  98. data/lib/rigor/cli/annotate_command.rb +5 -6
  99. data/lib/rigor/cli/check_command.rb +57 -10
  100. data/lib/rigor/cli/check_invocation.rb +6 -11
  101. data/lib/rigor/cli/check_runner_factory.rb +1 -5
  102. data/lib/rigor/cli/coverage_command.rb +1 -1
  103. data/lib/rigor/cli/coverage_mutation.rb +12 -3
  104. data/lib/rigor/cli/coverage_scan.rb +1 -5
  105. data/lib/rigor/cli/diff_command.rb +1 -1
  106. data/lib/rigor/cli/doc_links.rb +3 -3
  107. data/lib/rigor/cli/docs_command.rb +2 -2
  108. data/lib/rigor/cli/doctor_command.rb +85 -38
  109. data/lib/rigor/cli/effects_command.rb +2 -2
  110. data/lib/rigor/cli/effects_diff_renderer.rb +3 -3
  111. data/lib/rigor/cli/effects_snapshot_command.rb +1 -1
  112. data/lib/rigor/cli/explain_command.rb +34 -1
  113. data/lib/rigor/cli/fused_protection_report.rb +7 -1
  114. data/lib/rigor/cli/lsp_command.rb +1 -1
  115. data/lib/rigor/cli/mcp_command.rb +1 -1
  116. data/lib/rigor/cli/measurement_integrity_warning.rb +4 -5
  117. data/lib/rigor/cli/mutation_fork_scan.rb +6 -6
  118. data/lib/rigor/cli/mutation_protection_report.rb +7 -1
  119. data/lib/rigor/cli/plugin_command.rb +1 -1
  120. data/lib/rigor/cli/plugins_command.rb +2 -1
  121. data/lib/rigor/cli/prism_colorizer.rb +2 -2
  122. data/lib/rigor/cli/protection_fork_scan.rb +6 -6
  123. data/lib/rigor/cli/show_bleedingedge_command.rb +1 -1
  124. data/lib/rigor/cli/sig_gen_command.rb +2 -2
  125. data/lib/rigor/cli/skill_command.rb +1 -1
  126. data/lib/rigor/cli/skill_deep_probe.rb +4 -4
  127. data/lib/rigor/cli/skill_describe.rb +36 -35
  128. data/lib/rigor/cli/trace_command.rb +4 -4
  129. data/lib/rigor/cli/trace_renderer.rb +5 -6
  130. data/lib/rigor/cli/triage_command.rb +1 -1
  131. data/lib/rigor/cli/type_of_command.rb +10 -6
  132. data/lib/rigor/cli/type_scan_command.rb +4 -4
  133. data/lib/rigor/cli/unused_command.rb +11 -3
  134. data/lib/rigor/cli/upgrade_command.rb +1 -1
  135. data/lib/rigor/cli.rb +68 -6
  136. data/lib/rigor/config_audit.rb +30 -5
  137. data/lib/rigor/configuration/severity_profile.rb +6 -6
  138. data/lib/rigor/configuration.rb +43 -4
  139. data/lib/rigor/effects/attribution.rb +1 -3
  140. data/lib/rigor/effects/config_envelopes.rb +6 -8
  141. data/lib/rigor/effects/definition_lines.rb +25 -6
  142. data/lib/rigor/effects/effect_table.rb +0 -1
  143. data/lib/rigor/effects/entry_points.rb +0 -2
  144. data/lib/rigor/effects/envelope_check.rb +8 -8
  145. data/lib/rigor/effects/envelope_index.rb +5 -8
  146. data/lib/rigor/effects/framework_units.rb +4 -6
  147. data/lib/rigor/effects/identity.rb +4 -6
  148. data/lib/rigor/effects/inline_anchor.rb +7 -7
  149. data/lib/rigor/effects/label_intent.rb +3 -4
  150. data/lib/rigor/effects/liskov_check.rb +8 -8
  151. data/lib/rigor/effects/method_key.rb +1 -1
  152. data/lib/rigor/effects/plugin_facts.rb +4 -6
  153. data/lib/rigor/effects/propagator.rb +2 -3
  154. data/lib/rigor/effects/scanner.rb +1 -1
  155. data/lib/rigor/effects/signature_sources.rb +3 -5
  156. data/lib/rigor/effects/snapshot.rb +10 -10
  157. data/lib/rigor/effects/snapshot_diff.rb +1 -1
  158. data/lib/rigor/effects/unit_scan.rb +10 -10
  159. data/lib/rigor/effects/unknown_label_check.rb +3 -8
  160. data/lib/rigor/effects/unknown_label_report.rb +3 -4
  161. data/lib/rigor/environment/bundle_sig_discovery.rb +6 -6
  162. data/lib/rigor/environment/class_registry.rb +3 -1
  163. data/lib/rigor/environment/failure_slot.rb +2 -2
  164. data/lib/rigor/environment/installed_gem_set.rb +85 -0
  165. data/lib/rigor/environment/lockfile_resolver.rb +51 -4
  166. data/lib/rigor/environment/missing_gem_constant_index.rb +4 -4
  167. data/lib/rigor/environment/rbs_collection_discovery.rb +5 -5
  168. data/lib/rigor/environment/rbs_coverage_report.rb +5 -5
  169. data/lib/rigor/environment/rbs_hierarchy.rb +3 -1
  170. data/lib/rigor/environment/rbs_loader.rb +388 -59
  171. data/lib/rigor/environment.rb +105 -20
  172. data/lib/rigor/flow_contribution/merger.rb +0 -2
  173. data/lib/rigor/flow_contribution.rb +11 -13
  174. data/lib/rigor/inference/acceptance.rb +153 -8
  175. data/lib/rigor/inference/block_parameter_binder.rb +2 -3
  176. data/lib/rigor/inference/body_fixpoint.rb +5 -5
  177. data/lib/rigor/inference/budget_trace.rb +1 -5
  178. data/lib/rigor/inference/captured_locals.rb +2 -3
  179. data/lib/rigor/inference/closure_escape_analyzer.rb +2 -4
  180. data/lib/rigor/inference/coverage_scanner.rb +4 -6
  181. data/lib/rigor/inference/dynamic_origin.rb +1 -1
  182. data/lib/rigor/inference/element_read_widening.rb +168 -0
  183. data/lib/rigor/inference/expression_typer.rb +763 -143
  184. data/lib/rigor/inference/fork_map.rb +5 -7
  185. data/lib/rigor/inference/hkt_reducer.rb +2 -3
  186. data/lib/rigor/inference/hkt_registry.rb +4 -7
  187. data/lib/rigor/inference/index_write_widening.rb +1 -5
  188. data/lib/rigor/inference/macro_block_self_type.rb +1 -4
  189. data/lib/rigor/inference/method_dispatcher/block_folding.rb +2 -9
  190. data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +1 -1
  191. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +305 -19
  192. data/lib/rigor/inference/method_dispatcher/data_folding.rb +1 -1
  193. data/lib/rigor/inference/method_dispatcher/file_folding.rb +1 -1
  194. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +2 -2
  195. data/lib/rigor/inference/method_dispatcher/json_folding.rb +1 -1
  196. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -1
  197. data/lib/rigor/inference/method_dispatcher/math_folding.rb +41 -3
  198. data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -5
  199. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +61 -47
  200. data/lib/rigor/inference/method_dispatcher/random_folding.rb +82 -0
  201. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +165 -31
  202. data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +25 -4
  203. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +2 -8
  204. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +1 -1
  205. data/lib/rigor/inference/method_dispatcher/set_folding.rb +1 -1
  206. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +1 -1
  207. data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +1 -1
  208. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +1 -2
  209. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +4 -5
  210. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +85 -4
  211. data/lib/rigor/inference/method_dispatcher/time_folding.rb +1 -1
  212. data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +1 -2
  213. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  214. data/lib/rigor/inference/method_dispatcher.rb +12 -13
  215. data/lib/rigor/inference/method_parameter_binder.rb +4 -6
  216. data/lib/rigor/inference/multi_target_binder.rb +1 -3
  217. data/lib/rigor/inference/mutation_rejoin.rb +168 -0
  218. data/lib/rigor/inference/mutation_widening.rb +100 -51
  219. data/lib/rigor/inference/narrowing.rb +299 -46
  220. data/lib/rigor/inference/optimistic_origin.rb +1 -7
  221. data/lib/rigor/inference/origin_lookup.rb +2 -3
  222. data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
  223. data/lib/rigor/inference/pre_eval_constants.rb +6 -5
  224. data/lib/rigor/inference/precision_scanner.rb +3 -4
  225. data/lib/rigor/inference/project_patched_methods.rb +2 -2
  226. data/lib/rigor/inference/project_patched_scanner.rb +3 -3
  227. data/lib/rigor/inference/protection_scanner.rb +1 -2
  228. data/lib/rigor/inference/range_constant.rb +57 -0
  229. data/lib/rigor/inference/rbs_type_translator.rb +49 -11
  230. data/lib/rigor/inference/receiver_alias.rb +3 -3
  231. data/lib/rigor/inference/refinement_mutation.rb +72 -0
  232. data/lib/rigor/inference/scope_indexer.rb +688 -151
  233. data/lib/rigor/inference/statement_evaluator.rb +221 -50
  234. data/lib/rigor/inference/string_mutation.rb +60 -0
  235. data/lib/rigor/inference/struct_fold_safety.rb +6 -7
  236. data/lib/rigor/inference/synthetic_method_index.rb +1 -2
  237. data/lib/rigor/inference/synthetic_method_scanner.rb +4 -6
  238. data/lib/rigor/inference/version_guard.rb +28 -21
  239. data/lib/rigor/inference/void_origin.rb +3 -3
  240. data/lib/rigor/inference/void_tail_summary.rb +2 -4
  241. data/lib/rigor/language_server/buffer_table.rb +4 -4
  242. data/lib/rigor/language_server/completion_provider.rb +1 -1
  243. data/lib/rigor/language_server/debouncer.rb +1 -1
  244. data/lib/rigor/language_server/diagnostic_publisher.rb +4 -4
  245. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  246. data/lib/rigor/language_server/folding_range_provider.rb +1 -1
  247. data/lib/rigor/language_server/hover_provider.rb +1 -1
  248. data/lib/rigor/language_server/hover_renderer.rb +2 -2
  249. data/lib/rigor/language_server/incremental_sync.rb +6 -6
  250. data/lib/rigor/language_server/project_context.rb +2 -5
  251. data/lib/rigor/language_server/publish_batcher.rb +2 -2
  252. data/lib/rigor/language_server/selection_range_provider.rb +2 -2
  253. data/lib/rigor/language_server/server.rb +9 -9
  254. data/lib/rigor/language_server/signature_help_provider.rb +1 -1
  255. data/lib/rigor/language_server/uri.rb +1 -1
  256. data/lib/rigor/plugin/base.rb +90 -21
  257. data/lib/rigor/plugin/bundled_catalog.rb +167 -0
  258. data/lib/rigor/plugin/effect_attribution.rb +7 -7
  259. data/lib/rigor/plugin/effect_entry_points.rb +3 -3
  260. data/lib/rigor/plugin/fact_store.rb +6 -6
  261. data/lib/rigor/plugin/io_boundary.rb +93 -8
  262. data/lib/rigor/plugin/isolation.rb +29 -6
  263. data/lib/rigor/plugin/loader.rb +31 -12
  264. data/lib/rigor/plugin/macro/heredoc_template.rb +1 -1
  265. data/lib/rigor/plugin/macro/trait_registry.rb +1 -1
  266. data/lib/rigor/plugin/manifest.rb +23 -1
  267. data/lib/rigor/plugin/registry.rb +42 -12
  268. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +12 -1
  269. data/lib/rigor/plugin/trust_policy.rb +2 -4
  270. data/lib/rigor/plugin/type_node_resolver.rb +3 -3
  271. data/lib/rigor/plugin_gap_advisory.rb +96 -0
  272. data/lib/rigor/project_environment.rb +138 -0
  273. data/lib/rigor/protection/analysis_guard.rb +93 -14
  274. data/lib/rigor/protection/closure_kill_oracle.rb +67 -23
  275. data/lib/rigor/protection/dependency_closure.rb +5 -8
  276. data/lib/rigor/protection/diagnostic_oracle.rb +2 -2
  277. data/lib/rigor/protection/discovery_seed.rb +11 -11
  278. data/lib/rigor/protection/kill_signature.rb +2 -4
  279. data/lib/rigor/protection/measurement_integrity.rb +1 -3
  280. data/lib/rigor/protection/mutation_cache.rb +6 -9
  281. data/lib/rigor/protection/mutation_scanner.rb +12 -15
  282. data/lib/rigor/protection/mutator.rb +2 -1
  283. data/lib/rigor/protection/test_suite_oracle.rb +5 -5
  284. data/lib/rigor/rbs_extended/conformance_checker.rb +4 -3
  285. data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
  286. data/lib/rigor/rbs_extended/reporter.rb +36 -10
  287. data/lib/rigor/rbs_extended.rb +61 -21
  288. data/lib/rigor/reflection/constant_path.rb +126 -0
  289. data/lib/rigor/reflection.rb +26 -10
  290. data/lib/rigor/runtime/jit.rb +6 -8
  291. data/lib/rigor/scope/discovery_index.rb +30 -0
  292. data/lib/rigor/scope.rb +203 -4
  293. data/lib/rigor/sig_gen/generator.rb +115 -22
  294. data/lib/rigor/sig_gen/layout_index.rb +3 -4
  295. data/lib/rigor/sig_gen/meta_class_shape.rb +3 -5
  296. data/lib/rigor/sig_gen/observation_collector.rb +17 -15
  297. data/lib/rigor/sig_gen/path_mapper.rb +4 -6
  298. data/lib/rigor/sig_gen/rbs_validity.rb +4 -4
  299. data/lib/rigor/sig_gen/renderer.rb +4 -5
  300. data/lib/rigor/sig_gen/skip_reason_catalog.rb +111 -0
  301. data/lib/rigor/sig_gen/superclass_spelling.rb +27 -0
  302. data/lib/rigor/sig_gen/type_elaborator.rb +1 -3
  303. data/lib/rigor/sig_gen/writer.rb +23 -6
  304. data/lib/rigor/signature_path_audit.rb +153 -6
  305. data/lib/rigor/source/literals.rb +0 -23
  306. data/lib/rigor/source/node_children.rb +0 -2
  307. data/lib/rigor/source/node_locator.rb +5 -11
  308. data/lib/rigor/source/node_walker.rb +2 -5
  309. data/lib/rigor/triage/catalogue.rb +1 -3
  310. data/lib/rigor/triage.rb +3 -5
  311. data/lib/rigor/type/accepts_result.rb +24 -6
  312. data/lib/rigor/type/combinator.rb +68 -5
  313. data/lib/rigor/type/data_class.rb +2 -2
  314. data/lib/rigor/type/data_instance.rb +4 -4
  315. data/lib/rigor/type/float_range.rb +128 -0
  316. data/lib/rigor/type/hash_shape.rb +5 -5
  317. data/lib/rigor/type/integer_range.rb +13 -8
  318. data/lib/rigor/type/nominal.rb +8 -2
  319. data/lib/rigor/type/refined.rb +3 -3
  320. data/lib/rigor/type/struct_class.rb +3 -3
  321. data/lib/rigor/type/struct_instance.rb +4 -4
  322. data/lib/rigor/type.rb +1 -0
  323. data/lib/rigor/type_node/generic.rb +1 -1
  324. data/lib/rigor/type_node/range_literal.rb +25 -0
  325. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  326. data/lib/rigor/type_node.rb +1 -0
  327. data/lib/rigor/version.rb +1 -1
  328. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +0 -4
  329. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +0 -1
  330. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +0 -2
  331. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -0
  332. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +0 -4
  333. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +3 -5
  334. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +1 -4
  335. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +1 -0
  336. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +33 -30
  337. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +0 -1
  338. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -3
  339. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +28 -2
  340. data/plugins/rigor-actionpack/sig/action_controller.rbs +71 -0
  341. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +0 -4
  342. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
  343. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +0 -1
  344. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +0 -2
  345. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +1 -1
  346. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +1 -0
  347. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +3 -4
  348. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +1 -2
  349. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +1 -2
  350. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +42 -7
  351. data/plugins/rigor-activerecord/sig/active_record/framework.rbs +123 -0
  352. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +1 -0
  353. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +9 -2
  354. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +119 -14
  355. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +1 -0
  356. data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +1 -0
  357. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +9 -7
  358. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +7 -3
  359. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +1 -0
  360. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +8 -6
  361. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +11 -7
  362. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -11
  363. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +9 -4
  364. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +1 -0
  365. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +0 -5
  366. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +0 -1
  367. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +0 -1
  368. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +8 -1
  369. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +36 -4
  370. data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +1 -0
  371. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +8 -6
  372. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +6 -3
  373. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +1 -0
  374. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -1
  375. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +0 -3
  376. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +1 -0
  377. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +0 -5
  378. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -2
  379. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +0 -1
  380. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +0 -1
  381. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +1 -0
  382. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +0 -12
  383. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +1 -3
  384. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +0 -1
  385. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +1 -0
  386. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/acronyms.rb +5 -5
  387. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -10
  388. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +3 -3
  389. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +2 -2
  390. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +2 -2
  391. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +2 -2
  392. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +9 -10
  393. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +2 -3
  394. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -0
  395. data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +1 -0
  396. data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +13 -1
  397. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +128 -3
  398. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +0 -3
  399. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +0 -3
  400. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -5
  401. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +2 -3
  402. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +1 -0
  403. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +0 -3
  404. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +1 -0
  405. data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +1 -0
  406. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +1 -9
  407. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +1 -0
  408. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +0 -4
  409. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/effects.rb +1 -1
  410. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +1 -1
  411. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +0 -1
  412. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +0 -2
  413. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +1 -0
  414. data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -0
  415. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +2 -5
  416. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +1 -4
  417. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +3 -9
  418. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +4 -4
  419. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +1 -2
  420. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +4 -5
  421. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +7 -5
  422. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +5 -5
  423. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +1 -0
  424. data/sig/rigor/analysis/reachability/scan_cache.rbs +12 -0
  425. data/sig/rigor/cache.rbs +2 -3
  426. data/sig/rigor/environment.rbs +5 -0
  427. data/sig/rigor/inference.rbs +3 -10
  428. data/sig/rigor/plugin/io_boundary.rbs +4 -0
  429. data/sig/rigor/plugin/manifest.rbs +2 -1
  430. data/sig/rigor/reflection.rbs +3 -1
  431. data/sig/rigor/scope.rbs +24 -3
  432. data/sig/rigor/sig_gen/skip_reason_catalog.rbs +14 -0
  433. data/sig/rigor/type.rbs +27 -1
  434. data/sig/rigor.rbs +1 -0
  435. data/skills/rigor-ask/SKILL.md +8 -6
  436. data/skills/rigor-plugin-author/references/02-walker-and-types.md +13 -2
  437. data/skills/rigor-project-init/SKILL.md +6 -3
  438. data/skills/rigor-project-init/references/02-configure.md +10 -6
  439. data/skills/rigor-project-init/references/06-agent-contract.md +67 -0
  440. data/skills/rigor-type-oracle/SKILL.md +228 -0
  441. data/skills/rigor-type-oracle/references/01-oracle-commands.md +261 -0
  442. data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +52 -0
  443. data/skills/rigor-type-oracle/references/03-gap-protocol.md +128 -0
  444. data/skills/rigor-unused-adjudicate/SKILL.md +8 -5
  445. metadata +26 -3
  446. data/lib/rigor/cli/probe_environment.rb +0 -85
  447. data/sig/rigor/inference/builtins/numeric_catalog.rbs +0 -3
@@ -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,14 @@ 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.
21
24
  def self.local(name)
22
- new(kind: :local, name: name.to_sym)
25
+ name = name.to_sym
26
+ @local_targets[name] ||= new(kind: :local, name: name)
23
27
  end
28
+ @local_targets = {}
24
29
 
25
30
  def initialize(kind:, name:)
26
31
  super(kind: kind.to_sym, name: name)
@@ -120,6 +120,22 @@ module Rigor
120
120
  appeared.freeze
121
121
  end
122
122
 
123
+ # Issue #639 — the SYMMETRIC reading of {appeared_classes}: the names whose declaration in a file
124
+ # appeared OR VANISHED. Appearance alone answered the miss side, which was the only side recording a
125
+ # `class:` edge; a bare reference that RESOLVED now records one too, and what invalidates it is the
126
+ # declaration going away. `paths` MUST therefore include REMOVED files — a removed file has no
127
+ # after-state, so its whole before-set vanished — which is the same reason
128
+ # {changed_constant_publications} diffs over `changed + removed`.
129
+ def changed_class_declarations(paths, decls_before, decls_after)
130
+ moved = Set.new
131
+ paths.each do |path|
132
+ before = decls_before[path] || Set.new
133
+ after = decls_after[path] || Set.new
134
+ moved.merge(after ^ before)
135
+ end
136
+ moved.freeze
137
+ end
138
+
123
139
  # Issue #644 — the qualified constant names whose PUBLICATION could have moved in this edit: the names
124
140
  # whose per-file census descriptor differs between the before- and after-state of any file in `paths`.
125
141
  # 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
@@ -174,6 +174,7 @@ module Rigor
174
174
  @effects_identity = current_effects_identity
175
175
  @cache = per_file(runner.per_file_diagnostics)
176
176
  @digests = @analyzed.to_h { |path| [path, pack_digest(path)] }
177
+ @run_level_rows = runner.run_level_rows
177
178
  diagnostics
178
179
  end
179
180
 
@@ -197,7 +198,8 @@ module Rigor
197
198
  # The freshly collected table is handed to the runner so the run seeds from the SAME table the diff
198
199
  # was decided on (and the collector runs once per recheck, not twice).
199
200
  runner = build_runner(analyze_only: analyze_set, record_dependencies: true,
200
- param_inferred_types: fresh_params)
201
+ param_inferred_types: fresh_params,
202
+ restored_run_level_rows: @run_level_rows)
201
203
  fresh = run_runner(runner).diagnostics
202
204
  @last_runner = runner # ADR-88 WD1 — the post-hoc fact-surface fingerprint reads this prepared registry.
203
205
  reused = (current & previous) - affected.to_a
@@ -407,10 +409,13 @@ module Rigor
407
409
  end
408
410
 
409
411
  # The current project file set (cheap directory expansion, no analysis), used to detect files added /
410
- # removed since the last run.
412
+ # removed since the last run. Also where an editor buffer's binding is re-spelled onto the set, since
413
+ # this is where the run first learns how it spells its own members.
411
414
  def current_files
412
415
  runner = build_runner
413
- @paths ? runner.analysis_file_set(@paths) : runner.analysis_file_set
416
+ files = @paths ? runner.analysis_file_set(@paths) : runner.analysis_file_set
417
+ rebind_buffer(files)
418
+ files
414
419
  end
415
420
 
416
421
  # Verification engine (the `--verify-incremental` gate): with NO source edit, re-analyze `subset` fresh
@@ -422,7 +427,8 @@ module Rigor
422
427
  affected = subset.to_set
423
428
  # ADR-67 WD6c lift — seed the subset run from the baseline's own table so the verification engine
424
429
  # 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)
430
+ runner = build_runner(analyze_only: affected, param_inferred_types: @param_table,
431
+ restored_run_level_rows: @run_level_rows)
426
432
  fresh = run_runner(runner).diagnostics
427
433
  reused = @analyzed - affected.to_a
428
434
  fresh + reused.flat_map { |path| @cache[path] || [] }
@@ -559,6 +565,9 @@ module Rigor
559
565
  restore_structural_declarations(payload)
560
566
  @return_summaries = payload.return_summaries || {}
561
567
  @param_table = payload.param_table || {} # ADR-67 WD6c lift — the seeds the cache was built under.
568
+ # Issues #796 / #794 — absent (nil) in a snapshot written before schema 22 is impossible (the SCHEMA
569
+ # gate rejects one), so nil here means the writing run genuinely had nothing to say.
570
+ @run_level_rows = payload.run_level_rows
562
571
  # ADR-103 WD13 / #382 — the effects sidecar rides its own identity, so a snapshot whose summaries
563
572
  # were collected under a different vocabulary / catalogue / `effects:` block restores as EMPTY
564
573
  # rather than as stale rows. {#run_incremental} turns that emptiness into a full baseline, because
@@ -592,7 +601,8 @@ module Rigor
592
601
  return_summaries: marshal_safe_return_summaries,
593
602
  param_table: marshal_safe_param_table,
594
603
  effect_collections: marshal_safe_effect_collections,
595
- effects_identity: @effects_identity
604
+ effects_identity: @effects_identity,
605
+ run_level_rows: @run_level_rows
596
606
  )
597
607
  end
598
608
 
@@ -606,6 +616,15 @@ module Rigor
606
616
  @effects_identity = nil
607
617
  end
608
618
 
619
+ # The state this session carries ACROSS processes in the snapshot rather than deriving each run: the
620
+ # effects sidecar above, and (issues #796 / #794) the run-level rows the last FULL run answered — the
621
+ # `definition-build-failed` set and the HKT-scan outcome, nil until a full run has produced them, and
622
+ # handed to every narrowed run this session builds, none of which can derive them from its own files.
623
+ def reset_carried_state
624
+ reset_effect_state
625
+ @run_level_rows = nil
626
+ end
627
+
609
628
  # Whether the effects half of a restored snapshot permits reuse. Collection off is vacuously true —
610
629
  # the session keeps no collections, writes none, and the pre-#382 reuse decision stands unchanged.
611
630
  def effects_reuse_permitted?(payload)
@@ -696,6 +715,9 @@ module Rigor
696
715
  @digests[path] = pack_digest(path)
697
716
  end
698
717
  absorb_dependency_graph(runner)
718
+ # Issues #796 / #794 — the recheck's own snapshots already carry the replayed rows folded together
719
+ # with anything its closure demanded for itself, so this is the same union the next run replays.
720
+ @run_level_rows = runner.run_level_rows
699
721
  refresh_return_summaries(runner, analyze_set)
700
722
  refresh_effect_collections(runner, analyze_set)
701
723
  end
@@ -803,20 +825,24 @@ module Rigor
803
825
  end
804
826
 
805
827
  # 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.
828
+ # a prior missed lookup, and (issue #639) because a class DECLARATION appeared or vanished. Maps each
829
+ # appeared `"ClassName#method"` to the negative-dependency key it would satisfy (`toplevel:foo` for a
830
+ # top-level def, `method:C#m` otherwise), then unions the recorded negative-dependents of those keys.
809
831
  def negative_affected(changed, removed, new_fingerprints, new_class_decls, new_constant_decls)
810
832
  appeared_methods = Incremental.appeared_symbols(changed, @symbol_fingerprints, new_fingerprints)
811
- appeared_classes = Incremental.appeared_classes(changed, @class_decls, new_class_decls)
833
+ # Issue #639 — a class DISAPPEARING satisfies the `class:` kind too, now that a resolved bare
834
+ # reference records the edge and not only a failed one; removed files join the diff for it.
835
+ moved_classes = Incremental.changed_class_declarations(
836
+ changed + removed, @class_decls, new_class_decls
837
+ )
812
838
  # 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.
839
+ # last segment the consumer recorded. Removed files join this diff (as they do the class one above):
840
+ # deleting a second declarer restores the precise answer, and no other producer input can see that.
815
841
  moved_constants = Incremental.changed_constant_publications(
816
842
  changed + removed, @constant_decls, new_constant_decls
817
843
  )
818
844
  keys = appeared_methods.map { |symbol| negative_key_for(symbol) }
819
- keys.concat(appeared_classes.map { |klass| "class:#{klass.split('::').last}" })
845
+ keys.concat(moved_classes.map { |klass| "class:#{klass.split('::').last}" })
820
846
  keys.concat(moved_constants.map { |name| "constant:#{name.split('::').last}" })
821
847
  Incremental.negative_closure(keys, @negative_dependents)
822
848
  end
@@ -1013,18 +1039,51 @@ module Rigor
1013
1039
  candidates.reject { |path| stat_fresh?(path) }
1014
1040
  end
1015
1041
 
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
1042
  def buffer_path?(path)
1020
1043
  !@buffer.nil? && path == @buffer.logical_path
1021
1044
  end
1022
1045
 
1046
+ # Editor mode (#146) — the bound buffer's logical path is fresh exactly when the EDITOR's bytes still
1047
+ # hash to the digest the snapshot recorded for the file they stand in for. The recorded stat tuple
1048
+ # describes the file on disk, which an edit in the editor never moves, so validating through it serves
1049
+ # the editor its own pre-buffer answers (#960); hashing the buffer makes the substituted file changed
1050
+ # whenever it differs, and keeps an untouched buffer out of the closure. Anything unreadable or
1051
+ # unrecorded reads as changed, the conservative direction the rest of this tier takes.
1052
+ def buffer_fresh?(path)
1053
+ recorded = Cache::FileDigest.content_digest(@digests[path])
1054
+ return false if recorded.nil?
1055
+
1056
+ Cache::FileDigest.hexdigest(@buffer.resolve(path)) == recorded
1057
+ rescue StandardError
1058
+ false
1059
+ end
1060
+
1061
+ # Editor mode (#146 / #960) — `--instead-of` is spelled the way the editor spells it (absolute, or
1062
+ # `./`-prefixed), while the analysed set is spelled the way the run's path arguments produce. A binding
1063
+ # whose logical path matches no member of that set substitutes nothing at all: every reader keeps
1064
+ # reading the file on disk and the run answers as if no buffer had been passed. Re-spell the binding
1065
+ # onto the set's own member, identified by real path, once the set is known.
1066
+ def rebind_buffer(files)
1067
+ return if @buffer.nil? || files.include?(@buffer.logical_path)
1068
+
1069
+ target = real_path(@buffer.logical_path)
1070
+ return if target.nil?
1071
+
1072
+ match = files.find { |path| real_path(path) == target }
1073
+ @buffer = BufferBinding.new(logical_path: match, physical_path: @buffer.physical_path) if match
1074
+ end
1075
+
1076
+ def real_path(path)
1077
+ File.realpath(path)
1078
+ rescue StandardError
1079
+ nil
1080
+ end
1081
+
1023
1082
  # True when `path`'s recorded stat entry proves it unchanged since the last analysis. Any stat / parse
1024
1083
  # failure (missing entry, unreadable / vanished file) reads as NOT fresh (→ re-analyse), preserving the
1025
1084
  # prior `digest(path) != recorded` "changed" semantics for a file that cannot be validated.
1026
1085
  def stat_fresh?(path)
1027
- return false if buffer_path?(path)
1086
+ return buffer_fresh?(path) if buffer_path?(path)
1028
1087
 
1029
1088
  entry = @digests[path]
1030
1089
  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
@@ -29,13 +29,11 @@ module Rigor
29
29
  # bucket on trust.
30
30
  Undecidable = Data.define(:fqn, :path, :line, :reason)
31
31
 
32
- # @param declarations [Array<Scan::Declaration>]
33
- # @param references [Array<Scan::Reference>]
34
- # @param root_fqns [Enumerable<String>] declarations that are entry points regardless of who references
32
+ # @param root_fqns — declarations that are entry points regardless of who references
35
33
  # them (config-declared globs in this slice; plugin-supplied roots are #349).
36
- # @param foreign [#call] predicate answering "is this FQN owned by something outside the project?" —
34
+ # @param foreign predicate answering "is this FQN owned by something outside the project?" —
37
35
  # a reopened gem or stdlib class must never be a candidate (WD6). Defaults to "nothing is foreign".
38
- # @param dynamic_uses [Array<Scan::DynamicUse>] sites where a constant is reached by name at runtime.
36
+ # @param dynamic_uses sites where a constant is reached by name at runtime.
39
37
  # A literal-argument site contributes a real reference; a dynamic one taints a namespace (WD4).
40
38
  def initialize(declarations:, references:, root_fqns: [], dynamic_uses: [], foreign: ->(_fqn) { false })
41
39
  @declarations = declarations
@@ -83,14 +83,15 @@ module Rigor
83
83
  # Loads the project's configured plugins, runs every `#prepare`, and returns the union of every
84
84
  # published `:reachability_roots` and `:reachability_references` fact.
85
85
  #
86
- # @param configuration [Rigor::Configuration] the loaded project configuration.
87
- # @param plugin_requirer [#call] how a plugin gem is brought into the process. The same seam
88
- # `Analysis::Runner` exposes, so a spec can register a plugin class without publishing a gem.
89
- # @param cache_store [Rigor::Cache::Store, nil] when given, each plugin's `#prepare` producers read
86
+ # @param configuration the loaded project configuration.
87
+ # @param plugin_requirer how a plugin gem is brought into the process, called with the gem name. The
88
+ # same seam `Analysis::Runner` exposes, so a spec can register a plugin class without publishing a
89
+ # gem.
90
+ # @param cache_store — when given, each plugin's `#prepare` producers read
90
91
  # and write the same ADR-60 record-and-validate slots they use under `rigor check`, instead of
91
92
  # recomputing from scratch — a routes parse or a factory discovery is a validated cache read on
92
93
  # every invocation after the first. Nil keeps the historical recompute-always behaviour.
93
- # @return [Contribution] sorted and de-duplicated. Empty whenever the project declares no plugins, no
94
+ # @return sorted and de-duplicated. Empty whenever the project declares no plugins, no
94
95
  # plugin contributes anything, or anything at all goes wrong.
95
96
  def collect(configuration:, plugin_requirer: ->(name) { require name }, cache_store: nil)
96
97
  return Contribution.empty if configuration.plugins.empty?
@@ -46,8 +46,8 @@ module Rigor
46
46
  File.fnmatch?(pattern, path, File::FNM_PATHNAME)
47
47
  end
48
48
 
49
- # @param relative_paths [Array<String>] paths relative to `root`.
50
- # @return [Array<String>] those that belong to the project itself.
49
+ # @param relative_paths paths relative to `root`.
50
+ # @return those that belong to the project itself.
51
51
  def own(relative_paths, root)
52
52
  prefixes = submodule_prefixes(root)
53
53
  relative_paths.grep_v(VENDOR_DIRS).reject { |rel| prefixes.any? { |prefix| rel.start_with?(prefix) } }
@@ -70,11 +70,6 @@ module Rigor
70
70
  end
71
71
  end
72
72
 
73
- # @param path [String] the file's path, as the report should render it.
74
- # @param source [String] the file's bytes.
75
- # @param target_ruby [String, nil] Prism version string, threaded from the project configuration.
76
- # @return [Result, nil] nil when the file does not parse (a parse error is the analyzer's business, not
77
- # this scan's — it simply contributes nothing rather than half a file).
78
73
  # A constant name is ASCII by construction, so a byte sequence that is not valid UTF-8 cannot be one.
79
74
  # Dropping it is both correct and the only safe answer: carrying it forward crashed the whole run on
80
75
  # the first `String#sub` downstream, which is how this surfaced — `rigor unused` on Rigor's own
@@ -86,6 +81,11 @@ module Rigor
86
81
  name.valid_encoding? ? name : nil
87
82
  end
88
83
 
84
+ # @param path — the file's path, as the report should render it.
85
+ # @param source — the file's bytes.
86
+ # @param target_ruby — Prism version string, threaded from the project configuration.
87
+ # @return nil when the file does not parse (a parse error is the analyzer's business, not
88
+ # this scan's — it simply contributes nothing rather than half a file).
89
89
  def self.call(path:, source:, target_ruby: nil)
90
90
  parsed = if target_ruby
91
91
  Prism.parse(source, filepath: path,
@@ -43,9 +43,9 @@ module Rigor
43
43
  RACY_WINDOW_NS = 2_000_000_000
44
44
  private_constant :RACY_WINDOW_NS
45
45
 
46
- # @param cache_path [String, nil] the configuration's cache root; nil or empty is an inert
46
+ # @param cache_path the configuration's cache root; nil or empty is an inert
47
47
  # cache (every fetch computes, nothing persists).
48
- # @param target_ruby [String, nil] parse-affecting configuration, part of the identity.
48
+ # @param target_ruby parse-affecting configuration, part of the identity.
49
49
  def self.open(cache_path, target_ruby: nil)
50
50
  return new(path: nil, header: nil) if cache_path.nil? || cache_path.to_s.empty?
51
51
 
@@ -26,8 +26,8 @@ module Rigor
26
26
  module SignatureScan
27
27
  module_function
28
28
 
29
- # @param file [String] path to a `.rbs` file.
30
- # @return [Array<Scan::Reference>] one file-level reference per distinct referenced name. Empty when the
29
+ # @param file path to a `.rbs` file.
30
+ # @return one file-level reference per distinct referenced name. Empty when the
31
31
  # file cannot be read or parsed — a broken signature is the analyzer's business, not this scan's.
32
32
  def call(file)
33
33
  _, _, decls = ::RBS::Parser.parse_signature(::RBS::Buffer.new(name: file, content: File.read(file)))
@@ -7,7 +7,7 @@ module Rigor
7
7
  class Result
8
8
  attr_reader :diagnostics, :stats
9
9
 
10
- # @param stats [Rigor::Analysis::RunStats, nil] end-of-run telemetry (target file count, RBS class
10
+ # @param stats end-of-run telemetry (target file count, RBS class
11
11
  # breakdown, wall + RSS) collected by the Runner. Nil when stats collection wasn't requested or wasn't
12
12
  # applicable (early-exit paths like `validate_target_ruby` failure).
13
13
  def initialize(diagnostics: [], stats: nil)
@@ -43,8 +43,6 @@ module Rigor
43
43
 
44
44
  # The diagnostics {#crashed?} answers true for, so a caller can name what it saw rather than only that
45
45
  # it saw something.
46
- #
47
- # @return [Array<Rigor::Analysis::Diagnostic>]
48
46
  def crash_diagnostics
49
47
  diagnostics.select { |diagnostic| CrashSignature.discards_file_analysis?(diagnostic) }
50
48
  end
@@ -642,7 +642,10 @@ module Rigor
642
642
  "Later write is `nil` — the `@cache = nil` clear-idiom is allowlisted.",
643
643
  "Either side is Union / Dynamic / IntegerRange / a shape-varied carrier.",
644
644
  "Writes live in different classes that happen to share an ivar name.",
645
- "Writes are in `def self.foo` (singleton) bodies — those track separately."
645
+ "Writes are in a singleton body — `def self.foo` or a `class << self` def — those track " \
646
+ "separately.",
647
+ "Writes are in a `def` inside an anonymous-class factory block (`Class.new do … end`, " \
648
+ "`Module.new`, `Struct.new`, `Data.define`): that body defines another class."
646
649
  ],
647
650
  suppression: "`# rigor:disable ivar-write-mismatch` on the offending write.",
648
651
  severity_authored: :error,
@@ -80,7 +80,7 @@ module Rigor
80
80
  # collecting project's path-set churn is the same churn, and over-evicting costs one recompute.
81
81
  EFFECTS_GENERATION_CAP = GENERATION_CAP
82
82
 
83
- # @param rbs_config_entries [Array<Cache::Descriptor::ConfigEntry>] the RBS-derived config slots
83
+ # @param rbs_config_entries the RBS-derived config slots
84
84
  # (`rbs.libraries` [+ `rbs.virtual_rbs`]). nil on any failure so a malformed key disables the cache.
85
85
  def descriptor(configuration:, files:, explain:, rbs_config_entries:)
86
86
  Cache::Descriptor.new(
@@ -51,17 +51,15 @@ module Rigor
51
51
  # serves it instead. The decline is measured against the declarations alone, never against what they
52
52
  # would judge to, so it costs one glob and never a wrong answer.
53
53
  class RunCacheProbe
54
- # @param configuration [Rigor::Configuration]
55
- # @param cache_root [String]
56
- # @param explain [Boolean] the `--explain` flag (folded into the key, as the runner does).
54
+ # @param explain — the `--explain` flag (folded into the key, as the runner does).
57
55
  def initialize(configuration:, cache_root:, explain:)
58
56
  @configuration = configuration
59
57
  @cache_root = cache_root
60
58
  @explain = explain
61
59
  end
62
60
 
63
- # @param paths [Array<String>] the analysis roots (`@argv` or `configuration.paths`).
64
- # @return [Analysis::Result, nil] the cached run result with the severity profile applied and no stats
61
+ # @param paths the analysis roots (`@argv` or `configuration.paths`).
62
+ # @return the cached run result with the severity profile applied and no stats
65
63
  # (matching a cache-served `Runner#run`), or nil to DECLINE — a miss / stale / unavailable cache — so
66
64
  # the caller loads the engine and runs the full path. Any failure declines rather than raising: the
67
65
  # probe must never turn a servable run into a crash.
@@ -66,7 +66,7 @@ module Rigor
66
66
  # via `RIGOR_LSP_POOL_MIN_BATCH` (mirrors `RIGOR_RACTOR_WORKERS`'s override shape).
67
67
  DEFAULT_MIN_BATCH_SIZE = 16
68
68
 
69
- # @return [Integer] `RIGOR_LSP_POOL_MIN_BATCH` when set to a non-empty value, else
69
+ # @return `RIGOR_LSP_POOL_MIN_BATCH` when set to a non-empty value, else
70
70
  # {DEFAULT_MIN_BATCH_SIZE}.
71
71
  def self.resolve_min_batch_size
72
72
  env_value = ENV.fetch("RIGOR_LSP_POOL_MIN_BATCH", nil)
@@ -75,19 +75,17 @@ module Rigor
75
75
  Integer(env_value)
76
76
  end
77
77
 
78
- # @param configuration [Rigor::Configuration]
79
- # @param cache_store [Rigor::Cache::Store, nil]
80
- # @param environment [Rigor::Environment] the warm, shared per-session Environment
78
+ # @param environment — the warm, shared per-session Environment
81
79
  # (`ProjectContext#environment`) every job's Runner reuses instead of rebuilding.
82
- # @param prebuilt [Rigor::Analysis::ProjectScan] the warm, shared pre-pass snapshot
80
+ # @param prebuilt the warm, shared pre-pass snapshot
83
81
  # (`ProjectContext#project_scan`) every job's Runner adopts instead of re-scanning.
84
- # @param workers [Integer] pool size. `#analyze` degrades to sequential in-process execution — one
82
+ # @param workers pool size. `#analyze` degrades to sequential in-process execution — one
85
83
  # job at a time, no `fork` — when fewer than 2 bindings are submitted, fewer than `min_batch_size`
86
84
  # bindings are submitted, `workers` is not positive, `fork` is unavailable on this platform, or
87
85
  # `cache_store` is nil. The middle two are the fork-pool-is-not-worth-it-yet gate documented on
88
86
  # {DEFAULT_MIN_BATCH_SIZE}; the last two mirror
89
87
  # {PoolCoordinator#analyze_files_in_pool}'s own fork-pool preconditions.
90
- # @param min_batch_size [Integer] see {DEFAULT_MIN_BATCH_SIZE}. Exposed as a constructor param
88
+ # @param min_batch_size see {DEFAULT_MIN_BATCH_SIZE}. Exposed as a constructor param
91
89
  # (rather than read from the env internally) purely for spec control; production callers get the
92
90
  # resolved default.
93
91
  def initialize(configuration:, cache_store:, environment:, prebuilt:, workers:,
@@ -185,7 +183,7 @@ module Rigor
185
183
  degraded
186
184
  end
187
185
 
188
- # @return [Hash, nil] the child's `{index => diagnostics}` payload, or nil when the child exited
186
+ # @return the child's `{index => diagnostics}` payload, or nil when the child exited
189
187
  # abnormally or wrote no readable payload. `Marshal.load` is safe here: the blob was written by our
190
188
  # own forked child to a temp file we created.
191
189
  def fork_worker_payload(status, out_path)
@@ -20,8 +20,7 @@ module Rigor
20
20
 
21
21
  module_function
22
22
 
23
- # @param finding [#location]
24
- # @return [Array(String, Integer)] `[path, line]`
23
+ # @return `[path, line]`
25
24
  def of(finding)
26
25
  location = finding.location
27
26
  return [CONFIG_PATH, 1] if location.nil?