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
@@ -48,7 +48,17 @@ module Rigor
48
48
  # added to a schema-less project) invalidates the warm entry. A pre-8 entry carries no absence rows
49
49
  # and would validate fresh across exactly that edit, so cached entries must read as misses once and
50
50
  # rebuild carrying the rows.
51
- SCHEMA_VERSION = 8
51
+ # v9: #979 — the run-result dependency descriptor now carries a names-only {GlobEntry} directory
52
+ # listing per RBS signature root, so a `.rbs` file APPEARING under one (a `sig/roles.rbs` declaring the
53
+ # interface a `conforms-to` names) invalidates the warm entry. A pre-9 entry carries no signature-root
54
+ # row and would validate fresh across exactly that edit — the #577 reasoning, one slot over — so cached
55
+ # entries must read as misses once and rebuild carrying the rows.
56
+ # v10: #1123 — the def-index seed bundles gain the `:prepends` table, the instance-ancestor-ORDER half
57
+ # of the `include` table (`prepend M`, and `Recv.prepend(M)`), which `Scope#user_def_through_ancestors`
58
+ # searches ahead of a class's own `def`s. A pre-10 bundle would silently contribute no prepends for an
59
+ # unchanged file, so that file's consumers would keep answering with the pre-fix MRO — cached bundles
60
+ # must read as misses once and rebuild carrying the slot.
61
+ SCHEMA_VERSION = 10
52
62
 
53
63
  # Per-slot entry value objects. Constructors validate enums / required fields and freeze the resulting
54
64
  # struct so no caller can mutate after the entry is in a Descriptor.
@@ -114,7 +124,7 @@ module Rigor
114
124
  new(path: path, comparator: :exists, value: PRESENT)
115
125
  end
116
126
 
117
- # @return [Boolean] whether this row records the path as absent (see {.absent}).
127
+ # @return whether this row records the path as absent (see {.absent}).
118
128
  def absent?
119
129
  comparator == :exists && value == ABSENT
120
130
  end
@@ -235,31 +245,58 @@ module Rigor
235
245
  FIELD_SEPARATOR = "\0"
236
246
  private_constant :ROW_SEPARATOR, :FIELD_SEPARATOR
237
247
 
248
+ # `:stat` is the ADR-87 WD2 signature above — it answers "did anything under this glob change",
249
+ # appearance and edit alike. `:names` (#979) answers only "did the SET of matching paths change":
250
+ # its rows are the sorted relative names, so a file whose bytes are unchanged never moves the
251
+ # signature however its stat tuple moved. A row exists to catch appearance/disappearance only —
252
+ # edits to the files it lists are already carried by their own `:stat` {FileEntry} rows — and a
253
+ # names row must therefore survive what those rows survive. A `:stat` glob over a signature tree
254
+ # would not: ADR-87 WD1 lets a `:stat` file row fall back to its recorded content digest when the
255
+ # tuple moves, so a `git checkout`, a `bundle install` or a CI cache restored over a fresh checkout
256
+ # keeps the file rows fresh while it would move every glob signature, turning every such run into a
257
+ # full re-analysis. `:names` also reads no stat at all, so it costs a `Dir.glob` and nothing else.
258
+ VALID_MODES = %i[stat names].freeze
259
+
238
260
  attr_reader :root, :pattern, :value
239
261
 
240
- value_fields :root, :pattern, :value
262
+ value_fields :root, :pattern, :mode, :value
263
+
264
+ def initialize(root:, pattern:, value:, mode: :stat)
265
+ raise ArgumentError, "GlobEntry mode must be one of #{VALID_MODES.inspect}, got #{mode.inspect}" \
266
+ unless VALID_MODES.include?(mode)
241
267
 
242
- def initialize(root:, pattern:, value:)
243
268
  @root = root.to_s.dup.freeze
244
269
  @pattern = pattern.to_s.dup.freeze
270
+ @mode = mode
245
271
  @value = value.to_s.dup.freeze
246
272
  freeze
247
273
  end
248
274
 
275
+ # Defaulted rather than an `attr_reader` because an entry Marshal-loaded from a store written before
276
+ # the field existed carries no `@mode` ivar; every such entry was recorded under the stat signature.
277
+ def mode
278
+ @mode || :stat
279
+ end
280
+
249
281
  # Builds the entry for the glob's CURRENT filesystem state.
250
- def self.compute(root:, pattern:)
251
- new(root: root, pattern: pattern, value: signature_for(root: root, pattern: pattern))
282
+ def self.compute(root:, pattern:, mode: :stat)
283
+ new(root: root, pattern: pattern, mode: mode,
284
+ value: signature_for(root: root, pattern: pattern, mode: mode))
252
285
  end
253
286
 
254
287
  # The aggregate signature the entry's `value` carries: SHA-256 over the sorted per-file rows. In the
255
288
  # default (`:stat`) mode a row is the file's `(path, size, mtime_ns, ctime_ns, inode)` tuple — NO
256
289
  # content read; in strict mode a row is `(path, content-sha256)`, restoring the pre-ADR-87 authority.
290
+ # In `:names` mode a row is the path alone, stat-free, so only the matching SET moves the signature.
257
291
  # Per-file stat failures (a file vanishing between the glob and the stat) drop the row — same race
258
292
  # posture as {Descriptor#file_entry_fresh?}. `Dir.glob` returns sorted entries by default so the row
259
293
  # order — and therefore the signature — is stable.
260
- def self.signature_for(root:, pattern:)
294
+ def self.signature_for(root:, pattern:, mode: :stat)
295
+ paths = Dir.glob(File.join(root, pattern))
296
+ return Digest::SHA256.hexdigest(paths.map { |path| "#{path}#{ROW_SEPARATOR}" }.join) if mode == :names
297
+
261
298
  strict = FileDigest.strict_validation?
262
- rows = Dir.glob(File.join(root, pattern)).filter_map do |path|
299
+ rows = paths.filter_map do |path|
263
300
  st = File.stat(path)
264
301
  next nil unless st.file?
265
302
 
@@ -276,23 +313,30 @@ module Rigor
276
313
  end
277
314
 
278
315
  # ADR-87 WD2 — fresh iff re-globbing + re-stat-ing reproduces the recorded signature. Zero file-content
279
- # bytes are read in the default mode. Any failure reads as stale (recompute), never a crash. A stored
280
- # signature recorded under the opposite validation mode simply mismatches and recomputes.
316
+ # bytes are read in the default mode, and no stat at all in `:names`. Any failure reads as stale
317
+ # (recompute), never a crash. A stored signature recorded under the opposite validation mode simply
318
+ # mismatches and recomputes.
281
319
  def self.fresh?(entry)
282
- signature_for(root: entry.root, pattern: entry.pattern) == entry.value
320
+ signature_for(root: entry.root, pattern: entry.pattern, mode: entry.mode) == entry.value
283
321
  rescue StandardError
284
322
  false
285
323
  end
286
324
 
287
- # Composition key — {.compose} unions per (root, pattern) slot; two contributions for the same slot
288
- # must agree on the value or {Conflict} is raised. Within one run the same glob stat-reads identically,
289
- # so contributions never conflict.
325
+ # Composition key — {.compose} unions per (root, pattern, mode) slot; two contributions for the same
326
+ # slot must agree on the value or {Conflict} is raised. Within one run the same glob stat-reads
327
+ # identically, so contributions never conflict. The mode is part of the key because the two signatures
328
+ # over one (root, pattern) are different questions, not rival answers to the same one.
290
329
  def slot_key
291
- "#{root}\0#{pattern}"
330
+ "#{root}\0#{pattern}\0#{mode}"
292
331
  end
293
332
 
294
333
  def to_h
295
- { "root" => root, "pattern" => pattern, "value" => value }
334
+ h = { "root" => root, "pattern" => pattern, "value" => value }
335
+ # Omitted in the default mode so a descriptor carrying only stat globs keeps its pre-#979 canonical
336
+ # bytes: this FIELD moves no producer's key. (Every key still moved once with SCHEMA_VERSION 9,
337
+ # which the storage layer mixes in; that is the #979 migration, not the mode.)
338
+ h["mode"] = mode.to_s unless mode == :stat
339
+ h
296
340
  end
297
341
  end
298
342
 
@@ -352,9 +396,8 @@ module Rigor
352
396
  dependencies: dependencies, globs: globs)
353
397
  end
354
398
 
355
- # @param producer_id [String]
356
- # @param params [Hash] inputs the producer was called with
357
- # @return [String] hex SHA-256 cache key for the value
399
+ # @param params — inputs the producer was called with
400
+ # @return hex SHA-256 cache key for the value
358
401
  def cache_key_for(producer_id:, params: {})
359
402
  payload = {
360
403
  "schema_version" => SCHEMA_VERSION,
@@ -377,7 +420,7 @@ module Rigor
377
420
  "dependencies" => sort_entries(dependencies, "gem_name").map(&:to_h),
378
421
  "files" => sort_entries(files, "path").map(&:to_h),
379
422
  "gems" => sort_entries(gems, "name").map(&:to_h),
380
- "globs" => globs.sort_by { |e| [e.root, e.pattern] }.map(&:to_h),
423
+ "globs" => globs.sort_by { |e| [e.root, e.pattern, e.mode.to_s] }.map(&:to_h),
381
424
  "plugins" => sort_entries(plugins, "id").map(&:to_h)
382
425
  }
383
426
  end
@@ -3,6 +3,7 @@
3
3
  require "digest"
4
4
 
5
5
  require_relative "../version"
6
+ require_relative "descriptor"
6
7
 
7
8
  module Rigor
8
9
  module Cache
@@ -63,17 +64,20 @@ module Rigor
63
64
  # to whatever else happened to be there, which is exactly the weakening this module forbids.
64
65
  REQUIRED_DIRECTORY = "lib"
65
66
 
67
+ # The config-row key every computed-value slot carries the identity under.
68
+ CONFIG_KEY = "engine-source"
69
+
66
70
  module_function
67
71
 
68
- # @return [String] the gem root — the directory holding `lib/`, three levels above this file.
72
+ # @return the gem root — the directory holding `lib/`, three levels above this file.
69
73
  def root
70
74
  @root ||= File.expand_path("../../..", __dir__)
71
75
  end
72
76
 
73
- # @param root [String] the gem root, defaulted through {.root} so a spec can relocate the tree.
74
- # @return [String, nil] a digest identifying the engine's current source, or nil when the tree is
77
+ # @param root — the gem root, defaulted through {.root} so a spec can relocate the tree.
78
+ # @return a digest identifying the engine's current source, or nil when the tree is
75
79
  # version-pinned and the caller should add no slot at all.
76
- # @raise [Unavailable] when a mutable tree's source cannot be read.
80
+ # @raise Unavailable — when a mutable tree's source cannot be read.
77
81
  def identity(root = self.root)
78
82
  return nil if version_pinned?(root)
79
83
 
@@ -100,10 +104,34 @@ module Rigor
100
104
  # exists at all. The same reading covers the long-running `rigor lsp` process.
101
105
  #
102
106
  # {Unavailable} propagates and is deliberately NOT memoised: the ivar is only assigned on success.
107
+ #
108
+ # The memo is FROZEN because a Ractor-pool worker reads it (#1055): the auto-wired ADR-93
109
+ # `rigor-rbs-inline` synthesizer reaches `.key_config_entries` from inside
110
+ # `Environment.for_project`, which is inside `WorkerSession#initialize`, which is inside the worker's
111
+ # own Ractor — and a non-main Ractor may read a class/module ivar only when its value is shareable.
112
+ # An unfrozen digest String therefore killed every worker in its constructor on any run with the
113
+ # synthesizer on, which is every real CLI run (the spec suite pins it off). The worker never WRITES
114
+ # it: `PoolCoordinator#analyze_files_in_pool` pre-warms this on the main Ractor first, which is where
115
+ # the argument above already says the value belongs.
103
116
  def process_identity
104
117
  return @process_identity if defined?(@process_identity)
105
118
 
106
- @process_identity = identity
119
+ @process_identity = identity.freeze
120
+ end
121
+
122
+ # The `engine-source` key row for a cache whose value this engine computed, built from
123
+ # {.process_identity}: empty for a version-pinned tree, so a released gem's keys stay byte-identical.
124
+ #
125
+ # Issue #1009 — a slot keyed only on its inputs and a plugin's manifest version outlives the engine
126
+ # that filled it. The per-file synthesizer slot and every plugin producer were keyed that way, so after
127
+ # an engine or bundled-plugin edit the run-result key moved and the run re-analysed, but through the
128
+ # previous build's synthesized RBS and producer values. {Unavailable} propagates, so a caller that
129
+ # cannot identify the engine bypasses its cache instead of falling back to the weaker key.
130
+ def key_config_entries
131
+ identity = process_identity
132
+ return [] if identity.nil?
133
+
134
+ [Descriptor::ConfigEntry.new(key: CONFIG_KEY, value_hash: Digest::SHA256.hexdigest(identity))]
107
135
  end
108
136
 
109
137
  # Discards the {.process_identity} memo; production code MUST NOT call this — a run that recomputed
@@ -74,7 +74,7 @@ module Rigor
74
74
  memo[path] ||= Digest::SHA256.file(path).hexdigest
75
75
  end
76
76
 
77
- # @return [Boolean] whether digest-always validation is forced for this run. The env var wins over the
77
+ # @return whether digest-always validation is forced for this run. The env var wins over the
78
78
  # run-scoped config flag so an operator can bypass the stat tier without editing `.rigor.yml`.
79
79
  def self.strict_validation?
80
80
  return true if ENV[STRICT_ENV] == "1"
@@ -112,6 +112,14 @@ module Rigor
112
112
  hexdigest(path) == digest
113
113
  end
114
114
 
115
+ # The content-digest field of a packed `:stat` entry ({.pack_stat}); nil when the entry is absent or not
116
+ # well-formed. Exposed for the caller that must compare the recorded digest against bytes it hashes
117
+ # itself rather than against the file on disk — the incremental session's editor-mode freshness check,
118
+ # whose authority is the editor's buffer.
119
+ def self.content_digest(packed)
120
+ parse_stat(packed)&.first
121
+ end
122
+
115
123
  # The VALIDATION-side stat, served from the per-run table when one is installed — a collecting run
116
124
  # validates the effects entry and the diagnostics entry against the same ~thousands-of-files
117
125
  # dependency descriptor, and the second pass is pure repetition under the run's own stable-filesystem
@@ -84,7 +84,61 @@ module Rigor
84
84
  # `[]`, deserialises cleanly, and would send every unchanged file's top-level helper back to the peel
85
85
  # on a warm run while a cold run resolved at the top level — the `--verify-incremental` divergence 14
86
86
  # was bumped for, in the same table.
87
- SCHEMA = 17
87
+ # 18: issue #728 re-keys each seed bundle's `header_nestings` VALUE from one chain per class to one
88
+ # chain per ancestor NAME the class's sites wrote, plus an unkeyed chain holding the old union. A
89
+ # pre-18 blob's Array value is not a Hash and the resolver's per-name lookup would raise on it, so the
90
+ # gate has to reject it — and even a reader that tolerated the old shape would serve the per-class
91
+ # union for every unchanged file, which is the answer this issue exists to stop giving.
92
+ # 19: issue #668 re-keys a constant write through a base no name reaches — `k::LIMIT = 7` — from the
93
+ # bare last segment to the wildcard `*::LIMIT`, in `constant_decls` and in each seed bundle's
94
+ # `constant_writes` census alike. A pre-19 blob's `LIMIT` key deserialises cleanly and reads as an
95
+ # ordinary top-level write, so every unchanged file would keep publishing the `Foo::LIMIT` the write
96
+ # may already have replaced while a cold run made it gradual — the `--verify-incremental` divergence
97
+ # 14 was bumped for.
98
+ # 20: issue #722 residue 2 adds each seed bundle's compact-header re-anchor census, without which a
99
+ # warm fold sees no candidate for an unchanged file and keeps serving the pre-fix `Wrap::Outer::Leaf`
100
+ # for a `class Outer::Leaf` that reopens `::Outer::Leaf`.
101
+ # 21: issue #915 widens the seed bundle's `extends` table to a singleton-body `include` / `prepend`;
102
+ # a pre-21 bundle for an unchanged file would keep the narrower record and still report the
103
+ # `case Widget when M` arm unreachable.
104
+ # 22: issues #796 / #794 add `run_level_rows` — the cold run's `definition-build-failed` set and its
105
+ # HKT-scan outcome, the two rows a narrowed run cannot re-derive from the files it analyses. A pre-22
106
+ # blob has no such section and would deserialise it as nil, which reads as "nothing cached" and is
107
+ # therefore not misread — but a snapshot written by an engine that did not yet REPLAY them was also
108
+ # written by one whose narrowed runs under-reported, so the gate rejects it and the next run is cold.
109
+ # 23: issue #992 gives each seed bundle a `parameter_envelopes` table — the joined positional envelope
110
+ # of every method a file records, plus its module / dynamic-surface marks — which `call.wrong-arity`
111
+ # reads for a method no signature declares. A pre-23 bundle folds as "no envelopes", which only ever
112
+ # withholds a check, but a warm run that withholds where a cold run fires is still the
113
+ # `--verify-incremental` divergence 14 was bumped for.
114
+ # 24: issue #1097 gives each seed bundle a `deferred_ranges` table — the per-file def / block /
115
+ # lambda body ranges `Scope#*_def_shadows_call?` orders a project-defined `sig` override
116
+ # against. A pre-24 bundle would fold as "no ranges", which the predicate reads as "cannot
117
+ # order → shadow" — a warm run declining the binding where a cold run binds.
118
+ # 25: issue #1097 extends each `deferred_ranges` row to `[start, end, name, kind, owner]` —
119
+ # owner-scoped ordering. A 24 bundle folds as "owner = nil", which the predicate reads as
120
+ # "cannot order", under-shadowing `module_function` defs whose only table evidence is the row
121
+ # itself.
122
+ # 26: issue #1123 gives each seed bundle a `prepends` table — the instance-ancestor ORDER of a
123
+ # class's `prepend`ed modules, which `Scope#user_def_through_ancestors` searches ahead of the
124
+ # class's own `def`s. A pre-26 bundle would fold as "prepends nothing": the warm run would answer
125
+ # the pre-fix MRO where a cold run answers the prepended module, exactly the `--verify-incremental`
126
+ # divergence 14 was bumped for (and a silently WRONG dispatch answer, not a withheld check).
127
+ # 27: issue #1173 re-MEANs each seed bundle's `includes` VALUES without changing their shape: the
128
+ # lists now store instance-ancestor SEARCH order (nearest first, prepends ahead of includes)
129
+ # instead of call order — the same flip 17 made for def-row `nesting`. A pre-27 blob deserialises
130
+ # cleanly and would serve the old call order on a warm run while a cold run searches nearer-first:
131
+ # `include A; include B` would resolve A's methods where a cold run resolves B's — the
132
+ # `--verify-incremental` divergence 14 was bumped for.
133
+ # 28: issue #1120 gives each seed bundle a `refinements` table — the `def`s of every `refine X do … end`
134
+ # body, which `call.undefined-method` reads where a `using` of the refining module is in effect. A
135
+ # pre-28 bundle would fold as "refines nothing", and a warm run would report a refined call a cold run
136
+ # declines.
137
+ # 29: issue #1367 gives each seed bundle a `global_write_census` — the globals its `alias` statements name and
138
+ # the `write` / `to_str` / `to_int` / hatch definitions and top-level mixins it holds, which the `global.*`
139
+ # write rules decline on project-wide. A pre-29 bundle would fold as "none of those", and a warm run would
140
+ # report a write a cold run exempts.
141
+ SCHEMA = 29
88
142
 
89
143
  # The persisted per-file state.
90
144
  # `cache` maps an analyzed file to its diagnostics.
@@ -136,11 +190,21 @@ module Rigor
136
190
  # collection was off). It is a SEPARATE gate from the global `fingerprint`: the `effects:` block is
137
191
  # deliberately absent from `Configuration#to_h`, so turning collection on invalidates no diagnostics,
138
192
  # and a vocabulary / catalogue / `effects:` change must invalidate the summaries alone.
193
+ # Issues #796 / #794 — the run-level rows a narrowed run replays instead of re-deriving; see
194
+ # docs/internal-spec/cache.md § "Run-level rows the snapshot carries" for why exactly these two are
195
+ # fresh under the fingerprint and why no other `.rigor.yml`-level row may join them.
139
196
  Payload = Data.define(:cache, :sources, :digests, :analyzed,
140
197
  :symbol_sources, :ancestry_sources, :symbol_fingerprints,
141
198
  :missing, :class_decls, :constant_decls, :seed_bundles, :plugin_fact_digest,
142
199
  :return_summaries, :param_table,
143
- :effect_collections, :effects_identity)
200
+ :effect_collections, :effects_identity, :run_level_rows)
201
+
202
+ # Issues #796 / #794 — the two run-level rows the snapshot carries. `definition_build_failures` is the
203
+ # reported per-class detail list ({Environment::RbsLoader#definition_build_failures}); `hkt_scan_failure`
204
+ # is the `[error_class, first_line, frame, stage]` tuple ({Rigor::Environment#hkt_scan_failure}), nil
205
+ # when the scan succeeded — which is why the presence of this object, not the tuple, is what says a
206
+ # replay is available.
207
+ RunLevelRows = Data.define(:definition_build_failures, :hkt_scan_failure)
144
208
 
145
209
  # The global fingerprint that gates a snapshot load: a digest of the inputs whose change requires a full
146
210
  # rebuild — the engine version + schema, the engine's own SOURCE when the version does not pin it, the
@@ -222,8 +286,8 @@ module Rigor
222
286
  # The fingerprint that matched is returned alongside the payload so the caller can mix it into ITS own
223
287
  # cache key — the snapshot's identity is exactly what "these dependency edges came from that world" means.
224
288
  #
225
- # @param fingerprints [Array<String>] candidates, most-specific first.
226
- # @return [Array(String, Payload), nil] `[matched fingerprint, payload]`, or nil on any miss.
289
+ # @param fingerprints — candidates, most-specific first.
290
+ # @return `[matched fingerprint, payload]`, or nil on any miss.
227
291
  def load_any(fingerprints:)
228
292
  data = read_data
229
293
  return nil if data.nil?
@@ -259,7 +323,8 @@ module Rigor
259
323
  return_summaries: data[:return_summaries] || {},
260
324
  param_table: data[:param_table] || {},
261
325
  effect_collections: data[:effect_collections] || {},
262
- effects_identity: data[:effects_identity]
326
+ effects_identity: data[:effects_identity],
327
+ run_level_rows: data[:run_level_rows]
263
328
  )
264
329
  end
265
330
  private :payload_from
@@ -283,7 +348,8 @@ module Rigor
283
348
  return_summaries: payload.return_summaries,
284
349
  param_table: payload.param_table,
285
350
  effect_collections: payload.effect_collections,
286
- effects_identity: payload.effects_identity
351
+ effects_identity: payload.effects_identity,
352
+ run_level_rows: payload.run_level_rows
287
353
  )
288
354
  blob = Zlib::Deflate.deflate(raw)
289
355
  tmp = "#{@path}.#{Process.pid}.tmp"
@@ -29,6 +29,10 @@ module Rigor
29
29
  # files, same libraries), so the loader memoises one build per process instead of re-digesting every
30
30
  # .rbs file once per producer.
31
31
  descriptor = loader.rbs_cache_descriptor
32
+ # Issue #1014 — nil is an engine the descriptor could not identify ({EngineSource::Unavailable}).
33
+ # Compute uncached rather than keying the value without the engine that produced it.
34
+ return compute(loader) if descriptor.nil?
35
+
32
36
  store.fetch_or_compute(producer_id: self::PRODUCER_ID, params: {}, descriptor: descriptor,
33
37
  generation_cap: generation_cap) do
34
38
  compute(loader)
@@ -20,9 +20,6 @@ module Rigor
20
20
  class RbsClassAncestorTable < RbsCacheProducer
21
21
  PRODUCER_ID = "rbs.class_ancestor_table"
22
22
 
23
- # @param loader [Rigor::Environment::RbsLoader]
24
- # @param store [Rigor::Cache::Store]
25
- # @return [Hash{String => Array<String>}]
26
23
  def self.compute(loader)
27
24
  table = {}
28
25
  loader.each_known_class_name do |name|
@@ -20,9 +20,6 @@ module Rigor
20
20
  class RbsClassTypeParamNames < RbsCacheProducer
21
21
  PRODUCER_ID = "rbs.class_type_param_names"
22
22
 
23
- # @param loader [Rigor::Environment::RbsLoader]
24
- # @param store [Rigor::Cache::Store]
25
- # @return [Hash{String => Array<Symbol>}]
26
23
  def self.compute(loader)
27
24
  table = {}
28
25
  loader.each_known_class_name do |name|
@@ -15,9 +15,6 @@ module Rigor
15
15
  class RbsConstantTable < RbsCacheProducer
16
16
  PRODUCER_ID = "rbs.constant_type_table"
17
17
 
18
- # @param loader [Rigor::Environment::RbsLoader]
19
- # @param store [Rigor::Cache::Store]
20
- # @return [Hash{String => Rigor::Type}]
21
18
  def self.compute(loader)
22
19
  table = {}
23
20
  loader.each_constant_decl do |name, entry|
@@ -3,22 +3,47 @@
3
3
  require "digest"
4
4
 
5
5
  require_relative "descriptor"
6
+ require_relative "engine_source"
6
7
  require_relative "file_digest"
7
8
 
8
9
  module Rigor
9
10
  module Cache
10
11
  # Shared descriptor builder for cache producers that depend on the RBS environment (constant table,
11
- # known-class set, future Marshal-clean reflection artefacts). Every consumer attaches the same three
12
- # slots, so factoring the construction here keeps the producers small and ensures invalidation behaves
13
- # identically across them.
12
+ # known-class set, future Marshal-clean reflection artefacts). Every consumer attaches the same slots —
13
+ # the `rbs` gem row, a digest row per signature file, and the `rbs.libraries` / `rbs.virtual_rbs` /
14
+ # `rbs.deferred_signature_paths` / `engine-source` config rows — so factoring the construction here keeps
15
+ # the producers small and ensures invalidation behaves identically across them.
14
16
  module RbsDescriptor
15
- # @param loader [Rigor::Environment::RbsLoader]
16
- # @return [Rigor::Cache::Descriptor]
17
+ # The key every `rbs.*` producer shares ({RbsCacheProducer#fetch}): `rbs.environment`,
18
+ # `rbs.constant_type_table`, `rbs.class_ancestor_table`, `rbs.known_class_names`,
19
+ # `rbs.class_type_param_names`.
20
+ #
21
+ # Issue #1014 — the engine-source row is here, not in {.config_entries}, because these are
22
+ # computed-value slots: what they hold is not the RBS the descriptor digests but what Rigor's own code
23
+ # made of it. `rbs.constant_type_table` stores `Inference::RbsTypeTranslator.translate` output and
24
+ # `rbs.class_ancestor_table` the chains `RbsClassAncestorTable.compute` walks out of
25
+ # `instance_definition`, so an engine edit to either moved the value while the key stood still: the
26
+ # run-result key moved (#285) and the run re-analysed, then read the previous build's table. Both were
27
+ # reproduced cross-build on a fixture — a true positive dropped, and a `flow.unreachable-clause` false
28
+ # positive served — before this row was added.
29
+ #
30
+ # {.config_entries} deliberately does NOT get it: that half is what the ADR-45 run cache key reads
31
+ # ({.build_run}), and `RunCacheKey` already contributes the identical row from
32
+ # `EngineSource.key_config_entries` itself. Adding it there would duplicate the row in the run key and
33
+ # would have to be reconstructible by the boot-slimming probe, which builds `config_entries` without a
34
+ # loader.
35
+ #
36
+ # @raise EngineSource::Unavailable — an engine that cannot be identified must not be keyed by its
37
+ # inputs alone; {RbsCacheProducer.fetch} turns this into an uncached compute.
17
38
  def self.build(loader)
39
+ # FIRST, before {.file_entries} digests the whole signature tree: the {EngineSource::Unavailable}
40
+ # path discards this descriptor entirely, and a SHA-256 of every `.rbs` under every signature root is
41
+ # the expensive way to reach that conclusion.
42
+ engine = EngineSource.key_config_entries
18
43
  Descriptor.new(
19
44
  gems: [rbs_gem_entry],
20
45
  files: file_entries(loader),
21
- configs: config_entries(loader)
46
+ configs: config_entries(loader) + env_only_config_entries(loader) + engine
22
47
  )
23
48
  end
24
49
 
@@ -26,8 +51,11 @@ module Rigor
26
51
  # `gems` + `configs` ({Runner#run_key_descriptor}); the RBS signature-tree `files` are read solely on a
27
52
  # MISS, by the dependency descriptor ({Runner#run_dependency_descriptor}). So a warm HIT never digests
28
53
  # the (large, vendored) RBS tree. {RunDescriptor} is NOT a {Descriptor} — it is never composed, hashed,
29
- # or `==`'d, only its three readers are consulted — so deferring `files` costs no soundness, and `gems`
30
- # / `configs` are byte-identical to {.build} (the key is unchanged).
54
+ # or `==`'d, only its four readers are consulted — so deferring `files` costs no soundness, and `gems`
55
+ # / `configs` match {.build}'s shared slots (the key is unchanged; the slots the env key adds on top,
56
+ # {.env_only_config_entries} and the #1014 `engine-source` row, are never read here — `RunCacheKey`
57
+ # contributes the identical engine-source row itself, from the same
58
+ # `EngineSource.key_config_entries`).
31
59
  def self.build_run(loader)
32
60
  RunDescriptor.new(loader: loader, gems: [rbs_gem_entry], configs: config_entries(loader))
33
61
  end
@@ -38,19 +66,47 @@ module Rigor
38
66
  [libraries_entry(loader.libraries), virtual_rbs_entry(loader)].compact
39
67
  end
40
68
 
69
+ # Issue #610 — the slots only the env-cache KEY reads, on top of {.config_entries}. Which of the
70
+ # loader's `signature_paths:` are DEFERRED (a bundled plugin's `sig/`, loaded last and allowed to
71
+ # stand down against a colliding generic arity) changes the env built from byte-identical files, so
72
+ # the partition belongs in the key: moving a plugin's `sig/` between `plugins:` and an explicit
73
+ # `signature_paths:` entry changes no file, and an env cached before the producer honoured the
74
+ # partition must read as a miss once. Kept OUT of {.config_entries} deliberately — the run-result
75
+ # key already digests the whole resolved configuration (`RunCacheKey`), and its boot-slimming probe
76
+ # reconstructs {.config_entries} without loading a plugin, so an entry there that it cannot rebuild
77
+ # would turn the warm fast lane into a permanent miss on every project that enables a bundled plugin.
78
+ def self.env_only_config_entries(loader)
79
+ [deferred_signature_paths_entry(loader)].compact
80
+ end
81
+
82
+ def self.deferred_signature_paths_entry(loader)
83
+ return nil unless loader.respond_to?(:deferred_signature_paths)
84
+
85
+ deferred = Array(loader.deferred_signature_paths).map(&:to_s).sort
86
+ return nil if deferred.empty?
87
+
88
+ Descriptor::ConfigEntry.new(
89
+ key: "rbs.deferred_signature_paths",
90
+ value_hash: Digest::SHA256.hexdigest(deferred.join("\n"))
91
+ )
92
+ end
93
+
94
+ private_class_method :deferred_signature_paths_entry
95
+
41
96
  # Public (ADR-87 WD4) so the boot-slimming run-cache probe reconstructs the identical `gems` +
42
97
  # `rbs.libraries` key slots the runner writes, from the config-derived library list, without a loader.
43
98
  def self.rbs_gem_entry
44
99
  Descriptor::GemEntry.new(name: "rbs", requirement: ">= 0", locked: ::RBS::VERSION.to_s)
45
100
  end
46
101
 
47
- # @param comparator [Symbol] `:digest` (default) for the env-cache KEY descriptor ({.build}), where the
102
+ # @param comparator — `:digest` (default) for the env-cache KEY descriptor ({.build}), where the
48
103
  # value must be deterministic; `:stat` (ADR-87 WD1) for the validation-only run-dependency descriptor
49
104
  # ({RunDescriptor#files}), where the stat tier short-circuits the SHA-256 on an unmoved file.
50
105
  def self.file_entries(loader, comparator: :digest)
51
106
  roots = loader.signature_paths +
52
107
  Rigor::Environment::RbsLoader.vendored_gem_sig_paths +
53
- Rigor::Environment::RbsLoader.core_overlay_sig_paths
108
+ Rigor::Environment::RbsLoader.core_overlay_sig_paths +
109
+ Rigor::Environment::RbsLoader.capability_role_sig_paths
54
110
  roots.flat_map do |root|
55
111
  next [] unless root.directory?
56
112
 
@@ -65,7 +121,41 @@ module Rigor
65
121
  end
66
122
  end
67
123
 
68
- # @param library_names [Array<String, Symbol>] the loader's merged library list (or, on the WD4 probe
124
+ # Issue #979 — the directory-LISTING rows for the signature roots, one {Descriptor::GlobEntry} per
125
+ # root over `**/*.rbs`. {.file_entries} answers "did any signature file I read change", which is a
126
+ # question only about files that existed when the run recorded its dependencies; a `sig/roles.rbs`
127
+ # written afterwards is in no row, so the ADR-45 run-result slot validated fresh and the warm run kept
128
+ # serving diagnostics computed without it. A glob row is the same edge {Plugin::IoBoundary} records
129
+ # for a listed directory (#954): re-globbing on the next run sees the appearance, and one row per root
130
+ # (not per file) keeps the cost a single `Dir.glob`.
131
+ #
132
+ # The rows are `:names` mode, not the default `:stat`: the question they add is which signature files
133
+ # EXIST, and every edit to one of them is already carried by that file's own ADR-87 WD1 `:stat` row —
134
+ # which survives a moved stat tuple by falling back to its recorded content digest. A stat-mode glob
135
+ # would not, so a `git checkout`, a `bundle install`, or a CI run with a restored cache over a fresh
136
+ # checkout — none of which changes a byte of RBS — would turn every warm run into a full re-analysis.
137
+ # Names mode also reads no stat, so it neither pays for nor perturbs the per-run validation-stat memo.
138
+ #
139
+ # Scoped to the loader's own roots — the project's `signature_paths:` (including the auto-detected
140
+ # `sig/`), the bundled-plugin `sig/` trees (ADR-25), the bundle walk, `rbs collection`, the ADR-72
141
+ # overlays. Rigor's own `data/` trees ({RbsLoader.vendored_gem_sig_paths}, `.core_overlay_sig_paths`,
142
+ # `.capability_role_sig_paths`) are engine-owned: a file appears under one only when the engine tree is
143
+ # edited, which normally arrives with the `lib/` change that reads it and so re-keys every
144
+ # computed-value slot through {EngineSource} — including, since #1014, this descriptor itself, which
145
+ # until then carried no such row. They get file rows and no glob — a glob there would cost
146
+ # every project a stat walk of the vendored gem-signature tree per warm run to catch an
147
+ # engine-development edit.
148
+ def self.glob_entries(loader)
149
+ # Deduplicated by path: a root reachable twice (an explicit `signature_paths:` entry that is also a
150
+ # bundled plugin's `sig/`, #610) would otherwise contribute two rows whose stat signatures race.
151
+ loader.signature_paths.uniq(&:to_s).filter_map do |root|
152
+ next unless root.directory?
153
+
154
+ Descriptor::GlobEntry.compute(root: root.to_s, pattern: File.join("**", "*.rbs"), mode: :names)
155
+ end
156
+ end
157
+
158
+ # @param library_names — the loader's merged library list (or, on the WD4 probe
69
159
  # path, `Environment::DEFAULT_LIBRARIES + config.libraries` reconstructed without a loader).
70
160
  def self.libraries_entry(library_names)
71
161
  sorted = library_names.map(&:to_s).sort
@@ -93,10 +183,11 @@ module Rigor
93
183
 
94
184
  private_class_method :virtual_rbs_entry
95
185
 
96
- # The lazy-files run descriptor {RbsDescriptor.build_run} returns. Exposes the three readers the
186
+ # The lazy-files run descriptor {RbsDescriptor.build_run} returns. Exposes the four readers the
97
187
  # run-diagnostics cache consults — `gems` + `configs` are supplied eagerly (they feed the cache KEY,
98
- # and are cheap); `files` (the RBS signature-tree digests, read only on a MISS) is computed once on
99
- # first access and memoised, so a warm HIT never pays for it.
188
+ # and are cheap); `files` (the RBS signature-tree digests) and `globs` (#979, the per-root listing
189
+ # rows) are read only on a MISS, computed once on first access and memoised, so a warm HIT pays for
190
+ # neither.
100
191
  class RunDescriptor
101
192
  attr_reader :gems, :configs
102
193
 
@@ -106,6 +197,13 @@ module Rigor
106
197
  @configs = configs
107
198
  end
108
199
 
200
+ # Issue #979 — one directory-listing row per signature root, so a `.rbs` file APPEARING under one
201
+ # (or vanishing from it) invalidates the run-result slot. Memoised for the same reason `files` is:
202
+ # a collecting run asks twice about the same post-run world.
203
+ def globs
204
+ @globs ||= RbsDescriptor.glob_entries(@loader)
205
+ end
206
+
109
207
  def files
110
208
  # ADR-87 WD1 — this descriptor is validated (never a cache key), so the RBS signature tree rides the
111
209
  # stat-then-digest `:stat` tier: a warm run stat-checks the (large, vendored) tree instead of
@@ -22,14 +22,18 @@ module Rigor
22
22
  class RbsEnvironment < RbsCacheProducer
23
23
  PRODUCER_ID = "rbs.environment"
24
24
 
25
- # @param loader [Rigor::Environment::RbsLoader]
26
- # @param store [Rigor::Cache::Store]
27
- # @return [::RBS::Environment]
25
+ # Issue #610 — the deferred (plugin-contributed) subset rides through too. This is the build path every
26
+ # cached run takes: a cache store is the CLI default and only `--no-cache` reaches the loader's own
27
+ # `build_env`, so a list threaded through the loader but not through here ran the arity stand-down on
28
+ # no real `rigor check` at all — 0.3.8 shipped exactly that, behind a green gate built over a
29
+ # store-less loader. Any input `build_env_for` takes MUST be passed from here as well.
28
30
  def self.compute(loader)
29
31
  Rigor::Environment::RbsLoader.build_env_for(
30
32
  libraries: loader.libraries,
31
33
  signature_paths: loader.signature_paths,
32
- virtual_rbs: loader.respond_to?(:virtual_rbs) ? loader.virtual_rbs : []
34
+ virtual_rbs: loader.respond_to?(:virtual_rbs) ? loader.virtual_rbs : [],
35
+ deferred_signature_paths:
36
+ loader.respond_to?(:deferred_signature_paths) ? loader.deferred_signature_paths : []
33
37
  )
34
38
  end
35
39
 
@@ -18,9 +18,6 @@ module Rigor
18
18
  class RbsKnownClassNames < RbsCacheProducer
19
19
  PRODUCER_ID = "rbs.known_class_names"
20
20
 
21
- # @param loader [Rigor::Environment::RbsLoader]
22
- # @param store [Rigor::Cache::Store]
23
- # @return [Set<String>]
24
21
  def self.compute(loader)
25
22
  names = Set.new
26
23
  loader.each_known_class_name { |name| names << name }