rigortype 0.4.6 → 0.4.7

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 (417) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  3. data/data/gem_overlay/activesupport/core_ext.rbs +16 -13
  4. data/data/vendored_gem_sigs/bundler/bundler.rbs +1 -1
  5. data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +4 -3
  6. data/docs/handbook/01-getting-started.md +5 -2
  7. data/docs/handbook/11-sig-gen.md +50 -3
  8. data/docs/handbook/appendix-phpstan.md +4 -4
  9. data/docs/manual/02-cli-reference.md +152 -26
  10. data/docs/manual/03-configuration.md +36 -3
  11. data/docs/manual/06-baseline.md +6 -4
  12. data/docs/manual/09-editor-integration.md +13 -2
  13. data/docs/manual/10-mcp-server.md +38 -2
  14. data/docs/manual/12-caching.md +4 -3
  15. data/docs/manual/plugins/rigor-actionpack.md +19 -11
  16. data/docs/manual/plugins/rigor-activesupport-core-ext.md +20 -1
  17. data/lib/rigor/analysis/baseline.rb +24 -27
  18. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +5 -6
  19. data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +2 -2
  20. data/lib/rigor/analysis/check_rules/declaration_sourced_guard.rb +4 -4
  21. data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +2 -2
  22. data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +85 -13
  23. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +3 -2
  24. data/lib/rigor/analysis/check_rules/lexical_method_sites.rb +0 -1
  25. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +5 -8
  26. data/lib/rigor/analysis/check_rules/published_constant_guard.rb +1 -1
  27. data/lib/rigor/analysis/check_rules/rebinding_block_callees.rb +3 -3
  28. data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +1 -2
  29. data/lib/rigor/analysis/check_rules/rule_ids.rb +1 -1
  30. data/lib/rigor/analysis/check_rules/rule_walk.rb +9 -7
  31. data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +3 -4
  32. data/lib/rigor/analysis/check_rules/special_global_setters.rb +3 -3
  33. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +2 -2
  34. data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +1 -1
  35. data/lib/rigor/analysis/check_rules.rb +124 -44
  36. data/lib/rigor/analysis/crash_signature.rb +2 -2
  37. data/lib/rigor/analysis/dependency_recorder.rb +4 -4
  38. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +14 -3
  39. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +1 -1
  40. data/lib/rigor/analysis/dependency_source_inference/index.rb +6 -8
  41. data/lib/rigor/analysis/dependency_source_inference/walker.rb +4 -4
  42. data/lib/rigor/analysis/edit_replay.rb +93 -6
  43. data/lib/rigor/analysis/fact_store.rb +3 -3
  44. data/lib/rigor/analysis/incremental_run_slot/write_guard.rb +2 -2
  45. data/lib/rigor/analysis/incremental_run_slot.rb +2 -2
  46. data/lib/rigor/analysis/incremental_session.rb +27 -16
  47. data/lib/rigor/analysis/rule_catalog.rb +2 -2
  48. data/lib/rigor/analysis/run_stats.rb +2 -2
  49. data/lib/rigor/analysis/runner/buffer_pool_dispatcher.rb +2 -2
  50. data/lib/rigor/analysis/runner/declaration_position.rb +1 -1
  51. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +193 -93
  52. data/lib/rigor/analysis/runner/effect_envelope_pass.rb +1 -1
  53. data/lib/rigor/analysis/runner/pool_coordinator.rb +147 -323
  54. data/lib/rigor/analysis/runner/project_pre_passes.rb +3 -3
  55. data/lib/rigor/analysis/runner/run_level_facts.rb +304 -0
  56. data/lib/rigor/analysis/runner/run_snapshots.rb +44 -54
  57. data/lib/rigor/analysis/runner/worker_payload.rb +36 -0
  58. data/lib/rigor/analysis/runner.rb +77 -71
  59. data/lib/rigor/analysis/self_call_resolution_recorder.rb +1 -1
  60. data/lib/rigor/analysis/template_unit_positions.rb +2 -3
  61. data/lib/rigor/analysis/template_units.rb +9 -1
  62. data/lib/rigor/analysis/worker_session.rb +24 -31
  63. data/lib/rigor/bleeding_edge.rb +3 -6
  64. data/lib/rigor/builtins/imported_refinements.rb +6 -8
  65. data/lib/rigor/builtins/predefined_constant_refinements.rb +1 -1
  66. data/lib/rigor/builtins/regex_refinement.rb +4 -6
  67. data/lib/rigor/cache/descriptor.rb +6 -5
  68. data/lib/rigor/cache/incremental_snapshot.rb +31 -12
  69. data/lib/rigor/cache/rbs_cache_producer.rb +2 -2
  70. data/lib/rigor/cache/rbs_class_ancestor_table.rb +1 -1
  71. data/lib/rigor/cache/rbs_class_type_param_names.rb +2 -2
  72. data/lib/rigor/cache/rbs_constant_table.rb +1 -1
  73. data/lib/rigor/cache/rbs_descriptor.rb +54 -3
  74. data/lib/rigor/cache/rbs_environment.rb +4 -1
  75. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +2 -2
  76. data/lib/rigor/cache/rbs_known_class_names.rb +1 -1
  77. data/lib/rigor/cache/store.rb +11 -10
  78. data/lib/rigor/ci_detector.rb +1 -1
  79. data/lib/rigor/cli/annotate_command.rb +24 -19
  80. data/lib/rigor/cli/baseline_command.rb +145 -55
  81. data/lib/rigor/cli/catalogue.rb +256 -0
  82. data/lib/rigor/cli/check_command.rb +108 -102
  83. data/lib/rigor/cli/check_runner_factory.rb +2 -2
  84. data/lib/rigor/cli/command.rb +15 -1
  85. data/lib/rigor/cli/coverage_command.rb +21 -8
  86. data/lib/rigor/cli/coverage_mutation.rb +5 -7
  87. data/lib/rigor/cli/coverage_renderer.rb +2 -1
  88. data/lib/rigor/cli/coverage_scan.rb +3 -5
  89. data/lib/rigor/cli/diff_command.rb +25 -20
  90. data/lib/rigor/cli/docs_command.rb +1 -2
  91. data/lib/rigor/cli/doctor_command.rb +12 -12
  92. data/lib/rigor/cli/effects_command.rb +50 -40
  93. data/lib/rigor/cli/effects_diff_renderer.rb +18 -17
  94. data/lib/rigor/cli/effects_explain_renderer.rb +13 -12
  95. data/lib/rigor/cli/effects_renderer.rb +2 -1
  96. data/lib/rigor/cli/effects_snapshot_command.rb +18 -17
  97. data/lib/rigor/cli/explain_command.rb +18 -8
  98. data/lib/rigor/cli/fused_protection_renderer.rb +2 -1
  99. data/lib/rigor/cli/json_document.rb +41 -0
  100. data/lib/rigor/cli/lens_command.rb +12 -8
  101. data/lib/rigor/cli/lsp_command.rb +250 -23
  102. data/lib/rigor/cli/mcp_command.rb +12 -7
  103. data/lib/rigor/cli/mutation_fork_scan.rb +1 -1
  104. data/lib/rigor/cli/mutation_protection_renderer.rb +3 -2
  105. data/lib/rigor/cli/options.rb +40 -1
  106. data/lib/rigor/cli/plugin_command.rb +93 -46
  107. data/lib/rigor/cli/plugins_command.rb +17 -18
  108. data/lib/rigor/cli/plugins_renderer.rb +10 -6
  109. data/lib/rigor/cli/protection_fork_scan.rb +2 -2
  110. data/lib/rigor/cli/protection_renderer.rb +2 -1
  111. data/lib/rigor/cli/show_bleedingedge_command.rb +21 -15
  112. data/lib/rigor/cli/sig_gen_command.rb +84 -27
  113. data/lib/rigor/cli/skill_command.rb +32 -3
  114. data/lib/rigor/cli/skill_deep_probe.rb +1 -1
  115. data/lib/rigor/cli/skill_describe.rb +1 -1
  116. data/lib/rigor/cli/trace_command.rb +14 -9
  117. data/lib/rigor/cli/triage_command.rb +10 -11
  118. data/lib/rigor/cli/triage_renderer.rb +2 -1
  119. data/lib/rigor/cli/type_of_command.rb +16 -11
  120. data/lib/rigor/cli/type_of_project_scope.rb +1 -4
  121. data/lib/rigor/cli/type_of_renderer.rb +4 -1
  122. data/lib/rigor/cli/type_scan_command.rb +14 -9
  123. data/lib/rigor/cli/type_scan_renderer.rb +2 -1
  124. data/lib/rigor/cli/unused_command.rb +27 -17
  125. data/lib/rigor/cli/upgrade_command.rb +5 -0
  126. data/lib/rigor/cli.rb +209 -54
  127. data/lib/rigor/config_audit.rb +1 -1
  128. data/lib/rigor/configuration/severity_profile.rb +5 -5
  129. data/lib/rigor/configuration.rb +72 -30
  130. data/lib/rigor/effects/definition_context.rb +0 -2
  131. data/lib/rigor/effects/envelope.rb +1 -1
  132. data/lib/rigor/effects/framework_units.rb +1 -1
  133. data/lib/rigor/effects/mutation_classifier.rb +4 -7
  134. data/lib/rigor/effects/propagator.rb +1 -1
  135. data/lib/rigor/effects/registry.rb +2 -2
  136. data/lib/rigor/effects/snapshot.rb +1 -1
  137. data/lib/rigor/effects/taint_cause.rb +0 -2
  138. data/lib/rigor/effects/unit_scan.rb +9 -13
  139. data/lib/rigor/environment/bundle_sig_discovery.rb +5 -9
  140. data/lib/rigor/environment/class_registry.rb +3 -3
  141. data/lib/rigor/environment/core_root.rb +77 -0
  142. data/lib/rigor/environment/default_libraries.rb +1 -1
  143. data/lib/rigor/environment/deferred_signature_plan.rb +283 -0
  144. data/lib/rigor/environment/gated_signature_guard.rb +1 -1
  145. data/lib/rigor/environment/gated_signature_plan.rb +5 -2
  146. data/lib/rigor/environment/lockfile_resolver.rb +28 -12
  147. data/lib/rigor/environment/member_consistency/comparator.rb +18 -1
  148. data/lib/rigor/environment/member_consistency.rb +56 -7
  149. data/lib/rigor/environment/missing_gem_constant_index.rb +3 -2
  150. data/lib/rigor/environment/project_path_gems.rb +124 -0
  151. data/lib/rigor/environment/rbs_hierarchy.rb +6 -15
  152. data/lib/rigor/environment/rbs_loader.rb +480 -67
  153. data/lib/rigor/environment/reflection.rb +7 -7
  154. data/lib/rigor/environment/required_features.rb +0 -3
  155. data/lib/rigor/environment/ruby_library_roots.rb +1 -1
  156. data/lib/rigor/environment/vendored_signature_plan.rb +153 -0
  157. data/lib/rigor/environment.rb +41 -10
  158. data/lib/rigor/flow_contribution/merger.rb +4 -4
  159. data/lib/rigor/inference/acceptance.rb +93 -32
  160. data/lib/rigor/inference/block_parameter_binder.rb +2 -3
  161. data/lib/rigor/inference/block_run_count.rb +178 -0
  162. data/lib/rigor/inference/budget_trace.rb +8 -2
  163. data/lib/rigor/inference/builtins/exception_catalog.rb +2 -2
  164. data/lib/rigor/inference/builtins/numeric_catalog.rb +2 -5
  165. data/lib/rigor/inference/builtins/random_catalog.rb +2 -2
  166. data/lib/rigor/inference/callee_mutation_chain.rb +88 -77
  167. data/lib/rigor/inference/callee_write_census.rb +815 -0
  168. data/lib/rigor/inference/construction_escape.rb +217 -0
  169. data/lib/rigor/inference/declaration_walk/traversal.rb +1 -1
  170. data/lib/rigor/inference/deep_erasure.rb +39 -7
  171. data/lib/rigor/inference/def_handle.rb +1 -1
  172. data/lib/rigor/inference/def_node_resolver.rb +1 -1
  173. data/lib/rigor/inference/def_return_typer.rb +46 -4
  174. data/lib/rigor/inference/definer_resolution.rb +4 -2
  175. data/lib/rigor/inference/definers_index.rb +106 -0
  176. data/lib/rigor/inference/direct_ivar_writes.rb +300 -0
  177. data/lib/rigor/inference/element_read_widening.rb +49 -3
  178. data/lib/rigor/inference/expression_typer.rb +116 -49
  179. data/lib/rigor/inference/external_ancestor_resolution.rb +3 -4
  180. data/lib/rigor/inference/global_write_census.rb +76 -2
  181. data/lib/rigor/inference/global_writer_reach.rb +260 -0
  182. data/lib/rigor/inference/hkt_body.rb +1 -2
  183. data/lib/rigor/inference/hkt_body_parser.rb +3 -5
  184. data/lib/rigor/inference/hkt_reducer.rb +2 -2
  185. data/lib/rigor/inference/hkt_registry.rb +3 -5
  186. data/lib/rigor/inference/indexed_narrowing.rb +4 -7
  187. data/lib/rigor/inference/intervening_ivar_reset.rb +26 -4
  188. data/lib/rigor/inference/ivar_writer_census.rb +51 -0
  189. data/lib/rigor/inference/key_presence_guard.rb +2 -1
  190. data/lib/rigor/inference/loop_value_record.rb +8 -4
  191. data/lib/rigor/inference/match_rebinding/frame.rb +46 -0
  192. data/lib/rigor/inference/match_rebinding/yielding_match.rb +45 -0
  193. data/lib/rigor/inference/match_rebinding.rb +11 -1
  194. data/lib/rigor/inference/memo_audit.rb +55 -0
  195. data/lib/rigor/inference/method_dispatcher/alias_strict_nominals.rb +1 -1
  196. data/lib/rigor/inference/method_dispatcher/argument_distribution.rb +40 -0
  197. data/lib/rigor/inference/method_dispatcher/block_folding.rb +1 -1
  198. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +3 -3
  199. data/lib/rigor/inference/method_dispatcher/contested_overloads.rb +89 -0
  200. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +1 -1
  201. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +1 -1
  202. data/lib/rigor/inference/method_dispatcher/no_match_fallback.rb +54 -0
  203. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +39 -27
  204. data/lib/rigor/inference/method_dispatcher/random_folding.rb +1 -1
  205. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +203 -17
  206. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +1 -1
  207. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +2 -2
  208. data/lib/rigor/inference/method_dispatcher/self_substitute.rb +2 -2
  209. data/lib/rigor/inference/method_dispatcher/set_folding.rb +9 -7
  210. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +83 -20
  211. data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +0 -1
  212. data/lib/rigor/inference/method_dispatcher/singleton_mixin_dispatch.rb +50 -17
  213. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +5 -5
  214. data/lib/rigor/inference/method_dispatcher/struct_materialization.rb +4 -6
  215. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +1 -1
  216. data/lib/rigor/inference/method_dispatcher.rb +64 -14
  217. data/lib/rigor/inference/module_function_state.rb +11 -1
  218. data/lib/rigor/inference/multi_target_binder.rb +1 -1
  219. data/lib/rigor/inference/mutation_rejoin.rb +240 -4
  220. data/lib/rigor/inference/mutation_widening.rb +157 -27
  221. data/lib/rigor/inference/narrowing.rb +47 -33
  222. data/lib/rigor/inference/optimistic_origin.rb +1 -1
  223. data/lib/rigor/inference/out_buffer_mutation.rb +72 -0
  224. data/lib/rigor/inference/parameter_inference_collector.rb +5 -6
  225. data/lib/rigor/inference/pre_eval_constants.rb +1 -1
  226. data/lib/rigor/inference/project_method_ownership.rb +6 -3
  227. data/lib/rigor/inference/project_yielder.rb +11 -17
  228. data/lib/rigor/inference/receiver_alias.rb +20 -0
  229. data/lib/rigor/inference/refined_dispatch/constructed_class.rb +3 -1
  230. data/lib/rigor/inference/refined_dispatch/own_method.rb +2 -1
  231. data/lib/rigor/inference/remover_holder_scan.rb +233 -0
  232. data/lib/rigor/inference/remover_narrowing.rb +224 -0
  233. data/lib/rigor/inference/rewrite_mutation.rb +53 -2
  234. data/lib/rigor/inference/scope_indexer/certainty.rb +0 -1
  235. data/lib/rigor/inference/scope_indexer/constant_ivar_value.rb +93 -0
  236. data/lib/rigor/inference/scope_indexer/declared_param_writes.rb +58 -15
  237. data/lib/rigor/inference/scope_indexer/derived_param_value.rb +112 -0
  238. data/lib/rigor/inference/scope_indexer/empty_seed.rb +99 -0
  239. data/lib/rigor/inference/scope_indexer/ivar_contributions.rb +135 -0
  240. data/lib/rigor/inference/scope_indexer.rb +137 -46
  241. data/lib/rigor/inference/self_call_ivar_writes.rb +84 -12
  242. data/lib/rigor/inference/set_mutation.rb +189 -0
  243. data/lib/rigor/inference/singleton_hook_decline.rb +1 -2
  244. data/lib/rigor/inference/statement_evaluator.rb +2328 -138
  245. data/lib/rigor/inference/struct_fold_safety.rb +4 -6
  246. data/lib/rigor/inference/synthetic_method_scanner.rb +5 -6
  247. data/lib/rigor/inference/toplevel_def_census.rb +59 -0
  248. data/lib/rigor/inference/unknown_store_widening.rb +103 -4
  249. data/lib/rigor/inference/unthreaded_rebinds.rb +2 -3
  250. data/lib/rigor/inference/void_tail_summary.rb +2 -1
  251. data/lib/rigor/language_server/buffer_table.rb +3 -3
  252. data/lib/rigor/language_server/completion_provider.rb +26 -20
  253. data/lib/rigor/language_server/config_warning_announcer.rb +28 -0
  254. data/lib/rigor/language_server/diagnostic_publisher.rb +8 -3
  255. data/lib/rigor/language_server/document_symbol_provider.rb +1 -1
  256. data/lib/rigor/language_server/folding_range_provider.rb +1 -2
  257. data/lib/rigor/language_server/hover_provider.rb +1 -7
  258. data/lib/rigor/language_server/hover_renderer.rb +6 -8
  259. data/lib/rigor/language_server/project_context.rb +39 -1
  260. data/lib/rigor/language_server/publish_batcher.rb +3 -4
  261. data/lib/rigor/language_server/selection_range_provider.rb +2 -4
  262. data/lib/rigor/language_server/server.rb +7 -3
  263. data/lib/rigor/language_server/signature_help_provider.rb +4 -4
  264. data/lib/rigor/lens/plugins.rb +1 -1
  265. data/lib/rigor/mcp/server.rb +73 -2
  266. data/lib/rigor/mcp.rb +1 -1
  267. data/lib/rigor/plugin/access_denied_error.rb +1 -2
  268. data/lib/rigor/plugin/bundled_catalog.rb +1 -12
  269. data/lib/rigor/plugin/io_boundary.rb +1 -1
  270. data/lib/rigor/plugin/loader.rb +25 -8
  271. data/lib/rigor/plugin/macro/heredoc_template.rb +7 -8
  272. data/lib/rigor/plugin/macro/trait_registry.rb +6 -9
  273. data/lib/rigor/plugin/manifest.rb +9 -13
  274. data/lib/rigor/plugin/registry.rb +3 -3
  275. data/lib/rigor/plugin/services.rb +2 -3
  276. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -0
  277. data/lib/rigor/plugin/trust_policy.rb +2 -2
  278. data/lib/rigor/plugin/type_node_resolver.rb +2 -4
  279. data/lib/rigor/plugin.rb +6 -8
  280. data/lib/rigor/plugin_gap_advisory.rb +5 -1
  281. data/lib/rigor/project_environment.rb +16 -2
  282. data/lib/rigor/protection/analysis_guard.rb +1 -1
  283. data/lib/rigor/protection/discovery_seed.rb +44 -5
  284. data/lib/rigor/protection/mutator.rb +1 -1
  285. data/lib/rigor/rbs_extended/envelope_scanner.rb +4 -6
  286. data/lib/rigor/rbs_extended/hkt_directives.rb +6 -10
  287. data/lib/rigor/rbs_extended/reporter.rb +27 -3
  288. data/lib/rigor/rbs_extended.rb +2 -3
  289. data/lib/rigor/reflection/constant_ancestors.rb +5 -3
  290. data/lib/rigor/rooted_glob.rb +15 -1
  291. data/lib/rigor/run_scoped_slots.rb +38 -0
  292. data/lib/rigor/runtime/jit.rb +1 -1
  293. data/lib/rigor/scope/discovery_index.rb +19 -6
  294. data/lib/rigor/scope/resolution_chain.rb +3 -1
  295. data/lib/rigor/scope.rb +365 -31
  296. data/lib/rigor/sig_gen/classification.rb +4 -0
  297. data/lib/rigor/sig_gen/effect_annotation.rb +3 -3
  298. data/lib/rigor/sig_gen/generator.rb +371 -58
  299. data/lib/rigor/sig_gen/generic_header_scan.rb +189 -0
  300. data/lib/rigor/sig_gen/inline_declarations.rb +2 -2
  301. data/lib/rigor/sig_gen/layout_index.rb +3 -3
  302. data/lib/rigor/sig_gen/method_candidate.rb +23 -4
  303. data/lib/rigor/sig_gen/observation_collector.rb +3 -5
  304. data/lib/rigor/sig_gen/observed_call.rb +1 -1
  305. data/lib/rigor/sig_gen/path_mapper.rb +6 -0
  306. data/lib/rigor/sig_gen/renderer.rb +15 -9
  307. data/lib/rigor/sig_gen/skip_reason_catalog.rb +15 -2
  308. data/lib/rigor/sig_gen/type_name_qualification.rb +69 -0
  309. data/lib/rigor/sig_gen/write_reporter.rb +63 -14
  310. data/lib/rigor/sig_gen/write_result.rb +23 -12
  311. data/lib/rigor/sig_gen/writer.rb +217 -37
  312. data/lib/rigor/type/acceptance_router.rb +2 -2
  313. data/lib/rigor/type/accepts_result.rb +1 -1
  314. data/lib/rigor/type/app.rb +4 -6
  315. data/lib/rigor/type/combinator.rb +9 -13
  316. data/lib/rigor/type/constant.rb +4 -5
  317. data/lib/rigor/type/hash_shape.rb +21 -40
  318. data/lib/rigor/type/union.rb +1 -1
  319. data/lib/rigor/type_node/identifier.rb +1 -2
  320. data/lib/rigor/type_node/resolver_chain.rb +1 -1
  321. data/lib/rigor/type_node/string_literal.rb +1 -2
  322. data/lib/rigor/type_node/symbol_literal.rb +1 -2
  323. data/lib/rigor/type_node/union.rb +3 -6
  324. data/lib/rigor/value_semantics.rb +1 -10
  325. data/lib/rigor/version.rb +1 -1
  326. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +2 -2
  327. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +2 -3
  328. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +1 -1
  329. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +38 -36
  330. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +4 -2
  331. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +6 -4
  332. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +1 -1
  333. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +1 -5
  334. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_scan.rb +0 -1
  335. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/erb_compiler.rb +2 -1
  336. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/view_assigns.rb +1 -1
  337. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +66 -19
  338. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/effects.rb +1 -1
  339. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +4 -3
  340. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/association_roots.rb +3 -2
  341. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/effects.rb +2 -2
  342. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +21 -31
  343. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +10 -16
  344. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +2 -3
  345. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +0 -1
  346. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +5 -4
  347. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +3 -4
  348. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +1 -1
  349. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +7 -12
  350. data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +1 -1
  351. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/effects.rb +1 -1
  352. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext/symbolize_keys.rb +102 -0
  353. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +19 -6
  354. data/plugins/rigor-activesupport-core-ext/sig/active_support/core_ext.rbs +26 -13
  355. data/plugins/rigor-alba/lib/rigor/plugin/alba/resource_index.rb +3 -3
  356. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +1 -3
  357. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +6 -6
  358. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +5 -6
  359. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +1 -1
  360. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/params_shape.rb +2 -3
  361. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +5 -8
  362. data/plugins/rigor-ethon/lib/rigor/plugin/ethon.rb +0 -1
  363. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +1 -1
  364. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +10 -18
  365. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/analyzer.rb +0 -1
  366. data/plugins/rigor-ffi/lib/rigor/plugin/ffi/discoverer.rb +0 -2
  367. data/plugins/rigor-ffi/lib/rigor/plugin/ffi.rb +4 -5
  368. data/plugins/rigor-grape/lib/rigor/plugin/grape.rb +0 -2
  369. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +6 -7
  370. data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +2 -4
  371. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +2 -5
  372. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +2 -2
  373. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +6 -6
  374. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +2 -1
  375. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +3 -8
  376. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +2 -3
  377. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +5 -8
  378. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +1 -3
  379. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +2 -3
  380. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +1 -1
  381. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +4 -11
  382. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +1 -1
  383. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +2 -1
  384. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +1 -1
  385. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +2 -2
  386. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +3 -4
  387. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +6 -8
  388. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +3 -2
  389. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +4 -5
  390. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +6 -7
  391. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +1 -1
  392. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +2 -2
  393. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +17 -21
  394. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +2 -3
  395. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_discoverer.rb +3 -2
  396. data/plugins/rigor-typelizer/lib/rigor/plugin/typelizer/serializer_index.rb +4 -4
  397. data/sig/rigor/analysis/baseline.rbs +0 -3
  398. data/sig/rigor/analysis/fact_store.rbs +1 -0
  399. data/sig/rigor/analysis/reachability/scan_cache.rbs +1 -1
  400. data/sig/rigor/cli/diff_command.rbs +1 -1
  401. data/sig/rigor/cli/explain_command.rbs +1 -1
  402. data/sig/rigor/effects/registry.rbs +2 -2
  403. data/sig/rigor/inference.rbs +4 -1
  404. data/sig/rigor/plugin/additional_initializer.rbs +0 -5
  405. data/sig/rigor/plugin/base.rbs +1 -0
  406. data/sig/rigor/plugin/effect_attribution.rbs +3 -3
  407. data/sig/rigor/plugin/effect_edge.rbs +1 -1
  408. data/sig/rigor/plugin/effect_entry_points.rbs +3 -3
  409. data/sig/rigor/plugin/io_boundary.rbs +2 -1
  410. data/sig/rigor/plugin/manifest.rbs +9 -9
  411. data/sig/rigor/plugin/protocol_contract.rbs +3 -7
  412. data/sig/rigor/scope.rbs +23 -3
  413. data/skills/rigor-plugin-author/SKILL.md +4 -4
  414. data/skills/rigor-plugin-author/references/02-walker-and-types.md +5 -5
  415. data/skills/rigor-plugin-review/SKILL.md +3 -3
  416. data/skills/rigor-project-init/references/03-baseline-and-bugs.md +12 -1
  417. metadata +36 -3
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "tempfile"
4
+
3
5
  require_relative "../environment"
4
6
  require_relative "../project_environment"
5
7
  require_relative "../cache/store"
@@ -9,6 +11,7 @@ require_relative "../cache/incremental_snapshot"
9
11
  require_relative "../effects/identity"
10
12
  require_relative "../analysis/path_expansion"
11
13
  require_relative "../protection/discovery_seed"
14
+ require_relative "../config_audit"
12
15
 
13
16
  module Rigor
14
17
  module LanguageServer
@@ -68,6 +71,24 @@ module Rigor
68
71
  @project_scan = nil
69
72
  @incremental_session = nil
70
73
  @session_primed = false
74
+ @announced_config_warnings = Set.new
75
+ @announce_lock = Mutex.new
76
+ end
77
+
78
+ # Issue #2231 — the {ConfigAudit} warnings for the configuration as last loaded that the editor has not been
79
+ # shown yet, or an empty array. `rigor check` prints these on every run; the server is one long run, so it
80
+ # announces each warning once: at `initialized` for the configuration it started with, and after a later reload
81
+ # (every save round re-reads `.rigor.yml`) only the warnings that were not in the previous reload's set. The
82
+ # comparison is by message, not by order, so reordering entries announces nothing, and fixing one of two
83
+ # warnings does not show the other again. A warning that was fixed and then comes back is new and is shown.
84
+ # Called from the dispatch thread and the save round's thread alike, hence the lock.
85
+ def unannounced_config_warnings
86
+ @announce_lock.synchronize do
87
+ warnings = ConfigAudit.warnings(@configuration)
88
+ fresh = warnings.reject { |warning| @announced_config_warnings.include?(warning.message) }
89
+ @announced_config_warnings = warnings.to_set(&:message)
90
+ fresh
91
+ end
71
92
  end
72
93
 
73
94
  # Returns the cached `Rigor::Environment` for this session, building it on first access. The build
@@ -132,6 +153,22 @@ module Rigor
132
153
  buffer: buffer, constant_sources: true)
133
154
  end
134
155
 
156
+ # Issue #2282 — the base scope an editor query (hover, completion, signature help) probes with: the same
157
+ # cross-file discovery seed the diagnostic publisher hands its `Runner` ({#discovery_seed_for}), so a constant
158
+ # or method another file defines answers what `rigor check` would. The seed fold reads the buffer through a
159
+ # {Analysis::BufferBinding}, so `bytes` is staged in a tempfile for the call. An empty seed (no configured
160
+ # files, or nothing discovered) leaves the bare single-file scope every query used before.
161
+ def seeded_scope_for(path:, bytes:)
162
+ seed = Tempfile.create(["rigor-lsp-query-", ".rb"]) do |tmp|
163
+ # Staged as the publisher stages them; the seed's job is to withhold the stale on-disk declarations of
164
+ # this file, so an empty-bytes stage is not observable by design.
165
+ tmp.write(bytes)
166
+ tmp.flush
167
+ discovery_seed_for(Analysis::BufferBinding.new(logical_path: path, physical_path: tmp.path))
168
+ end
169
+ Protection::DiscoverySeed.seeded_scope(environment: environment, seed: seed)
170
+ end
171
+
135
172
  # Builds what {#discovery_seed_for} reads once per generation, so a fork batch's children inherit it instead
136
173
  # of each walking the project.
137
174
  def prepare_discovery_seed
@@ -251,7 +288,8 @@ module Rigor
251
288
  def reload_configuration
252
289
  return unless @configuration_loader
253
290
 
254
- @configuration = @configuration_loader.call
291
+ loaded = @configuration_loader.call
292
+ @announce_lock.synchronize { @configuration = loaded }
255
293
  rescue StandardError
256
294
  nil
257
295
  end
@@ -15,10 +15,9 @@ module Rigor
15
15
  # dropped. The same claim/consume shape `DiagnosticPublisher#run_project_round` already uses for the
16
16
  # whole-project save round (#246), generalised to an arbitrary key type and an arbitrary batch action.
17
17
  class PublishBatcher
18
- # @param on_batch — `(keys) -> void`, called with the deduplicated Array of keys pending at the
19
- # start of one round. May be called more than once in a row when keys keep arriving while a round
20
- # runs.
21
- # @param on_error — `(exception) -> void`, called when `on_batch` raises. A round must
18
+ # @param on_batch — called with the deduplicated keys pending at the start of one round. May be called
19
+ # more than once in a row when keys keep arriving while a round runs.
20
+ # @param on_error — called with the exception when `on_batch` raises. A round must
22
21
  # never wedge the coalescing lock for every future `#enqueue` call — ownership is always released
23
22
  # before this fires. Whatever was mid-flight when the round raised is lost; anything enqueued by a
24
23
  # concurrent `#enqueue` call after this round's drain but before the rescue stays pending and rides
@@ -20,10 +20,8 @@ module Rigor
20
20
  @project_context = project_context
21
21
  end
22
22
 
23
- # @param positions — LSP `Position[]` — each
24
- # `{ line:, character: }` 0-based.
25
- # @return one `SelectionRange` per
26
- # position, or nil when the URI / buffer isn't resolvable.
23
+ # @param positions — LSP positions, each `{ line:, character: }` 0-based.
24
+ # @return one `SelectionRange` per position, or nil when the URI / buffer isn't resolvable.
27
25
  def provide(uri, positions)
28
26
  path, entry = buffer_for(uri)
29
27
  return nil if entry.nil?
@@ -194,7 +194,12 @@ module Rigor
194
194
 
195
195
  # `initialized` is a notification — no response body. Nothing is registered here yet: the server handles
196
196
  # `workspace/didChangeWatchedFiles` when a client sends it, but does not register for it dynamically.
197
+ #
198
+ # Issue #2231 — the configuration the server started with is audited here, the first point at which the
199
+ # client is listening, so a `.rigor.yml` key that does nothing is named in the editor as `rigor check`
200
+ # names it on STDERR.
197
201
  def handle_initialized
202
+ @publisher&.announce_config_warnings
198
203
  nil
199
204
  end
200
205
 
@@ -267,8 +272,7 @@ module Rigor
267
272
 
268
273
  # textDocument/hover REQUEST. Slice 5 returns either a `Hover` payload (markdown contents wrapping type
269
274
  # + erased-RBS info) or nil when no expression is at the queried position. Nil maps to `result: null`
270
- # per LSP spec; clients suppress the popup. Returns `MethodNotFound` when no hover_provider is wired
271
- # (slice 1-4 behaviour).
275
+ # per LSP spec; clients suppress the popup. Returns `MethodNotFound` when no hover_provider is wired.
272
276
  def handle_hover(params)
273
277
  return method_not_found("textDocument/hover") unless @hover_provider
274
278
 
@@ -351,7 +355,7 @@ module Rigor
351
355
  )
352
356
  end
353
357
 
354
- # textDocument/documentSymbol REQUEST. Returns the `DocumentSymbol[]` outline for the buffer at the
358
+ # textDocument/documentSymbol REQUEST. Returns the document-symbol outline for the buffer at the
355
359
  # requested URI. Returns `MethodNotFound` when no provider is wired.
356
360
  def handle_document_symbol(params)
357
361
  return method_not_found("textDocument/documentSymbol") unless @document_symbol_provider
@@ -25,7 +25,7 @@ module Rigor
25
25
  # parses the buffer, locates the enclosing `CallNode`, infers the receiver's type, and returns the
26
26
  # method's first overload as a `SignatureInformation`.
27
27
  #
28
- # Slice C1 (this commit) ships:
28
+ # Slice C1 ships:
29
29
  # - Sentinel patching for `obj.foo(|` so Prism's parse succeeds (mirrors `CompletionProvider`'s slice B4
30
30
  # pattern).
31
31
  # - First-overload signature only.
@@ -111,7 +111,7 @@ module Rigor
111
111
  end
112
112
 
113
113
  def build_signature(call_node, root, path, bytes, line, character)
114
- scope_index = build_scope_index(root, path)
114
+ scope_index = build_scope_index(root, path, bytes)
115
115
  receiver_node = call_node.receiver
116
116
  return nil if receiver_node.nil?
117
117
 
@@ -197,8 +197,8 @@ module Rigor
197
197
  end
198
198
  end
199
199
 
200
- def build_scope_index(root, _path)
201
- scope = Scope.empty(environment: @project_context.environment)
200
+ def build_scope_index(root, path, bytes)
201
+ scope = @project_context.seeded_scope_for(path: path, bytes: bytes)
202
202
  Inference::ScopeIndexer.index(root, default_scope: scope)
203
203
  end
204
204
 
@@ -29,7 +29,7 @@ module Rigor
29
29
  return Rigor::VERSION if absolute.start_with?("#{BUNDLED_PLUGINS}/")
30
30
 
31
31
  spec = Gem.loaded_specs.each_value.find do |gem|
32
- absolute.start_with?("#{gem.full_gem_path}/") # rigor:disable undefined-method
32
+ absolute.start_with?("#{gem.full_gem_path}/")
33
33
  end
34
34
  spec&.version&.to_s
35
35
  rescue StandardError
@@ -155,6 +155,10 @@ module Rigor
155
155
  ].freeze
156
156
  Ractor.make_shareable(TOOLS)
157
157
 
158
+ # The tools that read a configuration — the ones taking a `config` argument — and so report its warnings.
159
+ CONFIG_TOOLS = TOOLS.filter_map { |tool| tool[:name] if tool.dig(:inputSchema, :properties, :config) }.freeze
160
+ Ractor.make_shareable(CONFIG_TOOLS)
161
+
158
162
  def initialize(config_path: nil, err: $stderr)
159
163
  @config_path = config_path
160
164
  @err = err
@@ -165,7 +169,7 @@ module Rigor
165
169
  id = request["id"]
166
170
  method_name = request["method"]
167
171
 
168
- # Notifications carry no `id` and require no response.
172
+ # A notification carries no `id` and requires no response.
169
173
  return nil if id.nil?
170
174
 
171
175
  case method_name
@@ -181,8 +185,38 @@ module Rigor
181
185
  end
182
186
  end
183
187
 
188
+ # ADR-126 WD4 — `rigor_type_of` and `rigor_explain` print documents whose top-level shape changes in the next
189
+ # minor, and their CLI says so on stderr, which `tool_result` drops once there is a document. The notice is
190
+ # carried to the agent as its own `text` content block, the same text the CLI printed, so the document block
191
+ # stays byte-for-byte what the CLI wrote. When stdout is empty the error text already is stderr.
192
+ SHAPE_NOTICE_TOOLS = %w[rigor_type_of rigor_explain].freeze
193
+
184
194
  private
185
195
 
196
+ def finish_tool_result(name, args, out, err, exit_code)
197
+ result = with_config_warnings(tool_result(out, err, exit_code), name, args, out)
198
+ result = with_shape_notice(result, name, out, err)
199
+ with_path_warnings(result, name, out, err)
200
+ end
201
+
202
+ # #2206 — `rigor coverage` writes its path warnings (a missing path skipped beside files it measures) to stderr,
203
+ # which a successful run drops. The tool carries the CLI's whole stderr (path warnings and any other warnings)
204
+ # as a second text block, unfiltered, so an agent sees what was skipped.
205
+ def with_path_warnings(result, name, out, err)
206
+ return result unless name == "rigor_coverage" && !out.empty? && !err.strip.empty?
207
+
208
+ result.merge(content: result.fetch(:content) + [{ type: "text", text: err }])
209
+ end
210
+
211
+ def with_shape_notice(result, name, out, err)
212
+ return result unless SHAPE_NOTICE_TOOLS.include?(name) && !out.empty?
213
+
214
+ notice = err[CLI::JsonDocument::NOTICE_PATTERN]
215
+ return result unless notice
216
+
217
+ result.merge(content: result.fetch(:content) + [{ type: "text", text: notice }])
218
+ end
219
+
186
220
  def handle_initialize(id)
187
221
  require_relative "../version"
188
222
  success(id, {
@@ -201,7 +235,7 @@ module Rigor
201
235
  require_relative "../cli"
202
236
  exit_code = with_captured_streams(out_io, err_io) { CLI.new(argv, out: out_io, err: err_io).run }
203
237
 
204
- success(id, tool_result(out_io.string, err_io.string, exit_code))
238
+ success(id, finish_tool_result(name, args, out_io.string, err_io.string, exit_code))
205
239
  rescue SystemExit => e
206
240
  # Defence against any command that exits: it must not take the server down with it.
207
241
  success(id, { content: [{ type: "text", text: "Command exited (status #{e.status})" }], isError: true })
@@ -221,6 +255,43 @@ module Rigor
221
255
  { content: [{ type: "text", text: out.empty? ? err : out }], isError: is_error }
222
256
  end
223
257
 
258
+ # Issue #2231 — `rigor check` puts {ConfigAudit} warnings in its JSON `config_warnings`; the other tools read the
259
+ # same configuration and said nothing about it, so an agent saw `call.undefined-method` on a project whose
260
+ # `.rigor.yml` misspelt the key meant to silence it and got no hint why. Every tool that takes a `config`
261
+ # argument now carries them: a second `text` content block holding `{"config_warnings": [...]}`, each entry the
262
+ # shape check's JSON uses. The tool's own document is left byte-for-byte as its CLI printed it, and an output
263
+ # that already carries the key (check's) gets no second copy. Nothing is added when there is nothing to say,
264
+ # nor when the configuration does not load — the CLI has already reported that.
265
+ def with_config_warnings(result, name, args, out)
266
+ return result unless CONFIG_TOOLS.include?(name)
267
+
268
+ warnings = config_warnings(present_config(args["config"]) || @config_path)
269
+ return result if warnings.empty? || carries_config_warnings?(out)
270
+
271
+ block = { type: "text", text: JSON.generate({ "config_warnings" => warnings.map(&:to_h) }) }
272
+ result.merge(content: result.fetch(:content) + [block])
273
+ end
274
+
275
+ # Only a configuration that does not load is "no warnings to show": the CLI has already reported that error.
276
+ # A failure in ConfigAudit itself is a bug and surfaces as an internal error rather than reading as clean.
277
+ def config_warnings(config_path)
278
+ require_relative "../configuration"
279
+ require_relative "../config_audit"
280
+ begin
281
+ configuration = Configuration.load(config_path)
282
+ rescue ConfigurationError
283
+ return []
284
+ end
285
+ ConfigAudit.warnings(configuration)
286
+ end
287
+
288
+ def carries_config_warnings?(out)
289
+ document = JSON.parse(out)
290
+ document.is_a?(Hash) && document.key?("config_warnings")
291
+ rescue JSON::ParserError
292
+ false
293
+ end
294
+
224
295
  # The JSON-RPC channel is the process's stdout, so nothing a command writes past its `out:` (an option
225
296
  # parser's `--help`, a stray `puts`) may reach it while the command runs.
226
297
  def with_captured_streams(out_io, err_io)
data/lib/rigor/mcp.rb CHANGED
@@ -4,7 +4,7 @@ module Rigor
4
4
  # The MCP (Model Context Protocol) server subsystem. See `docs/adr/33-mcp-server.md` for the design.
5
5
  #
6
6
  # Entry point: `rigor mcp --transport stdio`. The server exposes Rigor's analysis tools (check, type-of, triage,
7
- # annotate, sig-gen, explain, coverage) as MCP tool calls over a newline-delimited JSON-RPC 2.0 stdio stream.
7
+ # lens, annotate, sig-gen, explain, coverage) as MCP tool calls over a newline-delimited JSON-RPC 2.0 stdio stream.
8
8
  module MCP
9
9
  end
10
10
  end
@@ -6,8 +6,7 @@ module Rigor
6
6
  # analyzer can convert the exception into a `:plugin_loader` diagnostic without crashing `rigor check`.
7
7
  #
8
8
  # The policy is documented in [ADR-2 § "Plugin Trust and I/O Policy"](../../../docs/adr/2-extension-api.md).
9
- # Slice 2's surface lists `:read_outside_scope` and `:network_disabled` as the two reason codes; future slices
10
- # may extend the set.
9
+ # `reason:` is one of `:read_outside_scope`, `:network_disabled`, `:url_fetch_failed` or `:url_body_too_large`.
11
10
  class AccessDeniedError < StandardError
12
11
  attr_reader :reason, :resource
13
12
 
@@ -127,7 +127,7 @@ module Rigor
127
127
  def require_bundled_plugins
128
128
  return {} unless File.directory?(BUNDLED_PLUGINS_ROOT)
129
129
 
130
- add_bundled_lib_dirs
130
+ Loader.add_bundled_lib_dirs
131
131
  Dir.children(BUNDLED_PLUGINS_ROOT).sort.each_with_object({}) do |gem_name, resolved|
132
132
  path = Loader.bundled_plugin_path(gem_name)
133
133
  next if path.nil?
@@ -137,17 +137,6 @@ module Rigor
137
137
  end
138
138
  end
139
139
 
140
- # A bundled plugin's entry file requires its own tree by bare feature name (`require
141
- # "rigor/plugin/activestorage"`), which resolves only with that plugin's `lib` on the load path — the
142
- # loader gets it from the bundle, and this catalogue has to arrange it itself. Prepending the engine's
143
- # own copies matches ADR-93 WD5's anti-skew rule, where the bundled copy always wins.
144
- def add_bundled_lib_dirs
145
- Dir.children(BUNDLED_PLUGINS_ROOT).sort.each do |gem_name|
146
- dir = File.join(BUNDLED_PLUGINS_ROOT, gem_name, "lib")
147
- $LOAD_PATH.unshift(dir) if File.directory?(dir) && !$LOAD_PATH.include?(dir)
148
- end
149
- end
150
-
151
140
  # A plugin gem that fails to load is skipped rather than fatal: the catalogue exists to *advise*, and
152
141
  # a broken bundled copy is already reported by `rigor doctor`'s plugin checks for the plugins the
153
142
  # project actually enabled. The failure is recorded in {load_failures} so a missing entry stays
@@ -17,7 +17,7 @@ module Rigor
17
17
  # scope and feed the cache descriptor; contributions built on top of out-of-scope reads will not
18
18
  # invalidate correctly.
19
19
  #
20
- # Slice 2 ships a minimal surface:
20
+ # The surface:
21
21
  #
22
22
  # - `#read_file(path)` — validates against the policy, returns the file's contents, and adds a
23
23
  # digest-keyed {Cache::Descriptor::FileEntry} to the boundary's accumulated descriptor. A read that
@@ -90,6 +90,20 @@ module Rigor
90
90
  File.directory?(path) ? path : nil
91
91
  end
92
92
 
93
+ # Puts every bundled plugin's `lib` on `$LOAD_PATH`, so a bundled plugin that requires another by gem
94
+ # name (`require "rigor-ffi"` from `rigor-ethon`, `rigor-sassc`, `rigor-ffi-rzmq` and `rigor-rbnacl`)
95
+ # resolves without a bundle. The engine's own copies go first, which matches ADR-93 WD5's anti-skew
96
+ # rule: the bundled copy always wins. Idempotent; a trimmed packaging with no `plugins/` tree adds none.
97
+ def self.add_bundled_lib_dirs
98
+ root = File.join(ENGINE_ROOT, "plugins")
99
+ return unless File.directory?(root)
100
+
101
+ Dir.children(root).sort.each do |gem_name|
102
+ dir = File.join(root, gem_name, "lib")
103
+ $LOAD_PATH.unshift(dir) if File.directory?(dir) && !$LOAD_PATH.include?(dir)
104
+ end
105
+ end
106
+
93
107
  # @param entries — the raw `plugins:` list from the configuration.
94
108
  def load(entries)
95
109
  plugins = []
@@ -233,7 +247,13 @@ module Rigor
233
247
  # `feature_resolver` is unaffected: an absolute-path require's `$LOADED_FEATURES` entry still ends in
234
248
  # `/<gem>.rb`, the suffix {FEATURE_RESOLVER} matches.
235
249
  def require_gem!(entry)
236
- @requirer.call(self.class.bundled_plugin_path(entry[:gem]) || entry[:gem])
250
+ path = self.class.bundled_plugin_path(entry[:gem])
251
+ if path
252
+ self.class.add_bundled_lib_dirs
253
+ @requirer.call(path)
254
+ else
255
+ @requirer.call(entry[:gem])
256
+ end
237
257
  rescue ::LoadError => e
238
258
  raise LoadError.new(
239
259
  "could not load plugin gem #{entry[:gem].inspect}: #{e.message}",
@@ -272,13 +292,10 @@ module Rigor
272
292
  end
273
293
  end
274
294
 
275
- # A gem that registers more than one plugin is a meta-gem only when NONE of the ids is its own. The
276
- # FFI family (`rigor-sassc`, `rigor-ethon`, `rigor-ffi-rzmq`, `rigor-rbnacl`) each `require "rigor-ffi"`
277
- # for the shared binding analyzer, so listing one of them ALONE registered two plugins — its own and
278
- # `ffi` — and the meta-gem branch rejected the entry, telling the user to list `rigor-ffi` instead,
279
- # which activates the wrong plugin. The failure was load-order dependent (listing `rigor-ffi` first
280
- # made the nested require a no-op and the entry loaded), so it never showed up in the suite, where
281
- # some earlier example has always loaded `rigor-ffi`.
295
+ # A gem that registers several plugins is a meta-gem only when NONE of the ids is its own. The FFI family
296
+ # (`rigor-sassc`, `rigor-ethon`, `rigor-ffi-rzmq`, `rigor-rbnacl`) each `require "rigor-ffi"`, so listing
297
+ # one alone registered `ffi` too and was wrongly rejected as a meta-gem. The bug was load-order dependent
298
+ # and hid in the suite, where an earlier example always loaded `rigor-ffi`.
282
299
  #
283
300
  # `rigor-rails` stays a meta-gem under this rule: it registers eight ids and none of them is `rails`.
284
301
  def own_id_among(gem_name, ids)
@@ -48,11 +48,11 @@ module Rigor
48
48
  #
49
49
  # Slice 2 ships at the **floor**: each emit row's `name:` is the source of truth for the synthetic
50
50
  # method's name (a single `"\#{name}"` placeholder gets interpolated with the literal symbol argument at
51
- # `symbol_arg_position`). The `returns:` strings are **recorded in the manifest but not resolved**; the
52
- # engine emits synthetic methods with `Dynamic[T]` returns plus a `macro.tier_c.unresolved-return`
53
- # provenance marker. Precise return-type resolution via ADR-13's `Plugin::TypeNodeResolver` is the
54
- # **ceiling**, deferred to a later slice — the `returns:` declarations cost nothing to write today and
55
- # unlock precision then.
51
+ # `symbol_arg_position`). The synthetic method's return type is the `returns_from_arg:` lookup when it
52
+ # resolves, else a non-empty `returns:` string (`SyntheticMethodScanner#resolve_emit_return_type`); the
53
+ # dispatcher turns it into a nominal type through `MethodDispatcher#promote_via_return_type` when the
54
+ # environment knows the class. With neither, an `"untyped"` / `"void"` placeholder, or an unknown class, the
55
+ # method answers `Dynamic[Top]`. Resolution through ADR-13's `Plugin::TypeNodeResolver` is not wired.
56
56
  #
57
57
  # Engine wiring: `Inference::SyntheticMethodScanner` (slice 2b, `synthetic_method_scanner.rb`) consumes
58
58
  # `manifest.heredoc_templates`. Worked consumers: `plugins/rigor-dry-struct/` and
@@ -98,7 +98,7 @@ module Rigor
98
98
  # `\#{name}` with the call-site literal symbol) and its declared return type. The return type can be
99
99
  # a static String (resolved via `Environment#nominal_for_name` per ADR-16 slice 6b) or a
100
100
  # per-call-site lookup ({ReturnsFromArg}) — see
101
- # [ADR-18](../../../../../docs/adr/18-substrate-per-call-site-return-type.md). When both are nil, the
101
+ # [ADR-18](../../../../docs/adr/18-substrate-per-call-site-return-type.md). When both are nil, the
102
102
  # synthesised method's return type falls back to `Dynamic[Top]`.
103
103
  class Emit
104
104
  attr_reader :name, :returns, :returns_from_arg
@@ -147,8 +147,7 @@ module Rigor
147
147
  # lookup_via: { plugin_id: "dry-types", fact: :dry_type_aliases }
148
148
  # }
149
149
  #
150
- # Slice 1 (this file) ships the value class + validation only. The scanner-side arg-position
151
- # extraction + fact-store lookup land in slice 2 / 3.
150
+ # The scanner's `resolve_emit_return_type` does the arg-position extraction and fact-store lookup.
152
151
  class ReturnsFromArg
153
152
  attr_reader :position, :plugin_id, :fact
154
153
 
@@ -39,12 +39,10 @@ module Rigor
39
39
  # lexical receiver class equals or inherits from this constraint.
40
40
  # - `method_name` — Symbol naming the DSL method (e.g. `:devise`).
41
41
  # - `symbol_arg_position` — `:rest` (all positional Symbol args are traits, slice 3a's only supported
42
- # form) or a non-negative Integer (the index of a single trait symbol — reserved for future shapes;
43
- # not yet honoured by the scanner).
44
- # - `modules_by_symbol` — Hash<Symbol, String>. Maps each recognised trait symbol to a fully-qualified
45
- # module name. Symbols not in the table fall through (silent skip; the scanner emits a
46
- # `macro.tier_b.unknown-trait` `:info` provenance marker per WD9 / WD13).
47
- # - `always_included` — Array<String>. Fully-qualified module names that are added to every call site
42
+ # form) or a non-negative Integer (the index of a single trait symbol).
43
+ # - `modules_by_symbol` — Maps each recognised trait symbol to a fully-qualified module name. Symbols not
44
+ # in the table are silently skipped.
45
+ # - `always_included` — Fully-qualified module names that are added to every call site
48
46
  # (even when no symbols match). Mirrors Devise's `always_include` modules.
49
47
  #
50
48
  # ## Floor / ceiling per ADR-16 WD13
@@ -53,7 +51,7 @@ module Rigor
53
51
  # methods into the existing `SyntheticMethodIndex` (slice 2b primitive). The synthesised methods adopt
54
52
  # the module's authored RBS return types — Tier B is NOT subject to the Tier C `Dynamic[T]` floor
55
53
  # because the source-of-truth (the module's authored RBS) is not a manifest-declared string. Per ADR-5
56
- # robustness, the substrate does not fabricate precision; it simply replays the modules's signatures.
54
+ # robustness, the substrate does not fabricate precision; it simply replays the module's signatures.
57
55
  #
58
56
  # **Out of scope for slice 3** (deferred follow-ups):
59
57
  # - `class_methods_module:` per-trait (Devise's `ClassMethods` extend-pattern); slice 3 covers instance
@@ -106,8 +104,7 @@ module Rigor
106
104
  end
107
105
 
108
106
  # @return fully-qualified module name for the given trait symbol, or nil when the
109
- # registry doesn't know the symbol (caller emits a tier_b.unknown-trait provenance marker and falls
110
- # through).
107
+ # registry doesn't know the symbol (the caller skips it).
111
108
  def module_for(symbol)
112
109
  modules_by_symbol[symbol.to_sym]
113
110
  end
@@ -168,9 +168,8 @@ module Rigor
168
168
  end
169
169
  private :assign_additional_initializers
170
170
 
171
- # ADR-103 WD2 / WD6 / WD10 / WD14 — the effect contract's five fields, assigned together and outside
172
- # `assign_fields` (which already carries the maximum positional arity).
173
- # The six `effect_*` fields validate together, so {#initialize} carries one call rather than six.
171
+ # ADR-103 WD2 / WD6 / WD10 / WD14 — the six `effect_*` fields validate together here, so {#initialize}
172
+ # carries one call rather than six.
174
173
  def validate_effect_fields!(effect_root, effect_labels, effect_attributions, effect_edges,
175
174
  effect_entry_points, effect_ancestry)
176
175
  validate_effect_root!(effect_root)
@@ -475,10 +474,9 @@ module Rigor
475
474
  end
476
475
  end
477
476
 
478
- # ADR-13 slice 2 — `type_node_resolvers:` declares the plugin-supplied `TypeNodeResolver` instances the
479
- # parser consults (in slice 3) when an RBS::Extended payload's named- or generic-type head misses the
480
- # built-in registry. Slice 2 carries the declarations on the manifest and the registry exposes them in
481
- # registration order; the parser integration that actually drives the chain lands in slice 3.
477
+ # ADR-13 — `type_node_resolvers:` declares the plugin-supplied `TypeNodeResolver` instances the parser
478
+ # consults when an RBS::Extended payload's named- or generic-type head misses the built-in registry. The
479
+ # registry exposes them in registration order, and `Environment#build_name_scope` builds the chain.
482
480
  def validate_type_node_resolvers!(resolvers)
483
481
  validate_array_of!("type_node_resolvers", resolvers, "Rigor::Plugin::TypeNodeResolver instances") do |r|
484
482
  r.is_a?(TypeNodeResolver)
@@ -486,8 +484,7 @@ module Rigor
486
484
  end
487
485
 
488
486
  # ADR-16 slice 1a — `block_as_methods:` declares the Tier A substrate entries the plugin contributes.
489
- # Slice 1a carries the declarations on the manifest; the engine hook that actually narrows
490
- # `Scope#self_type` for matching blocks arrives in a subsequent slice.
487
+ # The engine hook that narrows `Scope#self_type` for matching blocks is `Inference::MacroBlockSelfType`.
491
488
  def validate_block_as_methods!(entries)
492
489
  validate_array_of!("block_as_methods", entries, "Rigor::Plugin::Macro::BlockAsMethod instances") do |e|
493
490
  e.is_a?(Macro::BlockAsMethod)
@@ -495,8 +492,7 @@ module Rigor
495
492
  end
496
493
 
497
494
  # ADR-16 slice 2a — `heredoc_templates:` declares the Tier C substrate entries (heredoc-template
498
- # synthesis on class-level DSL calls). Slice 2a carries the declarations on the manifest; the pre-pass +
499
- # `SyntheticMethodIndex` that actually emit synthetic methods arrive in slice 2b.
495
+ # synthesis on class-level DSL calls). `Inference::SyntheticMethodScanner` emits the synthetic methods.
500
496
  def validate_heredoc_templates!(entries)
501
497
  validate_array_of!("heredoc_templates", entries, "Rigor::Plugin::Macro::HeredocTemplate instances") do |e|
502
498
  e.is_a?(Macro::HeredocTemplate)
@@ -515,8 +511,8 @@ module Rigor
515
511
  end
516
512
 
517
513
  # ADR-16 slice 3a — `trait_registries:` declares the Tier B substrate entries (trait-inlining via
518
- # bundled module registry). Slice 3a carries the declarations on the manifest; the scanner + per-method
519
- # explosion through `SyntheticMethodIndex` (slice 2b primitive) arrives in slice 3b.
514
+ # bundled module registry). `Inference::SyntheticMethodScanner#collect_trait_registries` explodes them
515
+ # per method through `SyntheticMethodIndex`.
520
516
  def validate_trait_registries!(entries)
521
517
  validate_array_of!("trait_registries", entries, "Rigor::Plugin::Macro::TraitRegistry instances") do |e|
522
518
  e.is_a?(Macro::TraitRegistry)
@@ -214,7 +214,7 @@ module Rigor
214
214
  # tie-breaker.
215
215
  #
216
216
  # ADR-15 Phase 3 — alongside the instantiated `plugins`, the registry carries `blueprints`: a frozen,
217
- # Ractor-shareable `Array<Blueprint>` that records how to re-instantiate the same plugin set in a worker
217
+ # Ractor-shareable list of {Blueprint} that records how to re-instantiate the same plugin set in a worker
218
218
  # Ractor. The eventual Phase 4 pool ships `blueprints` across the boundary and calls {.materialize}
219
219
  # per-Ractor; the live `plugins` carriage on the coordinator registry stays unchanged.
220
220
  class Registry
@@ -556,8 +556,8 @@ module Rigor
556
556
  # Issue #806 — {#safe_manifest} with a receipt. The construction-time aggregates degraded silently
557
557
  # when a manifest read raised, so a plugin that contributed nothing looked identical to a plugin that
558
558
  # was never asked; the load-error channel is where "a plugin could not be loaded" already lives, and
559
- # `Analysis::Runner` turns each entry into one `plugin_loader.load-error` row naming the plugin. The
560
- # plugin ref is the CLASS, not `manifest.id`: the manifest is the surface that just failed.
559
+ # `Analysis::Runner` turns each entry into one `load-error` row (source family `plugin_loader`) naming
560
+ # the plugin. The plugin ref is the CLASS, not `manifest.id`: the manifest is the surface that just failed.
561
561
  def guarded_manifest(plugin, errors)
562
562
  plugin.manifest
563
563
  rescue StandardError => e
@@ -25,9 +25,8 @@ module Rigor
25
25
  # documents the trust model the boundary enforces.
26
26
  #
27
27
  # ADR-9 slice 2 adds `fact_store`: the per-run cross-plugin `Plugin::FactStore`. Producer plugins publish
28
- # their facts in `#prepare(services)` (slice 3); consumer plugins read in `#diagnostics_for_file` via
29
- # `services.fact_store.read(...)`. A fresh `FactStore` instance is constructed per Services when none is
30
- # supplied — the runner threads its own instance in once slice 3 wires `#prepare` invocation.
28
+ # their facts in `#prepare(services)`; consumer plugins read in `#diagnostics_for_file` via
29
+ # `services.fact_store.read(...)`. A fresh `FactStore` is constructed when none is supplied.
31
30
  class Services
32
31
  attr_reader :reflection, :type, :configuration, :cache_store, :trust_policy, :fact_store
33
32
 
@@ -61,6 +61,17 @@ module Rigor
61
61
  def empty?
62
62
  @entries.empty?
63
63
  end
64
+
65
+ # The pool drain ({Rigor::Analysis::WorkerSession#drain_reporters}): the frozen entries.
66
+ def snapshot
67
+ entries
68
+ end
69
+
70
+ # Replays one worker's {#snapshot}, `kind:` (#824) and `line:` (#1075) included; the dedup above
71
+ # collapses every worker's copy. Nil records nothing.
72
+ def absorb(snapshot)
73
+ Array(snapshot).each { |entry| record(**entry.to_h) }
74
+ end
64
75
  end
65
76
  end
66
77
  end
@@ -19,8 +19,8 @@ module Rigor
19
19
  # - `trusted_gems`: gem names the user has authorised. Derived from the `plugins:` section of `.rigor.yml`
20
20
  # plus any gems they reach transitively. Used today for documentation and future trust diagnostics.
21
21
  # - `allowed_read_roots`: absolute paths plugin code may read from through the {IoBoundary}. The default
22
- # set covers the project root, the project's `signature_paths`, the active `Gemfile.lock`, and each
23
- # trusted gem's `Gem::Specification#full_gem_path`. The user extends this with `.rigor.yml`'s
22
+ # set covers the project root, the project's `signature_paths`, and each trusted
23
+ # gem's `Gem::Specification#full_gem_path`. The user extends this with `.rigor.yml`'s
24
24
  # `plugins_io.allowed_paths:`.
25
25
  # - `network_policy`: one of {VALID_NETWORK_POLICIES}. `:disabled` (default) makes
26
26
  # {IoBoundary#open_url} always raise. `:allowlist` (v0.1.2) consults `allowed_url_hosts` on every fetch —
@@ -28,10 +28,8 @@ module Rigor
28
28
  # Resolvers SHOULD be stateless and re-entrant; the registry builds the chain once per
29
29
  # `Analysis::Runner.run` and may consult any resolver multiple times for the same node.
30
30
  class TypeNodeResolver
31
- # @param node — the parser-emitted node the chain is
32
- # asking about.
33
- # @param scope — companion value object (slice 3); slice 2 invocations MAY pass
34
- # `nil` because the chain doesn't exist yet.
31
+ # @param node — the parser-emitted node the chain is asking about.
32
+ # @param scope — the scope the chain was invoked with.
35
33
  # @return resolved type, or `nil` to fall through.
36
34
  def resolve(node, scope) # rubocop:disable Lint/UnusedMethodArgument
37
35
  nil