rigortype 0.3.7 → 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 (450) 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 +96 -15
  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 +39 -4
  22. data/docs/manual/03-configuration.md +11 -0
  23. data/docs/manual/04-diagnostics.md +12 -1
  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 +69 -13
  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 +100 -29
  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 +316 -40
  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 +430 -77
  76. data/lib/rigor/analysis/runner/project_pre_passes.rb +12 -6
  77. data/lib/rigor/analysis/runner/run_snapshots.rb +13 -3
  78. data/lib/rigor/analysis/runner.rb +200 -43
  79. data/lib/rigor/analysis/worker_session.rb +36 -12
  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 +166 -30
  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 +70 -0
  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_environment_marshal_patch.rb +39 -0
  97. data/lib/rigor/cache/rbs_known_class_names.rb +0 -3
  98. data/lib/rigor/cache/store.rb +85 -33
  99. data/lib/rigor/cli/annotate_command.rb +5 -6
  100. data/lib/rigor/cli/check_command.rb +57 -10
  101. data/lib/rigor/cli/check_invocation.rb +6 -11
  102. data/lib/rigor/cli/check_runner_factory.rb +1 -5
  103. data/lib/rigor/cli/coverage_command.rb +1 -1
  104. data/lib/rigor/cli/coverage_mutation.rb +12 -3
  105. data/lib/rigor/cli/coverage_scan.rb +1 -5
  106. data/lib/rigor/cli/diff_command.rb +1 -1
  107. data/lib/rigor/cli/doc_links.rb +3 -3
  108. data/lib/rigor/cli/docs_command.rb +2 -2
  109. data/lib/rigor/cli/doctor_command.rb +85 -38
  110. data/lib/rigor/cli/effects_command.rb +2 -2
  111. data/lib/rigor/cli/effects_diff_renderer.rb +3 -3
  112. data/lib/rigor/cli/effects_snapshot_command.rb +1 -1
  113. data/lib/rigor/cli/explain_command.rb +34 -1
  114. data/lib/rigor/cli/fused_protection_report.rb +7 -1
  115. data/lib/rigor/cli/lsp_command.rb +1 -1
  116. data/lib/rigor/cli/mcp_command.rb +1 -1
  117. data/lib/rigor/cli/measurement_integrity_warning.rb +4 -5
  118. data/lib/rigor/cli/mutation_fork_scan.rb +6 -6
  119. data/lib/rigor/cli/mutation_protection_report.rb +7 -1
  120. data/lib/rigor/cli/plugin_command.rb +1 -1
  121. data/lib/rigor/cli/plugins_command.rb +2 -1
  122. data/lib/rigor/cli/prism_colorizer.rb +2 -2
  123. data/lib/rigor/cli/protection_fork_scan.rb +6 -6
  124. data/lib/rigor/cli/show_bleedingedge_command.rb +1 -1
  125. data/lib/rigor/cli/sig_gen_command.rb +31 -2
  126. data/lib/rigor/cli/skill_command.rb +1 -1
  127. data/lib/rigor/cli/skill_deep_probe.rb +4 -4
  128. data/lib/rigor/cli/skill_describe.rb +36 -35
  129. data/lib/rigor/cli/trace_command.rb +4 -4
  130. data/lib/rigor/cli/trace_renderer.rb +5 -6
  131. data/lib/rigor/cli/triage_command.rb +1 -1
  132. data/lib/rigor/cli/type_of_command.rb +10 -6
  133. data/lib/rigor/cli/type_scan_command.rb +4 -4
  134. data/lib/rigor/cli/unused_command.rb +11 -3
  135. data/lib/rigor/cli/upgrade_command.rb +1 -1
  136. data/lib/rigor/cli.rb +68 -6
  137. data/lib/rigor/config_audit.rb +30 -5
  138. data/lib/rigor/configuration/severity_profile.rb +6 -6
  139. data/lib/rigor/configuration.rb +43 -4
  140. data/lib/rigor/effects/attribution.rb +1 -3
  141. data/lib/rigor/effects/config_envelopes.rb +6 -8
  142. data/lib/rigor/effects/definition_lines.rb +25 -6
  143. data/lib/rigor/effects/effect_table.rb +0 -1
  144. data/lib/rigor/effects/entry_points.rb +0 -2
  145. data/lib/rigor/effects/envelope_check.rb +8 -8
  146. data/lib/rigor/effects/envelope_index.rb +5 -8
  147. data/lib/rigor/effects/framework_units.rb +4 -6
  148. data/lib/rigor/effects/identity.rb +4 -6
  149. data/lib/rigor/effects/inline_anchor.rb +7 -7
  150. data/lib/rigor/effects/label_intent.rb +3 -4
  151. data/lib/rigor/effects/liskov_check.rb +8 -8
  152. data/lib/rigor/effects/method_key.rb +1 -1
  153. data/lib/rigor/effects/plugin_facts.rb +4 -6
  154. data/lib/rigor/effects/propagator.rb +2 -3
  155. data/lib/rigor/effects/scanner.rb +1 -1
  156. data/lib/rigor/effects/signature_sources.rb +3 -5
  157. data/lib/rigor/effects/snapshot.rb +10 -10
  158. data/lib/rigor/effects/snapshot_diff.rb +1 -1
  159. data/lib/rigor/effects/unit_scan.rb +10 -10
  160. data/lib/rigor/effects/unknown_label_check.rb +3 -8
  161. data/lib/rigor/effects/unknown_label_report.rb +3 -4
  162. data/lib/rigor/environment/bundle_sig_discovery.rb +6 -6
  163. data/lib/rigor/environment/class_registry.rb +3 -1
  164. data/lib/rigor/environment/failure_slot.rb +28 -0
  165. data/lib/rigor/environment/installed_gem_set.rb +85 -0
  166. data/lib/rigor/environment/lockfile_resolver.rb +51 -4
  167. data/lib/rigor/environment/missing_gem_constant_index.rb +4 -4
  168. data/lib/rigor/environment/rbs_collection_discovery.rb +5 -5
  169. data/lib/rigor/environment/rbs_coverage_report.rb +5 -5
  170. data/lib/rigor/environment/rbs_hierarchy.rb +3 -1
  171. data/lib/rigor/environment/rbs_loader.rb +613 -113
  172. data/lib/rigor/environment.rb +189 -27
  173. data/lib/rigor/flow_contribution/merger.rb +0 -2
  174. data/lib/rigor/flow_contribution.rb +11 -13
  175. data/lib/rigor/inference/acceptance.rb +153 -8
  176. data/lib/rigor/inference/block_parameter_binder.rb +2 -3
  177. data/lib/rigor/inference/body_fixpoint.rb +5 -5
  178. data/lib/rigor/inference/budget_trace.rb +1 -5
  179. data/lib/rigor/inference/captured_locals.rb +2 -3
  180. data/lib/rigor/inference/closure_escape_analyzer.rb +2 -4
  181. data/lib/rigor/inference/coverage_scanner.rb +4 -6
  182. data/lib/rigor/inference/dynamic_origin.rb +1 -1
  183. data/lib/rigor/inference/element_read_widening.rb +168 -0
  184. data/lib/rigor/inference/expression_typer.rb +763 -143
  185. data/lib/rigor/inference/fork_map.rb +5 -7
  186. data/lib/rigor/inference/hkt_reducer.rb +2 -3
  187. data/lib/rigor/inference/hkt_registry.rb +19 -10
  188. data/lib/rigor/inference/hkt_sugar_translator.rb +15 -21
  189. data/lib/rigor/inference/index_write_widening.rb +1 -5
  190. data/lib/rigor/inference/macro_block_self_type.rb +1 -4
  191. data/lib/rigor/inference/method_dispatcher/block_folding.rb +2 -9
  192. data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +1 -1
  193. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +305 -19
  194. data/lib/rigor/inference/method_dispatcher/data_folding.rb +1 -1
  195. data/lib/rigor/inference/method_dispatcher/file_folding.rb +1 -1
  196. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +2 -2
  197. data/lib/rigor/inference/method_dispatcher/json_folding.rb +1 -1
  198. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +7 -1
  199. data/lib/rigor/inference/method_dispatcher/math_folding.rb +41 -3
  200. data/lib/rigor/inference/method_dispatcher/method_folding.rb +2 -5
  201. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +61 -47
  202. data/lib/rigor/inference/method_dispatcher/random_folding.rb +82 -0
  203. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +165 -31
  204. data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +25 -4
  205. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +2 -8
  206. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +1 -1
  207. data/lib/rigor/inference/method_dispatcher/set_folding.rb +1 -1
  208. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +1 -1
  209. data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +1 -1
  210. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +1 -2
  211. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +4 -5
  212. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +85 -4
  213. data/lib/rigor/inference/method_dispatcher/time_folding.rb +1 -1
  214. data/lib/rigor/inference/method_dispatcher/universal_object_dispatch.rb +1 -2
  215. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  216. data/lib/rigor/inference/method_dispatcher.rb +12 -13
  217. data/lib/rigor/inference/method_parameter_binder.rb +4 -6
  218. data/lib/rigor/inference/multi_target_binder.rb +1 -3
  219. data/lib/rigor/inference/mutation_rejoin.rb +168 -0
  220. data/lib/rigor/inference/mutation_widening.rb +100 -51
  221. data/lib/rigor/inference/narrowing.rb +299 -46
  222. data/lib/rigor/inference/optimistic_origin.rb +1 -7
  223. data/lib/rigor/inference/origin_lookup.rb +2 -3
  224. data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
  225. data/lib/rigor/inference/pre_eval_constants.rb +6 -5
  226. data/lib/rigor/inference/precision_scanner.rb +3 -4
  227. data/lib/rigor/inference/project_patched_methods.rb +2 -2
  228. data/lib/rigor/inference/project_patched_scanner.rb +3 -3
  229. data/lib/rigor/inference/protection_scanner.rb +1 -2
  230. data/lib/rigor/inference/range_constant.rb +57 -0
  231. data/lib/rigor/inference/rbs_type_translator.rb +49 -11
  232. data/lib/rigor/inference/receiver_alias.rb +3 -3
  233. data/lib/rigor/inference/refinement_mutation.rb +72 -0
  234. data/lib/rigor/inference/scope_indexer.rb +688 -151
  235. data/lib/rigor/inference/statement_evaluator.rb +221 -50
  236. data/lib/rigor/inference/string_mutation.rb +60 -0
  237. data/lib/rigor/inference/struct_fold_safety.rb +6 -7
  238. data/lib/rigor/inference/synthetic_method_index.rb +1 -2
  239. data/lib/rigor/inference/synthetic_method_scanner.rb +4 -6
  240. data/lib/rigor/inference/version_guard.rb +28 -21
  241. data/lib/rigor/inference/void_origin.rb +3 -3
  242. data/lib/rigor/inference/void_tail_summary.rb +2 -4
  243. data/lib/rigor/language_server/buffer_table.rb +4 -4
  244. data/lib/rigor/language_server/completion_provider.rb +1 -1
  245. data/lib/rigor/language_server/debouncer.rb +1 -1
  246. data/lib/rigor/language_server/diagnostic_publisher.rb +4 -4
  247. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  248. data/lib/rigor/language_server/folding_range_provider.rb +1 -1
  249. data/lib/rigor/language_server/hover_provider.rb +1 -1
  250. data/lib/rigor/language_server/hover_renderer.rb +2 -2
  251. data/lib/rigor/language_server/incremental_sync.rb +6 -6
  252. data/lib/rigor/language_server/project_context.rb +2 -5
  253. data/lib/rigor/language_server/publish_batcher.rb +2 -2
  254. data/lib/rigor/language_server/selection_range_provider.rb +2 -2
  255. data/lib/rigor/language_server/server.rb +9 -9
  256. data/lib/rigor/language_server/signature_help_provider.rb +1 -1
  257. data/lib/rigor/language_server/uri.rb +1 -1
  258. data/lib/rigor/plugin/base.rb +90 -21
  259. data/lib/rigor/plugin/bundled_catalog.rb +167 -0
  260. data/lib/rigor/plugin/effect_attribution.rb +7 -7
  261. data/lib/rigor/plugin/effect_entry_points.rb +3 -3
  262. data/lib/rigor/plugin/fact_store.rb +6 -6
  263. data/lib/rigor/plugin/io_boundary.rb +93 -8
  264. data/lib/rigor/plugin/isolation.rb +29 -6
  265. data/lib/rigor/plugin/loader.rb +31 -12
  266. data/lib/rigor/plugin/macro/heredoc_template.rb +1 -1
  267. data/lib/rigor/plugin/macro/trait_registry.rb +1 -1
  268. data/lib/rigor/plugin/manifest.rb +23 -1
  269. data/lib/rigor/plugin/registry.rb +67 -14
  270. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +12 -1
  271. data/lib/rigor/plugin/trust_policy.rb +2 -4
  272. data/lib/rigor/plugin/type_node_resolver.rb +3 -3
  273. data/lib/rigor/plugin_gap_advisory.rb +96 -0
  274. data/lib/rigor/project_environment.rb +138 -0
  275. data/lib/rigor/protection/analysis_guard.rb +93 -14
  276. data/lib/rigor/protection/closure_kill_oracle.rb +67 -23
  277. data/lib/rigor/protection/dependency_closure.rb +5 -8
  278. data/lib/rigor/protection/diagnostic_oracle.rb +2 -2
  279. data/lib/rigor/protection/discovery_seed.rb +11 -11
  280. data/lib/rigor/protection/kill_signature.rb +2 -4
  281. data/lib/rigor/protection/measurement_integrity.rb +1 -3
  282. data/lib/rigor/protection/mutation_cache.rb +6 -9
  283. data/lib/rigor/protection/mutation_scanner.rb +12 -15
  284. data/lib/rigor/protection/mutator.rb +2 -1
  285. data/lib/rigor/protection/test_suite_oracle.rb +5 -5
  286. data/lib/rigor/rbs_extended/conformance_checker.rb +4 -3
  287. data/lib/rigor/rbs_extended/envelope_scanner.rb +12 -13
  288. data/lib/rigor/rbs_extended/hkt_directives.rb +16 -1
  289. data/lib/rigor/rbs_extended/reporter.rb +122 -16
  290. data/lib/rigor/rbs_extended.rb +67 -22
  291. data/lib/rigor/reflection/constant_path.rb +126 -0
  292. data/lib/rigor/reflection.rb +26 -10
  293. data/lib/rigor/runtime/jit.rb +6 -8
  294. data/lib/rigor/scope/discovery_index.rb +30 -0
  295. data/lib/rigor/scope.rb +203 -4
  296. data/lib/rigor/sig_gen/generator.rb +181 -102
  297. data/lib/rigor/sig_gen/layout_index.rb +3 -4
  298. data/lib/rigor/sig_gen/meta_class_shape.rb +3 -5
  299. data/lib/rigor/sig_gen/observation_collector.rb +17 -15
  300. data/lib/rigor/sig_gen/path_mapper.rb +4 -6
  301. data/lib/rigor/sig_gen/rbs_validity.rb +4 -4
  302. data/lib/rigor/sig_gen/renderer.rb +17 -11
  303. data/lib/rigor/sig_gen/skip_reason_catalog.rb +111 -0
  304. data/lib/rigor/sig_gen/superclass_spelling.rb +27 -0
  305. data/lib/rigor/sig_gen/type_elaborator.rb +1 -3
  306. data/lib/rigor/sig_gen/writer.rb +23 -6
  307. data/lib/rigor/signature_path_audit.rb +153 -6
  308. data/lib/rigor/source/literals.rb +0 -23
  309. data/lib/rigor/source/node_children.rb +0 -2
  310. data/lib/rigor/source/node_locator.rb +5 -11
  311. data/lib/rigor/source/node_walker.rb +2 -5
  312. data/lib/rigor/triage/catalogue.rb +1 -3
  313. data/lib/rigor/triage.rb +3 -5
  314. data/lib/rigor/type/accepts_result.rb +24 -6
  315. data/lib/rigor/type/combinator.rb +68 -5
  316. data/lib/rigor/type/data_class.rb +2 -2
  317. data/lib/rigor/type/data_instance.rb +4 -4
  318. data/lib/rigor/type/float_range.rb +128 -0
  319. data/lib/rigor/type/hash_shape.rb +5 -5
  320. data/lib/rigor/type/integer_range.rb +13 -8
  321. data/lib/rigor/type/nominal.rb +8 -2
  322. data/lib/rigor/type/refined.rb +3 -3
  323. data/lib/rigor/type/struct_class.rb +3 -3
  324. data/lib/rigor/type/struct_instance.rb +4 -4
  325. data/lib/rigor/type.rb +1 -0
  326. data/lib/rigor/type_node/generic.rb +1 -1
  327. data/lib/rigor/type_node/range_literal.rb +25 -0
  328. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  329. data/lib/rigor/type_node.rb +1 -0
  330. data/lib/rigor/version.rb +1 -1
  331. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +0 -4
  332. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +0 -1
  333. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +0 -2
  334. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -0
  335. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +0 -4
  336. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +3 -5
  337. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +1 -4
  338. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +1 -0
  339. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +33 -30
  340. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +0 -1
  341. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -3
  342. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +28 -2
  343. data/plugins/rigor-actionpack/sig/action_controller.rbs +71 -0
  344. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +0 -4
  345. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
  346. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +0 -1
  347. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +0 -2
  348. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/recurring_scan.rb +1 -1
  349. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +1 -0
  350. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +3 -4
  351. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +1 -2
  352. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/structure_sql_parser.rb +1 -2
  353. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +42 -7
  354. data/plugins/rigor-activerecord/sig/active_record/framework.rbs +123 -0
  355. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +1 -0
  356. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +9 -2
  357. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +119 -14
  358. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +1 -0
  359. data/plugins/rigor-dry-monads/lib/rigor/plugin/dry_monads.rb +1 -0
  360. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +9 -7
  361. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +7 -3
  362. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +1 -0
  363. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +8 -6
  364. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +11 -7
  365. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -11
  366. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +9 -4
  367. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +1 -0
  368. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +0 -5
  369. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +0 -1
  370. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +0 -1
  371. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +8 -1
  372. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +36 -4
  373. data/plugins/rigor-ffi-rzmq/lib/rigor/plugin/ffi_rzmq.rb +1 -0
  374. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +8 -6
  375. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +6 -3
  376. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +1 -0
  377. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -1
  378. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +0 -3
  379. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +1 -0
  380. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +0 -5
  381. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/authorization_scan.rb +2 -2
  382. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +0 -1
  383. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +0 -1
  384. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +1 -0
  385. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +0 -12
  386. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +1 -3
  387. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +0 -1
  388. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +1 -0
  389. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/acronyms.rb +5 -5
  390. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -10
  391. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +3 -3
  392. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +2 -2
  393. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +2 -2
  394. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +2 -2
  395. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +9 -10
  396. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +2 -3
  397. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -0
  398. data/plugins/rigor-railties/lib/rigor/plugin/railties.rb +1 -0
  399. data/plugins/rigor-rbnacl/lib/rigor/plugin/rbnacl.rb +13 -1
  400. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +128 -3
  401. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +0 -3
  402. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +0 -3
  403. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +1 -5
  404. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +2 -3
  405. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +1 -0
  406. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +0 -3
  407. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +1 -0
  408. data/plugins/rigor-sassc/lib/rigor/plugin/sassc.rb +1 -0
  409. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +1 -9
  410. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +1 -0
  411. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +0 -4
  412. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/effects.rb +1 -1
  413. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/schedule_scan.rb +1 -1
  414. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +0 -1
  415. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +0 -2
  416. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +1 -0
  417. data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +1 -0
  418. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +2 -5
  419. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +1 -4
  420. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +3 -9
  421. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +4 -4
  422. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +1 -2
  423. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +4 -5
  424. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +7 -5
  425. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +5 -5
  426. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +1 -0
  427. data/sig/rigor/analysis/reachability/scan_cache.rbs +12 -0
  428. data/sig/rigor/cache.rbs +2 -3
  429. data/sig/rigor/environment.rbs +11 -0
  430. data/sig/rigor/inference.rbs +3 -10
  431. data/sig/rigor/plugin/io_boundary.rbs +4 -0
  432. data/sig/rigor/plugin/manifest.rbs +2 -1
  433. data/sig/rigor/reflection.rbs +3 -1
  434. data/sig/rigor/scope.rbs +24 -3
  435. data/sig/rigor/sig_gen/skip_reason_catalog.rbs +14 -0
  436. data/sig/rigor/type.rbs +27 -1
  437. data/sig/rigor.rbs +1 -0
  438. data/skills/rigor-ask/SKILL.md +8 -6
  439. data/skills/rigor-plugin-author/references/02-walker-and-types.md +13 -2
  440. data/skills/rigor-project-init/SKILL.md +6 -3
  441. data/skills/rigor-project-init/references/02-configure.md +10 -6
  442. data/skills/rigor-project-init/references/06-agent-contract.md +67 -0
  443. data/skills/rigor-type-oracle/SKILL.md +228 -0
  444. data/skills/rigor-type-oracle/references/01-oracle-commands.md +261 -0
  445. data/skills/rigor-type-oracle/references/02-agents-md-fragment.md +52 -0
  446. data/skills/rigor-type-oracle/references/03-gap-protocol.md +128 -0
  447. data/skills/rigor-unused-adjudicate/SKILL.md +8 -5
  448. metadata +28 -3
  449. data/lib/rigor/cli/probe_environment.rb +0 -85
  450. data/sig/rigor/inference/builtins/numeric_catalog.rbs +0 -3
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rbs"
4
+
5
+ module Rigor
6
+ module Cache
7
+ # The `RBS::AST::Annotation` location carry (issue #799): the two halves of the `marshal_dump` /
8
+ # `marshal_load` pair {file:lib/rigor/cache/rbs_environment_marshal_patch.rb the env-cache Marshal patch}
9
+ # installs on `RBS::AST::Annotation`, plus the buffer a reconstructed location points at. That file's
10
+ # header carries the why; this one is the how.
11
+ #
12
+ # Nothing here reconstructs the annotation's SOURCE — the cached env has no file contents, and a
13
+ # consumer that wants the text already has `Annotation#string`. What it reconstructs is the pair of
14
+ # `(line, column)` answers `RBS::Location` gives, so a diagnostic positioned on the directive reads the
15
+ # same line warm as it does cold.
16
+ module AnnotationLocation
17
+ # The two positions {Buffer} distinguishes. `RBS::Location` addresses its buffer by character
18
+ # offset, and the reconstruction has no content to offset into, so the offsets are used as tags:
19
+ # 0 is "the start pair", 1 is "the end pair".
20
+ START_POS = 0
21
+ END_POS = 1
22
+
23
+ # A content-less `RBS::Buffer` that answers `pos_to_loc` from the pairs the cold parse had.
24
+ #
25
+ # Answering there is all it takes: `RBS::Location#start_line` and its three siblings are C methods
26
+ # that resolve the offset through `buffer.pos_to_loc`, so the reconstructed Location reports the real
27
+ # position without the buffer having to hold (or synthesize) the file it came from. The alternative —
28
+ # a synthetic content string of `line - 1` newlines — is exact only while an annotation stays on one
29
+ # line, and `%a{...}` may span two.
30
+ class Buffer < ::RBS::Buffer
31
+ def initialize(name:, start_loc:, end_loc:)
32
+ super(name: name, content: "")
33
+ @start_loc = start_loc
34
+ @end_loc = end_loc
35
+ end
36
+
37
+ def pos_to_loc(pos)
38
+ pos == START_POS ? @start_loc : @end_loc
39
+ end
40
+ end
41
+
42
+ module_function
43
+
44
+ # @return `[name, start_line, start_column, end_line, end_column]`, or nil when there is
45
+ # no location to carry. Fail-soft: a location whose buffer cannot answer is dumped as nil rather
46
+ # than failing the whole environment's dump.
47
+ def dump(location)
48
+ return nil if location.nil?
49
+
50
+ buffer = location.buffer
51
+ name = buffer.respond_to?(:name) ? buffer.name.to_s : nil
52
+ [name, location.start_line, location.start_column, location.end_line, location.end_column]
53
+ rescue StandardError
54
+ nil
55
+ end
56
+
57
+ # @param payload — what {dump} produced.
58
+ def load(payload)
59
+ return nil if payload.nil?
60
+
61
+ name, start_line, start_column, end_line, end_column = payload
62
+ name = ::RBS::Location::CACHED_BUFFER_NAME if name.nil? || name.empty?
63
+ buffer = Buffer.new(name: name, start_loc: [start_line, start_column], end_loc: [end_line, end_column])
64
+ ::RBS::Location.new(buffer: buffer, start_pos: START_POS, end_pos: END_POS)
65
+ rescue StandardError
66
+ nil
67
+ end
68
+ end
69
+ end
70
+ end
@@ -48,7 +48,12 @@ 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
+ SCHEMA_VERSION = 9
52
57
 
53
58
  # Per-slot entry value objects. Constructors validate enums / required fields and freeze the resulting
54
59
  # struct so no caller can mutate after the entry is in a Descriptor.
@@ -114,7 +119,7 @@ module Rigor
114
119
  new(path: path, comparator: :exists, value: PRESENT)
115
120
  end
116
121
 
117
- # @return [Boolean] whether this row records the path as absent (see {.absent}).
122
+ # @return whether this row records the path as absent (see {.absent}).
118
123
  def absent?
119
124
  comparator == :exists && value == ABSENT
120
125
  end
@@ -235,31 +240,58 @@ module Rigor
235
240
  FIELD_SEPARATOR = "\0"
236
241
  private_constant :ROW_SEPARATOR, :FIELD_SEPARATOR
237
242
 
243
+ # `:stat` is the ADR-87 WD2 signature above — it answers "did anything under this glob change",
244
+ # appearance and edit alike. `:names` (#979) answers only "did the SET of matching paths change":
245
+ # its rows are the sorted relative names, so a file whose bytes are unchanged never moves the
246
+ # signature however its stat tuple moved. A row exists to catch appearance/disappearance only —
247
+ # edits to the files it lists are already carried by their own `:stat` {FileEntry} rows — and a
248
+ # names row must therefore survive what those rows survive. A `:stat` glob over a signature tree
249
+ # would not: ADR-87 WD1 lets a `:stat` file row fall back to its recorded content digest when the
250
+ # tuple moves, so a `git checkout`, a `bundle install` or a CI cache restored over a fresh checkout
251
+ # keeps the file rows fresh while it would move every glob signature, turning every such run into a
252
+ # full re-analysis. `:names` also reads no stat at all, so it costs a `Dir.glob` and nothing else.
253
+ VALID_MODES = %i[stat names].freeze
254
+
238
255
  attr_reader :root, :pattern, :value
239
256
 
240
- value_fields :root, :pattern, :value
257
+ value_fields :root, :pattern, :mode, :value
258
+
259
+ def initialize(root:, pattern:, value:, mode: :stat)
260
+ raise ArgumentError, "GlobEntry mode must be one of #{VALID_MODES.inspect}, got #{mode.inspect}" \
261
+ unless VALID_MODES.include?(mode)
241
262
 
242
- def initialize(root:, pattern:, value:)
243
263
  @root = root.to_s.dup.freeze
244
264
  @pattern = pattern.to_s.dup.freeze
265
+ @mode = mode
245
266
  @value = value.to_s.dup.freeze
246
267
  freeze
247
268
  end
248
269
 
270
+ # Defaulted rather than an `attr_reader` because an entry Marshal-loaded from a store written before
271
+ # the field existed carries no `@mode` ivar; every such entry was recorded under the stat signature.
272
+ def mode
273
+ @mode || :stat
274
+ end
275
+
249
276
  # 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))
277
+ def self.compute(root:, pattern:, mode: :stat)
278
+ new(root: root, pattern: pattern, mode: mode,
279
+ value: signature_for(root: root, pattern: pattern, mode: mode))
252
280
  end
253
281
 
254
282
  # The aggregate signature the entry's `value` carries: SHA-256 over the sorted per-file rows. In the
255
283
  # default (`:stat`) mode a row is the file's `(path, size, mtime_ns, ctime_ns, inode)` tuple — NO
256
284
  # content read; in strict mode a row is `(path, content-sha256)`, restoring the pre-ADR-87 authority.
285
+ # In `:names` mode a row is the path alone, stat-free, so only the matching SET moves the signature.
257
286
  # Per-file stat failures (a file vanishing between the glob and the stat) drop the row — same race
258
287
  # posture as {Descriptor#file_entry_fresh?}. `Dir.glob` returns sorted entries by default so the row
259
288
  # order — and therefore the signature — is stable.
260
- def self.signature_for(root:, pattern:)
289
+ def self.signature_for(root:, pattern:, mode: :stat)
290
+ paths = Dir.glob(File.join(root, pattern))
291
+ return Digest::SHA256.hexdigest(paths.map { |path| "#{path}#{ROW_SEPARATOR}" }.join) if mode == :names
292
+
261
293
  strict = FileDigest.strict_validation?
262
- rows = Dir.glob(File.join(root, pattern)).filter_map do |path|
294
+ rows = paths.filter_map do |path|
263
295
  st = File.stat(path)
264
296
  next nil unless st.file?
265
297
 
@@ -276,23 +308,30 @@ module Rigor
276
308
  end
277
309
 
278
310
  # 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.
311
+ # bytes are read in the default mode, and no stat at all in `:names`. Any failure reads as stale
312
+ # (recompute), never a crash. A stored signature recorded under the opposite validation mode simply
313
+ # mismatches and recomputes.
281
314
  def self.fresh?(entry)
282
- signature_for(root: entry.root, pattern: entry.pattern) == entry.value
315
+ signature_for(root: entry.root, pattern: entry.pattern, mode: entry.mode) == entry.value
283
316
  rescue StandardError
284
317
  false
285
318
  end
286
319
 
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.
320
+ # Composition key — {.compose} unions per (root, pattern, mode) slot; two contributions for the same
321
+ # slot must agree on the value or {Conflict} is raised. Within one run the same glob stat-reads
322
+ # identically, so contributions never conflict. The mode is part of the key because the two signatures
323
+ # over one (root, pattern) are different questions, not rival answers to the same one.
290
324
  def slot_key
291
- "#{root}\0#{pattern}"
325
+ "#{root}\0#{pattern}\0#{mode}"
292
326
  end
293
327
 
294
328
  def to_h
295
- { "root" => root, "pattern" => pattern, "value" => value }
329
+ h = { "root" => root, "pattern" => pattern, "value" => value }
330
+ # Omitted in the default mode so a descriptor carrying only stat globs keeps its pre-#979 canonical
331
+ # bytes: this FIELD moves no producer's key. (Every key still moved once with SCHEMA_VERSION 9,
332
+ # which the storage layer mixes in; that is the #979 migration, not the mode.)
333
+ h["mode"] = mode.to_s unless mode == :stat
334
+ h
296
335
  end
297
336
  end
298
337
 
@@ -352,9 +391,8 @@ module Rigor
352
391
  dependencies: dependencies, globs: globs)
353
392
  end
354
393
 
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
394
+ # @param params — inputs the producer was called with
395
+ # @return hex SHA-256 cache key for the value
358
396
  def cache_key_for(producer_id:, params: {})
359
397
  payload = {
360
398
  "schema_version" => SCHEMA_VERSION,
@@ -377,7 +415,7 @@ module Rigor
377
415
  "dependencies" => sort_entries(dependencies, "gem_name").map(&:to_h),
378
416
  "files" => sort_entries(files, "path").map(&:to_h),
379
417
  "gems" => sort_entries(gems, "name").map(&:to_h),
380
- "globs" => globs.sort_by { |e| [e.root, e.pattern] }.map(&:to_h),
418
+ "globs" => globs.sort_by { |e| [e.root, e.pattern, e.mode.to_s] }.map(&:to_h),
381
419
  "plugins" => sort_entries(plugins, "id").map(&:to_h)
382
420
  }
383
421
  end
@@ -65,15 +65,15 @@ module Rigor
65
65
 
66
66
  module_function
67
67
 
68
- # @return [String] the gem root — the directory holding `lib/`, three levels above this file.
68
+ # @return the gem root — the directory holding `lib/`, three levels above this file.
69
69
  def root
70
70
  @root ||= File.expand_path("../../..", __dir__)
71
71
  end
72
72
 
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
73
+ # @param root — the gem root, defaulted through {.root} so a spec can relocate the tree.
74
+ # @return a digest identifying the engine's current source, or nil when the tree is
75
75
  # version-pinned and the caller should add no slot at all.
76
- # @raise [Unavailable] when a mutable tree's source cannot be read.
76
+ # @raise Unavailable — when a mutable tree's source cannot be read.
77
77
  def identity(root = self.root)
78
78
  return nil if version_pinned?(root)
79
79
 
@@ -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,29 @@ 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
+ SCHEMA = 22
88
110
 
89
111
  # The persisted per-file state.
90
112
  # `cache` maps an analyzed file to its diagnostics.
@@ -136,11 +158,21 @@ module Rigor
136
158
  # collection was off). It is a SEPARATE gate from the global `fingerprint`: the `effects:` block is
137
159
  # deliberately absent from `Configuration#to_h`, so turning collection on invalidates no diagnostics,
138
160
  # and a vocabulary / catalogue / `effects:` change must invalidate the summaries alone.
161
+ # Issues #796 / #794 — the run-level rows a narrowed run replays instead of re-deriving; see
162
+ # docs/internal-spec/cache.md § "Run-level rows the snapshot carries" for why exactly these two are
163
+ # fresh under the fingerprint and why no other `.rigor.yml`-level row may join them.
139
164
  Payload = Data.define(:cache, :sources, :digests, :analyzed,
140
165
  :symbol_sources, :ancestry_sources, :symbol_fingerprints,
141
166
  :missing, :class_decls, :constant_decls, :seed_bundles, :plugin_fact_digest,
142
167
  :return_summaries, :param_table,
143
- :effect_collections, :effects_identity)
168
+ :effect_collections, :effects_identity, :run_level_rows)
169
+
170
+ # Issues #796 / #794 — the two run-level rows the snapshot carries. `definition_build_failures` is the
171
+ # reported per-class detail list ({Environment::RbsLoader#definition_build_failures}); `hkt_scan_failure`
172
+ # is the `[error_class, first_line, frame, stage]` tuple ({Rigor::Environment#hkt_scan_failure}), nil
173
+ # when the scan succeeded — which is why the presence of this object, not the tuple, is what says a
174
+ # replay is available.
175
+ RunLevelRows = Data.define(:definition_build_failures, :hkt_scan_failure)
144
176
 
145
177
  # The global fingerprint that gates a snapshot load: a digest of the inputs whose change requires a full
146
178
  # rebuild — the engine version + schema, the engine's own SOURCE when the version does not pin it, the
@@ -222,8 +254,8 @@ module Rigor
222
254
  # The fingerprint that matched is returned alongside the payload so the caller can mix it into ITS own
223
255
  # cache key — the snapshot's identity is exactly what "these dependency edges came from that world" means.
224
256
  #
225
- # @param fingerprints [Array<String>] candidates, most-specific first.
226
- # @return [Array(String, Payload), nil] `[matched fingerprint, payload]`, or nil on any miss.
257
+ # @param fingerprints — candidates, most-specific first.
258
+ # @return `[matched fingerprint, payload]`, or nil on any miss.
227
259
  def load_any(fingerprints:)
228
260
  data = read_data
229
261
  return nil if data.nil?
@@ -259,7 +291,8 @@ module Rigor
259
291
  return_summaries: data[:return_summaries] || {},
260
292
  param_table: data[:param_table] || {},
261
293
  effect_collections: data[:effect_collections] || {},
262
- effects_identity: data[:effects_identity]
294
+ effects_identity: data[:effects_identity],
295
+ run_level_rows: data[:run_level_rows]
263
296
  )
264
297
  end
265
298
  private :payload_from
@@ -283,7 +316,8 @@ module Rigor
283
316
  return_summaries: payload.return_summaries,
284
317
  param_table: payload.param_table,
285
318
  effect_collections: payload.effect_collections,
286
- effects_identity: payload.effects_identity
319
+ effects_identity: payload.effects_identity,
320
+ run_level_rows: payload.run_level_rows
287
321
  )
288
322
  blob = Zlib::Deflate.deflate(raw)
289
323
  tmp = "#{@path}.#{Process.pid}.tmp"
@@ -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|
@@ -12,13 +12,11 @@ module Rigor
12
12
  # slots, so factoring the construction here keeps the producers small and ensures invalidation behaves
13
13
  # identically across them.
14
14
  module RbsDescriptor
15
- # @param loader [Rigor::Environment::RbsLoader]
16
- # @return [Rigor::Cache::Descriptor]
17
15
  def self.build(loader)
18
16
  Descriptor.new(
19
17
  gems: [rbs_gem_entry],
20
18
  files: file_entries(loader),
21
- configs: config_entries(loader)
19
+ configs: config_entries(loader) + env_only_config_entries(loader)
22
20
  )
23
21
  end
24
22
 
@@ -26,8 +24,9 @@ module Rigor
26
24
  # `gems` + `configs` ({Runner#run_key_descriptor}); the RBS signature-tree `files` are read solely on a
27
25
  # MISS, by the dependency descriptor ({Runner#run_dependency_descriptor}). So a warm HIT never digests
28
26
  # 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).
27
+ # or `==`'d, only its four readers are consulted — so deferring `files` costs no soundness, and `gems`
28
+ # / `configs` match {.build}'s shared slots (the key is unchanged; the one slot the env key adds on
29
+ # top, {.env_only_config_entries}, is never read here).
31
30
  def self.build_run(loader)
32
31
  RunDescriptor.new(loader: loader, gems: [rbs_gem_entry], configs: config_entries(loader))
33
32
  end
@@ -38,19 +37,47 @@ module Rigor
38
37
  [libraries_entry(loader.libraries), virtual_rbs_entry(loader)].compact
39
38
  end
40
39
 
40
+ # Issue #610 — the slots only the env-cache KEY reads, on top of {.config_entries}. Which of the
41
+ # loader's `signature_paths:` are DEFERRED (a bundled plugin's `sig/`, loaded last and allowed to
42
+ # stand down against a colliding generic arity) changes the env built from byte-identical files, so
43
+ # the partition belongs in the key: moving a plugin's `sig/` between `plugins:` and an explicit
44
+ # `signature_paths:` entry changes no file, and an env cached before the producer honoured the
45
+ # partition must read as a miss once. Kept OUT of {.config_entries} deliberately — the run-result
46
+ # key already digests the whole resolved configuration (`RunCacheKey`), and its boot-slimming probe
47
+ # reconstructs {.config_entries} without loading a plugin, so an entry there that it cannot rebuild
48
+ # would turn the warm fast lane into a permanent miss on every project that enables a bundled plugin.
49
+ def self.env_only_config_entries(loader)
50
+ [deferred_signature_paths_entry(loader)].compact
51
+ end
52
+
53
+ def self.deferred_signature_paths_entry(loader)
54
+ return nil unless loader.respond_to?(:deferred_signature_paths)
55
+
56
+ deferred = Array(loader.deferred_signature_paths).map(&:to_s).sort
57
+ return nil if deferred.empty?
58
+
59
+ Descriptor::ConfigEntry.new(
60
+ key: "rbs.deferred_signature_paths",
61
+ value_hash: Digest::SHA256.hexdigest(deferred.join("\n"))
62
+ )
63
+ end
64
+
65
+ private_class_method :deferred_signature_paths_entry
66
+
41
67
  # Public (ADR-87 WD4) so the boot-slimming run-cache probe reconstructs the identical `gems` +
42
68
  # `rbs.libraries` key slots the runner writes, from the config-derived library list, without a loader.
43
69
  def self.rbs_gem_entry
44
70
  Descriptor::GemEntry.new(name: "rbs", requirement: ">= 0", locked: ::RBS::VERSION.to_s)
45
71
  end
46
72
 
47
- # @param comparator [Symbol] `:digest` (default) for the env-cache KEY descriptor ({.build}), where the
73
+ # @param comparator — `:digest` (default) for the env-cache KEY descriptor ({.build}), where the
48
74
  # value must be deterministic; `:stat` (ADR-87 WD1) for the validation-only run-dependency descriptor
49
75
  # ({RunDescriptor#files}), where the stat tier short-circuits the SHA-256 on an unmoved file.
50
76
  def self.file_entries(loader, comparator: :digest)
51
77
  roots = loader.signature_paths +
52
78
  Rigor::Environment::RbsLoader.vendored_gem_sig_paths +
53
- Rigor::Environment::RbsLoader.core_overlay_sig_paths
79
+ Rigor::Environment::RbsLoader.core_overlay_sig_paths +
80
+ Rigor::Environment::RbsLoader.capability_role_sig_paths
54
81
  roots.flat_map do |root|
55
82
  next [] unless root.directory?
56
83
 
@@ -65,7 +92,40 @@ module Rigor
65
92
  end
66
93
  end
67
94
 
68
- # @param library_names [Array<String, Symbol>] the loader's merged library list (or, on the WD4 probe
95
+ # Issue #979 — the directory-LISTING rows for the signature roots, one {Descriptor::GlobEntry} per
96
+ # root over `**/*.rbs`. {.file_entries} answers "did any signature file I read change", which is a
97
+ # question only about files that existed when the run recorded its dependencies; a `sig/roles.rbs`
98
+ # written afterwards is in no row, so the ADR-45 run-result slot validated fresh and the warm run kept
99
+ # serving diagnostics computed without it. A glob row is the same edge {Plugin::IoBoundary} records
100
+ # for a listed directory (#954): re-globbing on the next run sees the appearance, and one row per root
101
+ # (not per file) keeps the cost a single `Dir.glob`.
102
+ #
103
+ # The rows are `:names` mode, not the default `:stat`: the question they add is which signature files
104
+ # EXIST, and every edit to one of them is already carried by that file's own ADR-87 WD1 `:stat` row —
105
+ # which survives a moved stat tuple by falling back to its recorded content digest. A stat-mode glob
106
+ # would not, so a `git checkout`, a `bundle install`, or a CI run with a restored cache over a fresh
107
+ # checkout — none of which changes a byte of RBS — would turn every warm run into a full re-analysis.
108
+ # Names mode also reads no stat, so it neither pays for nor perturbs the per-run validation-stat memo.
109
+ #
110
+ # Scoped to the loader's own roots — the project's `signature_paths:` (including the auto-detected
111
+ # `sig/`), the bundled-plugin `sig/` trees (ADR-25), the bundle walk, `rbs collection`, the ADR-72
112
+ # overlays. Rigor's own `data/` trees ({RbsLoader.vendored_gem_sig_paths}, `.core_overlay_sig_paths`,
113
+ # `.capability_role_sig_paths`) are engine-owned: a file appears under one only when the engine tree is
114
+ # edited, which normally arrives with the `lib/` change that reads it and so re-keys every
115
+ # computed-value slot through {EngineSource}. They get file rows and no glob — a glob there would cost
116
+ # every project a stat walk of the vendored gem-signature tree per warm run to catch an
117
+ # engine-development edit.
118
+ def self.glob_entries(loader)
119
+ # Deduplicated by path: a root reachable twice (an explicit `signature_paths:` entry that is also a
120
+ # bundled plugin's `sig/`, #610) would otherwise contribute two rows whose stat signatures race.
121
+ loader.signature_paths.uniq(&:to_s).filter_map do |root|
122
+ next unless root.directory?
123
+
124
+ Descriptor::GlobEntry.compute(root: root.to_s, pattern: File.join("**", "*.rbs"), mode: :names)
125
+ end
126
+ end
127
+
128
+ # @param library_names — the loader's merged library list (or, on the WD4 probe
69
129
  # path, `Environment::DEFAULT_LIBRARIES + config.libraries` reconstructed without a loader).
70
130
  def self.libraries_entry(library_names)
71
131
  sorted = library_names.map(&:to_s).sort
@@ -93,10 +153,11 @@ module Rigor
93
153
 
94
154
  private_class_method :virtual_rbs_entry
95
155
 
96
- # The lazy-files run descriptor {RbsDescriptor.build_run} returns. Exposes the three readers the
156
+ # The lazy-files run descriptor {RbsDescriptor.build_run} returns. Exposes the four readers the
97
157
  # 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.
158
+ # and are cheap); `files` (the RBS signature-tree digests) and `globs` (#979, the per-root listing
159
+ # rows) are read only on a MISS, computed once on first access and memoised, so a warm HIT pays for
160
+ # neither.
100
161
  class RunDescriptor
101
162
  attr_reader :gems, :configs
102
163
 
@@ -106,6 +167,13 @@ module Rigor
106
167
  @configs = configs
107
168
  end
108
169
 
170
+ # Issue #979 — one directory-listing row per signature root, so a `.rbs` file APPEARING under one
171
+ # (or vanishing from it) invalidates the run-result slot. Memoised for the same reason `files` is:
172
+ # a collecting run asks twice about the same post-run world.
173
+ def globs
174
+ @globs ||= RbsDescriptor.glob_entries(@loader)
175
+ end
176
+
109
177
  def files
110
178
  # ADR-87 WD1 — this descriptor is validated (never a cache key), so the RBS signature tree rides the
111
179
  # 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
 
@@ -2,6 +2,8 @@
2
2
 
3
3
  require "rbs"
4
4
 
5
+ require_relative "annotation_location"
6
+
5
7
  # Adds `_dump` / `_load` to {RBS::Location} so an `RBS::Environment` (and its transitive AST nodes, all of
6
8
  # which carry Locations) round-trips through `Marshal`. The rbs gem's C-extension `RBS::Location` ships
7
9
  # without the Marshal hooks; until rbs grows them upstream this patch is the minimal monkey-patch the v0.0.9
@@ -31,6 +33,25 @@ require "rbs"
31
33
  # than a cold run does. ADR-6's store never evicts, so that would persist. `Cache::Store::FORMAT_VERSION`
32
34
  # is therefore bumped to 3: `PAYLOAD_ABI_VERSION` already rebuilds across a release, and the bump closes
33
35
  # the same-version window too.
36
+ # - `RBS::AST::Annotation` carries its own `marshal_dump` / `marshal_load`, which keep the annotation's
37
+ # POSITION as well as its file (issue #799). "Nothing reads positions" was never quite true: the two
38
+ # `conforms-to` rows — `rbs_extended.unsatisfied-conformance` and `dynamic.rbs-extended.unresolved` —
39
+ # are reported AT the directive, because there is no Ruby `def` a missing interface method could be
40
+ # reported at, so both read `start_line` / `start_column` off an annotation's location. (They are the
41
+ # only readers: {Rigor::RbsExtended::EnvelopeScanner} positions `effect.unknown-label` at an annotation
42
+ # too, but reaches it by parsing the project's own `.rbs` rather than the built env — refusing this very
43
+ # loss is one of the two reasons it does.) Cold the row is `sig/buffer.rbs:6:1`; through a `_dump`ed
44
+ # location it collapsed to `1:1`, which made `--verify-incremental` fail outright on any project carrying
45
+ # an unsatisfied directive (the replica normalises rows by position, and only one side of the comparison
46
+ # runs against the cached env) and made a warm `--incremental` run move a row on a tree that had not
47
+ # changed. The carry is scoped to annotations rather than done in `_dump` for the reason the paragraph
48
+ # above gives: a Location hangs off every AST node, and carrying its position measured +2.9% on Rigor's
49
+ # own env (9,941K to 10,229K) on top of the names. An annotation is rare by comparison — 18 of them in
50
+ # that same env — so the same carry costs +1.4K there, which is the whole argument for scoping it here
51
+ # rather than widening `_dump`. `Cache::Store::FORMAT_VERSION` is bumped to 4 for the same reason it was
52
+ # bumped to 3: a pre-change blob still loads (Marshal encodes an ivar dump and a `marshal_dump` payload
53
+ # differently, and only the latter reaches `marshal_load`), so without the bump a stale blob would keep
54
+ # reporting the moved position indefinitely.
34
55
  #
35
56
  # Idempotent: the guard checks `method_defined?(:_dump)` so requiring this file twice (or against an upstream
36
57
  # rbs that adds Marshal hooks itself) is a no-op.
@@ -67,6 +88,24 @@ module RBS
67
88
  end
68
89
  end
69
90
 
91
+ module AST
92
+ class Annotation
93
+ # Carries the annotation's POSITION across the cache, not just its file. See the header's fourth
94
+ # bullet for why this one AST node opts out of the position-dropping `RBS::Location#_dump` above.
95
+ unless method_defined?(:marshal_dump)
96
+ def marshal_dump
97
+ [string, Rigor::Cache::AnnotationLocation.dump(location)]
98
+ end
99
+
100
+ def marshal_load(payload)
101
+ dumped_string, dumped_location = payload
102
+ @string = dumped_string
103
+ @location = Rigor::Cache::AnnotationLocation.load(dumped_location)
104
+ end
105
+ end
106
+ end
107
+ end
108
+
70
109
  class Namespace
71
110
  unless method_defined?(:_dump)
72
111
  def _dump(_)
@@ -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 }