rigortype 0.2.7 → 0.2.8

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 (414) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/docs/manual/04-diagnostics.md +7 -4
  4. data/docs/manual/06-baseline.md +5 -2
  5. data/docs/manual/15-type-protection-coverage.md +6 -4
  6. data/lib/rigor/analysis/baseline.rb +56 -96
  7. data/lib/rigor/analysis/buffer_binding.rb +13 -20
  8. data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +29 -48
  9. data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +35 -54
  10. data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +23 -37
  11. data/lib/rigor/analysis/check_rules/main_pass_collector.rb +16 -23
  12. data/lib/rigor/analysis/check_rules/rule_walk.rb +48 -71
  13. data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +19 -28
  14. data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +48 -67
  15. data/lib/rigor/analysis/check_rules.rb +174 -274
  16. data/lib/rigor/analysis/dependency_recorder.rb +26 -38
  17. data/lib/rigor/analysis/dependency_source_inference/boundary_cross_reporter.rb +13 -25
  18. data/lib/rigor/analysis/dependency_source_inference/builder.rb +17 -30
  19. data/lib/rigor/analysis/dependency_source_inference/gem_resolver.rb +15 -26
  20. data/lib/rigor/analysis/dependency_source_inference/index.rb +45 -76
  21. data/lib/rigor/analysis/dependency_source_inference/return_type_heuristic.rb +23 -40
  22. data/lib/rigor/analysis/dependency_source_inference/walker.rb +50 -83
  23. data/lib/rigor/analysis/dependency_source_inference.rb +6 -10
  24. data/lib/rigor/analysis/diagnostic.rb +48 -75
  25. data/lib/rigor/analysis/erb_template_detector.rb +13 -18
  26. data/lib/rigor/analysis/fact_store.rb +12 -22
  27. data/lib/rigor/analysis/incremental.rb +47 -63
  28. data/lib/rigor/analysis/incremental_session.rb +74 -100
  29. data/lib/rigor/analysis/project_scan.rb +12 -23
  30. data/lib/rigor/analysis/result.rb +3 -5
  31. data/lib/rigor/analysis/rule_catalog.rb +67 -95
  32. data/lib/rigor/analysis/run_stats.rb +38 -61
  33. data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +101 -160
  34. data/lib/rigor/analysis/runner/pool_coordinator.rb +121 -188
  35. data/lib/rigor/analysis/runner/project_pre_passes.rb +62 -96
  36. data/lib/rigor/analysis/runner/run_snapshots.rb +12 -18
  37. data/lib/rigor/analysis/runner.rb +220 -333
  38. data/lib/rigor/analysis/self_call_resolution_recorder.rb +30 -43
  39. data/lib/rigor/analysis/worker_session.rb +64 -102
  40. data/lib/rigor/ast/type_node.rb +6 -8
  41. data/lib/rigor/ast.rb +6 -10
  42. data/lib/rigor/bleeding_edge.rb +27 -35
  43. data/lib/rigor/builtins/hkt_builtins.rb +7 -14
  44. data/lib/rigor/builtins/imported_refinements.rb +106 -149
  45. data/lib/rigor/builtins/predefined_constant_refinements.rb +51 -65
  46. data/lib/rigor/builtins/regex_refinement.rb +32 -43
  47. data/lib/rigor/builtins/static_return_refinements.rb +57 -81
  48. data/lib/rigor/cache/descriptor.rb +58 -94
  49. data/lib/rigor/cache/incremental_snapshot.rb +32 -48
  50. data/lib/rigor/cache/rbs_cache_producer.rb +10 -15
  51. data/lib/rigor/cache/rbs_class_ancestor_table.rb +10 -16
  52. data/lib/rigor/cache/rbs_class_type_param_names.rb +10 -16
  53. data/lib/rigor/cache/rbs_constant_table.rb +8 -13
  54. data/lib/rigor/cache/rbs_descriptor.rb +8 -13
  55. data/lib/rigor/cache/rbs_environment.rb +11 -19
  56. data/lib/rigor/cache/rbs_environment_marshal_patch.rb +11 -19
  57. data/lib/rigor/cache/rbs_known_class_names.rb +8 -12
  58. data/lib/rigor/cache/store.rb +96 -151
  59. data/lib/rigor/cli/annotate_command.rb +51 -88
  60. data/lib/rigor/cli/baseline_command.rb +10 -17
  61. data/lib/rigor/cli/check_command.rb +121 -199
  62. data/lib/rigor/cli/check_runner_factory.rb +8 -14
  63. data/lib/rigor/cli/ci_detector.rb +9 -14
  64. data/lib/rigor/cli/command.rb +8 -14
  65. data/lib/rigor/cli/coverage_command.rb +19 -26
  66. data/lib/rigor/cli/coverage_mutation.rb +14 -19
  67. data/lib/rigor/cli/coverage_report.rb +0 -2
  68. data/lib/rigor/cli/coverage_scan.rb +7 -11
  69. data/lib/rigor/cli/diagnostic_formats.rb +43 -62
  70. data/lib/rigor/cli/diff_command.rb +10 -20
  71. data/lib/rigor/cli/docs_command.rb +26 -40
  72. data/lib/rigor/cli/doctor_command.rb +8 -9
  73. data/lib/rigor/cli/explain_command.rb +4 -7
  74. data/lib/rigor/cli/fused_protection_renderer.rb +4 -5
  75. data/lib/rigor/cli/fused_protection_report.rb +6 -8
  76. data/lib/rigor/cli/lsp_command.rb +11 -19
  77. data/lib/rigor/cli/mcp_command.rb +4 -6
  78. data/lib/rigor/cli/mutation_protection_renderer.rb +3 -4
  79. data/lib/rigor/cli/mutation_protection_report.rb +6 -9
  80. data/lib/rigor/cli/options.rb +11 -19
  81. data/lib/rigor/cli/plugin_command.rb +18 -30
  82. data/lib/rigor/cli/plugins_command.rb +29 -51
  83. data/lib/rigor/cli/plugins_renderer.rb +12 -20
  84. data/lib/rigor/cli/prism_colorizer.rb +13 -19
  85. data/lib/rigor/cli/protection_renderer.rb +5 -7
  86. data/lib/rigor/cli/protection_report.rb +27 -17
  87. data/lib/rigor/cli/renderable.rb +4 -6
  88. data/lib/rigor/cli/show_bleedingedge_command.rb +5 -8
  89. data/lib/rigor/cli/sig_gen_command.rb +14 -26
  90. data/lib/rigor/cli/skill_command.rb +30 -47
  91. data/lib/rigor/cli/skill_describe.rb +40 -64
  92. data/lib/rigor/cli/trace_command.rb +9 -16
  93. data/lib/rigor/cli/trace_renderer.rb +35 -51
  94. data/lib/rigor/cli/triage_command.rb +6 -10
  95. data/lib/rigor/cli/triage_renderer.rb +1 -2
  96. data/lib/rigor/cli/type_of_command.rb +14 -23
  97. data/lib/rigor/cli/type_of_renderer.rb +3 -5
  98. data/lib/rigor/cli/type_scan_command.rb +6 -10
  99. data/lib/rigor/cli/type_scan_renderer.rb +3 -5
  100. data/lib/rigor/cli/type_scan_report.rb +2 -3
  101. data/lib/rigor/cli/upgrade_command.rb +2 -3
  102. data/lib/rigor/cli.rb +16 -28
  103. data/lib/rigor/config_audit.rb +35 -44
  104. data/lib/rigor/configuration/dependencies.rb +41 -70
  105. data/lib/rigor/configuration/severity_profile.rb +28 -42
  106. data/lib/rigor/configuration.rb +150 -241
  107. data/lib/rigor/environment/bundle_sig_discovery.rb +74 -118
  108. data/lib/rigor/environment/class_registry.rb +19 -26
  109. data/lib/rigor/environment/constant_type_cache_holder.rb +14 -21
  110. data/lib/rigor/environment/hkt_registry_holder.rb +6 -12
  111. data/lib/rigor/environment/lockfile_resolver.rb +31 -47
  112. data/lib/rigor/environment/rbs_collection_discovery.rb +39 -63
  113. data/lib/rigor/environment/rbs_coverage_report.rb +24 -42
  114. data/lib/rigor/environment/rbs_loader.rb +420 -518
  115. data/lib/rigor/environment/reflection.rb +28 -53
  116. data/lib/rigor/environment/reporters.rb +13 -24
  117. data/lib/rigor/environment.rb +176 -291
  118. data/lib/rigor/flow_contribution/conflict.rb +16 -27
  119. data/lib/rigor/flow_contribution/element.rb +7 -12
  120. data/lib/rigor/flow_contribution/fact.rb +33 -57
  121. data/lib/rigor/flow_contribution/merge_result.rb +6 -9
  122. data/lib/rigor/flow_contribution/merger.rb +32 -47
  123. data/lib/rigor/flow_contribution.rb +37 -55
  124. data/lib/rigor/inference/acceptance.rb +133 -219
  125. data/lib/rigor/inference/block_parameter_binder.rb +52 -82
  126. data/lib/rigor/inference/body_fixpoint.rb +22 -30
  127. data/lib/rigor/inference/budget_trace.rb +43 -63
  128. data/lib/rigor/inference/builtins/array_catalog.rb +4 -6
  129. data/lib/rigor/inference/builtins/comparable_catalog.rb +4 -6
  130. data/lib/rigor/inference/builtins/complex_catalog.rb +13 -19
  131. data/lib/rigor/inference/builtins/date_catalog.rb +38 -67
  132. data/lib/rigor/inference/builtins/encoding_catalog.rb +22 -36
  133. data/lib/rigor/inference/builtins/enumerable_catalog.rb +4 -6
  134. data/lib/rigor/inference/builtins/exception_catalog.rb +38 -57
  135. data/lib/rigor/inference/builtins/hash_catalog.rb +9 -14
  136. data/lib/rigor/inference/builtins/method_catalog.rb +29 -48
  137. data/lib/rigor/inference/builtins/numeric_catalog.rb +13 -18
  138. data/lib/rigor/inference/builtins/pathname_catalog.rb +8 -13
  139. data/lib/rigor/inference/builtins/proc_catalog.rb +38 -62
  140. data/lib/rigor/inference/builtins/random_catalog.rb +21 -32
  141. data/lib/rigor/inference/builtins/range_catalog.rb +12 -21
  142. data/lib/rigor/inference/builtins/rational_catalog.rb +9 -15
  143. data/lib/rigor/inference/builtins/re_catalog.rb +29 -48
  144. data/lib/rigor/inference/builtins/set_catalog.rb +18 -27
  145. data/lib/rigor/inference/builtins/string_catalog.rb +12 -19
  146. data/lib/rigor/inference/builtins/struct_catalog.rb +18 -30
  147. data/lib/rigor/inference/builtins/time_catalog.rb +28 -44
  148. data/lib/rigor/inference/closure_escape_analyzer.rb +44 -67
  149. data/lib/rigor/inference/coverage_scanner.rb +13 -18
  150. data/lib/rigor/inference/def_return_typer.rb +8 -14
  151. data/lib/rigor/inference/dynamic_origin.rb +23 -20
  152. data/lib/rigor/inference/expression_typer.rb +774 -1152
  153. data/lib/rigor/inference/fallback.rb +8 -12
  154. data/lib/rigor/inference/fallback_tracer.rb +4 -10
  155. data/lib/rigor/inference/flow_tracer.rb +25 -36
  156. data/lib/rigor/inference/hkt_body.rb +45 -68
  157. data/lib/rigor/inference/hkt_body_parser.rb +33 -52
  158. data/lib/rigor/inference/hkt_reducer.rb +38 -59
  159. data/lib/rigor/inference/hkt_registry.rb +50 -76
  160. data/lib/rigor/inference/indexed_narrowing.rb +55 -81
  161. data/lib/rigor/inference/macro_block_self_type.rb +21 -34
  162. data/lib/rigor/inference/method_dispatcher/array_to_h_folding.rb +11 -18
  163. data/lib/rigor/inference/method_dispatcher/block_folding.rb +49 -83
  164. data/lib/rigor/inference/method_dispatcher/call_context.rb +16 -23
  165. data/lib/rigor/inference/method_dispatcher/cgi_folding.rb +13 -22
  166. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +307 -454
  167. data/lib/rigor/inference/method_dispatcher/data_folding.rb +24 -34
  168. data/lib/rigor/inference/method_dispatcher/file_folding.rb +22 -37
  169. data/lib/rigor/inference/method_dispatcher/iterator_dispatch.rb +52 -81
  170. data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +51 -78
  171. data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +60 -97
  172. data/lib/rigor/inference/method_dispatcher/math_folding.rb +23 -34
  173. data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +18 -26
  174. data/lib/rigor/inference/method_dispatcher/method_folding.rb +37 -71
  175. data/lib/rigor/inference/method_dispatcher/overload_selector.rb +114 -182
  176. data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +122 -193
  177. data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +15 -24
  178. data/lib/rigor/inference/method_dispatcher/reduce_folding.rb +64 -93
  179. data/lib/rigor/inference/method_dispatcher/regexp_folding.rb +31 -45
  180. data/lib/rigor/inference/method_dispatcher/set_folding.rb +6 -9
  181. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +356 -511
  182. data/lib/rigor/inference/method_dispatcher/shellwords_folding.rb +22 -32
  183. data/lib/rigor/inference/method_dispatcher/singleton_folding.rb +15 -21
  184. data/lib/rigor/inference/method_dispatcher/struct_folding.rb +66 -82
  185. data/lib/rigor/inference/method_dispatcher/time_folding.rb +10 -15
  186. data/lib/rigor/inference/method_dispatcher/uri_folding.rb +9 -13
  187. data/lib/rigor/inference/method_dispatcher.rb +378 -524
  188. data/lib/rigor/inference/method_parameter_binder.rb +81 -114
  189. data/lib/rigor/inference/multi_target_binder.rb +51 -68
  190. data/lib/rigor/inference/mutation_widening.rb +115 -158
  191. data/lib/rigor/inference/narrowing.rb +509 -727
  192. data/lib/rigor/inference/origin_lookup.rb +38 -0
  193. data/lib/rigor/inference/parameter_inference_collector.rb +100 -100
  194. data/lib/rigor/inference/precision_scanner.rb +26 -35
  195. data/lib/rigor/inference/project_patched_methods.rb +20 -32
  196. data/lib/rigor/inference/project_patched_scanner.rb +23 -37
  197. data/lib/rigor/inference/protection_scanner.rb +16 -18
  198. data/lib/rigor/inference/rbs_type_translator.rb +49 -66
  199. data/lib/rigor/inference/scope_indexer.rb +473 -817
  200. data/lib/rigor/inference/statement_evaluator.rb +706 -1111
  201. data/lib/rigor/inference/struct_fold_safety.rb +34 -48
  202. data/lib/rigor/inference/synthetic_method.rb +9 -16
  203. data/lib/rigor/inference/synthetic_method_index.rb +20 -35
  204. data/lib/rigor/inference/synthetic_method_scanner.rb +63 -103
  205. data/lib/rigor/language_server/buffer_resolution.rb +6 -9
  206. data/lib/rigor/language_server/buffer_table.rb +11 -18
  207. data/lib/rigor/language_server/completion_provider.rb +69 -116
  208. data/lib/rigor/language_server/debouncer.rb +16 -25
  209. data/lib/rigor/language_server/diagnostic_publisher.rb +26 -40
  210. data/lib/rigor/language_server/document_symbol_provider.rb +14 -19
  211. data/lib/rigor/language_server/folding_range_provider.rb +11 -19
  212. data/lib/rigor/language_server/hover_provider.rb +8 -11
  213. data/lib/rigor/language_server/hover_renderer.rb +56 -93
  214. data/lib/rigor/language_server/loop.rb +14 -19
  215. data/lib/rigor/language_server/project_context.rb +46 -74
  216. data/lib/rigor/language_server/selection_range_provider.rb +9 -12
  217. data/lib/rigor/language_server/server.rb +55 -83
  218. data/lib/rigor/language_server/signature_help_provider.rb +29 -46
  219. data/lib/rigor/language_server/synchronized_writer.rb +4 -7
  220. data/lib/rigor/language_server/uri.rb +8 -13
  221. data/lib/rigor/language_server.rb +4 -6
  222. data/lib/rigor/mcp/loop.rb +2 -3
  223. data/lib/rigor/mcp/server.rb +4 -7
  224. data/lib/rigor/mcp.rb +3 -6
  225. data/lib/rigor/plugin/access_denied_error.rb +5 -8
  226. data/lib/rigor/plugin/additional_initializer.rb +21 -31
  227. data/lib/rigor/plugin/base.rb +335 -518
  228. data/lib/rigor/plugin/blueprint.rb +14 -23
  229. data/lib/rigor/plugin/box.rb +18 -29
  230. data/lib/rigor/plugin/fact_store.rb +16 -26
  231. data/lib/rigor/plugin/inflector.rb +37 -53
  232. data/lib/rigor/plugin/io_boundary.rb +33 -56
  233. data/lib/rigor/plugin/isolation.rb +42 -55
  234. data/lib/rigor/plugin/load_error.rb +10 -15
  235. data/lib/rigor/plugin/loader.rb +30 -49
  236. data/lib/rigor/plugin/macro/block_as_method.rb +20 -32
  237. data/lib/rigor/plugin/macro/heredoc_template.rb +35 -58
  238. data/lib/rigor/plugin/macro/nested_class_template.rb +22 -36
  239. data/lib/rigor/plugin/macro/trait_registry.rb +34 -51
  240. data/lib/rigor/plugin/macro.rb +10 -15
  241. data/lib/rigor/plugin/manifest.rb +85 -144
  242. data/lib/rigor/plugin/node_context.rb +14 -22
  243. data/lib/rigor/plugin/node_rule_walk.rb +49 -74
  244. data/lib/rigor/plugin/protocol_contract.rb +25 -39
  245. data/lib/rigor/plugin/registry.rb +132 -205
  246. data/lib/rigor/plugin/services.rb +21 -33
  247. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +11 -19
  248. data/lib/rigor/plugin/trust_policy.rb +24 -38
  249. data/lib/rigor/plugin/type_node_resolver.rb +15 -24
  250. data/lib/rigor/protection/diagnostic_oracle.rb +11 -13
  251. data/lib/rigor/protection/mutation_scanner.rb +27 -35
  252. data/lib/rigor/protection/mutator.rb +50 -70
  253. data/lib/rigor/protection/test_suite_oracle.rb +20 -27
  254. data/lib/rigor/rbs_extended/conformance_checker.rb +48 -68
  255. data/lib/rigor/rbs_extended/hkt_directives.rb +28 -54
  256. data/lib/rigor/rbs_extended/reporter.rb +24 -40
  257. data/lib/rigor/rbs_extended.rb +107 -197
  258. data/lib/rigor/reflection.rb +68 -86
  259. data/lib/rigor/scope/discovery_index.rb +14 -19
  260. data/lib/rigor/scope.rb +255 -310
  261. data/lib/rigor/sig_gen/classification.rb +6 -10
  262. data/lib/rigor/sig_gen/generator.rb +197 -323
  263. data/lib/rigor/sig_gen/layout_index.rb +12 -20
  264. data/lib/rigor/sig_gen/method_candidate.rb +12 -17
  265. data/lib/rigor/sig_gen/observation_collector.rb +38 -70
  266. data/lib/rigor/sig_gen/observed_call.rb +13 -23
  267. data/lib/rigor/sig_gen/path_mapper.rb +17 -29
  268. data/lib/rigor/sig_gen/renderer.rb +7 -13
  269. data/lib/rigor/sig_gen/type_elaborator.rb +15 -28
  270. data/lib/rigor/sig_gen/write_result.rb +8 -16
  271. data/lib/rigor/sig_gen/writer.rb +95 -174
  272. data/lib/rigor/sig_gen.rb +3 -6
  273. data/lib/rigor/signature_path_audit.rb +24 -30
  274. data/lib/rigor/source/constant_path.rb +10 -14
  275. data/lib/rigor/source/literals.rb +31 -45
  276. data/lib/rigor/source/node_locator.rb +9 -11
  277. data/lib/rigor/source/node_walker.rb +9 -13
  278. data/lib/rigor/source.rb +3 -4
  279. data/lib/rigor/testing.rb +16 -20
  280. data/lib/rigor/triage/catalogue.rb +38 -62
  281. data/lib/rigor/triage.rb +31 -52
  282. data/lib/rigor/trinary.rb +9 -13
  283. data/lib/rigor/type/acceptance_router.rb +4 -6
  284. data/lib/rigor/type/accepts_result.rb +10 -14
  285. data/lib/rigor/type/app.rb +19 -27
  286. data/lib/rigor/type/bot.rb +4 -6
  287. data/lib/rigor/type/bound_method.rb +10 -15
  288. data/lib/rigor/type/combinator.rb +165 -257
  289. data/lib/rigor/type/constant.rb +23 -34
  290. data/lib/rigor/type/data_class.rb +10 -15
  291. data/lib/rigor/type/data_instance.rb +14 -20
  292. data/lib/rigor/type/difference.rb +21 -32
  293. data/lib/rigor/type/dynamic.rb +3 -5
  294. data/lib/rigor/type/hash_shape.rb +32 -18
  295. data/lib/rigor/type/integer_range.rb +11 -16
  296. data/lib/rigor/type/intersection.rb +27 -42
  297. data/lib/rigor/type/nominal.rb +10 -15
  298. data/lib/rigor/type/plain_lattice.rb +9 -13
  299. data/lib/rigor/type/refined.rb +67 -114
  300. data/lib/rigor/type/singleton.rb +4 -6
  301. data/lib/rigor/type/struct_class.rb +11 -16
  302. data/lib/rigor/type/struct_instance.rb +15 -21
  303. data/lib/rigor/type/tuple.rb +14 -19
  304. data/lib/rigor/type/union.rb +30 -42
  305. data/lib/rigor/type_node/generic.rb +14 -26
  306. data/lib/rigor/type_node/identifier.rb +12 -19
  307. data/lib/rigor/type_node.rb +3 -12
  308. data/lib/rigor/value_semantics.rb +16 -21
  309. data/lib/rigor/version.rb +1 -1
  310. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/analyzer.rb +18 -30
  311. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +16 -29
  312. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_index.rb +12 -23
  313. data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +22 -39
  314. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/analyzer.rb +26 -46
  315. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +51 -87
  316. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_index.rb +10 -17
  317. data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer.rb +26 -39
  318. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/analyzer.rb +118 -176
  319. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +30 -51
  320. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_index.rb +57 -92
  321. data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack.rb +71 -108
  322. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/analyzer.rb +13 -22
  323. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +15 -25
  324. data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_index.rb +8 -13
  325. data/plugins/rigor-activejob/lib/rigor/plugin/activejob.rb +16 -24
  326. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +30 -47
  327. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +79 -129
  328. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_index.rb +36 -57
  329. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +27 -41
  330. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_table.rb +16 -24
  331. data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord.rb +148 -235
  332. data/plugins/rigor-activerecord/lib/rigor-activerecord.rb +3 -5
  333. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +8 -14
  334. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +9 -15
  335. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_index.rb +4 -7
  336. data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage.rb +23 -41
  337. data/plugins/rigor-activestorage/lib/rigor-activestorage.rb +3 -5
  338. data/plugins/rigor-activesupport-core-ext/lib/rigor/plugin/activesupport_core_ext.rb +6 -10
  339. data/plugins/rigor-activesupport-core-ext/lib/rigor-activesupport-core-ext.rb +8 -11
  340. data/plugins/rigor-devise/lib/rigor/plugin/devise.rb +30 -45
  341. data/plugins/rigor-devise/lib/rigor-devise.rb +3 -5
  342. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema/schema_scanner.rb +43 -69
  343. data/plugins/rigor-dry-schema/lib/rigor/plugin/dry_schema.rb +26 -35
  344. data/plugins/rigor-dry-schema/lib/rigor-dry-schema.rb +3 -5
  345. data/plugins/rigor-dry-struct/lib/rigor/plugin/dry_struct.rb +30 -47
  346. data/plugins/rigor-dry-struct/lib/rigor-dry-struct.rb +3 -5
  347. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types/alias_scanner.rb +67 -113
  348. data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +29 -46
  349. data/plugins/rigor-dry-types/lib/rigor-dry-types.rb +3 -5
  350. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation/contract_scanner.rb +13 -20
  351. data/plugins/rigor-dry-validation/lib/rigor/plugin/dry_validation.rb +14 -23
  352. data/plugins/rigor-dry-validation/lib/rigor-dry-validation.rb +2 -3
  353. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/analyzer.rb +22 -37
  354. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +28 -46
  355. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_index.rb +13 -17
  356. data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot.rb +30 -47
  357. data/plugins/rigor-graphql/lib/rigor/plugin/graphql/type_scanner.rb +52 -80
  358. data/plugins/rigor-graphql/lib/rigor/plugin/graphql.rb +21 -31
  359. data/plugins/rigor-graphql/lib/rigor-graphql.rb +3 -5
  360. data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +11 -17
  361. data/plugins/rigor-hanami/lib/rigor/plugin/hanami.rb +20 -34
  362. data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +45 -75
  363. data/plugins/rigor-minitest/lib/rigor/plugin/minitest/assertion_analyzer.rb +22 -36
  364. data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +22 -41
  365. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/analyzer.rb +17 -32
  366. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +6 -12
  367. data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_index.rb +4 -8
  368. data/plugins/rigor-pundit/lib/rigor/plugin/pundit.rb +17 -32
  369. data/plugins/rigor-rails/lib/rigor-rails.rb +11 -18
  370. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/analyzer.rb +58 -92
  371. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_index.rb +20 -34
  372. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n/locale_loader.rb +20 -31
  373. data/plugins/rigor-rails-i18n/lib/rigor/plugin/rails_i18n.rb +49 -82
  374. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/analyzer.rb +55 -92
  375. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/devise_routes.rb +51 -90
  376. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/doorkeeper_routes.rb +17 -30
  377. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +38 -70
  378. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_table.rb +42 -74
  379. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +280 -497
  380. data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes.rb +54 -89
  381. data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +32 -54
  382. data/plugins/rigor-rbs-inline/lib/rigor-rbs-inline.rb +8 -13
  383. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/analyzer.rb +9 -16
  384. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +24 -39
  385. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_type_resolver.rb +13 -22
  386. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/matcher_analyzer.rb +34 -56
  387. data/plugins/rigor-rspec/lib/rigor/plugin/rspec/scope_walker.rb +22 -42
  388. data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +43 -75
  389. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/have_http_status_analyzer.rb +27 -39
  390. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/http_status_codes.rb +14 -19
  391. data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails.rb +20 -34
  392. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers/analyzer.rb +28 -47
  393. data/plugins/rigor-shoulda-matchers/lib/rigor/plugin/shoulda_matchers.rb +21 -37
  394. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/analyzer.rb +13 -22
  395. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +18 -31
  396. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_index.rb +6 -10
  397. data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq.rb +14 -29
  398. data/plugins/rigor-sinatra/lib/rigor/plugin/sinatra.rb +23 -41
  399. data/plugins/rigor-sinatra/lib/rigor-sinatra.rb +3 -5
  400. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/absurd_recognizer.rb +23 -38
  401. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/assertion_recognizer.rb +57 -96
  402. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog.rb +22 -35
  403. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +41 -69
  404. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/method_signature.rb +6 -10
  405. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sig_parser.rb +24 -41
  406. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/sigil_detector.rb +29 -50
  407. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/type_translator.rb +55 -88
  408. data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +159 -262
  409. data/plugins/rigor-statesman/lib/rigor/plugin/statesman.rb +21 -29
  410. data/plugins/rigor-statesman/lib/rigor-statesman.rb +3 -5
  411. data/plugins/rigor-typescript-utility-types/lib/rigor/plugin/typescript_utility_types.rb +23 -37
  412. data/plugins/rigor-typescript-utility-types/lib/rigor-typescript-utility-types.rb +4 -6
  413. data/sig/rigor/scope.rbs +6 -0
  414. metadata +2 -1
@@ -4,20 +4,15 @@ require_relative "command"
4
4
 
5
5
  module Rigor
6
6
  class CLI
7
- # `rigor skill` — discover and print the SKILL.md files
8
- # bundled with the `rigortype` gem.
7
+ # `rigor skill` — discover and print the SKILL.md files bundled with the `rigortype` gem.
9
8
  #
10
- # Rigor ships a small set of Agent Skills under `skills/` that
11
- # walk an AI coding agent through onboarding (`rigor-project-init`),
12
- # baseline reduction (`rigor-baseline-reduce`), and authoring a
13
- # plugin (`rigor-plugin-author`). When Rigor is installed via
14
- # `mise` / `gem install` / etc. the SKILL files live inside the
15
- # gem checkout — the project being analysed has no copy, so an
16
- # AI agent has no a priori way to find them.
9
+ # Rigor ships a small set of Agent Skills under `skills/` that walk an AI coding agent through onboarding
10
+ # (`rigor-project-init`), baseline reduction (`rigor-baseline-reduce`), and authoring a plugin
11
+ # (`rigor-plugin-author`). When Rigor is installed via `mise` / `gem install` / etc. the SKILL files live inside the
12
+ # gem checkout the project being analysed has no copy, so an AI agent has no a priori way to find them.
17
13
  #
18
- # Grammar (mirrors `rigor docs`): the positional slot is always a
19
- # skill *name*; alternative outputs are flags, so a skill named
20
- # `list` or `path` can never be shadowed by a verb.
14
+ # Grammar (mirrors `rigor docs`): the positional slot is always a skill *name*; alternative outputs are flags, so a
15
+ # skill named `list` or `path` can never be shadowed by a verb.
21
16
  #
22
17
  # - `rigor skill` — list bundled skills (the default).
23
18
  # - `rigor skill <name>` — print the SKILL.md body (header + body).
@@ -42,11 +37,9 @@ module Rigor
42
37
  # the SKILL. Also spelled `describe`, and
43
38
  # surfaced top-level as `rigor describe`.
44
39
  #
45
- # The pre-v0.3.0 verb spellings `rigor skill list` / `print <name>` /
46
- # `path <name>` still work but emit a stderr deprecation notice; they
47
- # are removed in v0.3.0 (see docs/ROADMAP.md § "Scheduled CLI
48
- # deprecations"). `describe` is a no-argument action, not a name-slot
49
- # verb, so it stays first-class alongside `--describe`.
40
+ # The pre-v0.3.0 verb spellings `rigor skill list` / `print <name>` / `path <name>` still work but emit a stderr
41
+ # deprecation notice; they are removed in v0.3.0 (see docs/ROADMAP.md § "Scheduled CLI deprecations"). `describe` is
42
+ # a no-argument action, not a name-slot verb, so it stays first-class alongside `--describe`.
50
43
  class SkillCommand < Command
51
44
  USAGE = <<~USAGE
52
45
  Usage: rigor skill [<name>] [--full <name>] [--path <name>] [--list] [--describe]
@@ -74,13 +67,12 @@ module Rigor
74
67
  rigor skill path <name> -> rigor skill --path <name>
75
68
  USAGE
76
69
 
77
- # The bundled skills live at `<gem_root>/skills/`. From
78
- # `lib/rigor/cli/skill_command.rb` that is three directories up.
70
+ # The bundled skills live at `<gem_root>/skills/`. From `lib/rigor/cli/skill_command.rb` that is three directories
71
+ # up.
79
72
  SKILLS_ROOT = File.expand_path("../../../skills", __dir__)
80
73
 
81
- # The verb subcommands the flags superseded keep working with a
82
- # stderr deprecation notice until this version drops them. Each maps
83
- # to the canonical advice printed and the flag it rewrites to.
74
+ # The verb subcommands the flags superseded keep working with a stderr deprecation notice until this version drops
75
+ # them. Each maps to the canonical advice printed and the flag it rewrites to.
84
76
  LEGACY_VERB_REMOVAL = "v0.3.0"
85
77
  LEGACY_VERBS = {
86
78
  "list" => { old: "list", advice: "--list", flag: "--list" },
@@ -120,8 +112,8 @@ module Rigor
120
112
 
121
113
  private
122
114
 
123
- # Translate a deprecated verb spelling into its flag form, warning
124
- # once on stderr, so the dispatch above only handles canonical forms.
115
+ # Translate a deprecated verb spelling into its flag form, warning once on stderr, so the dispatch above only
116
+ # handles canonical forms.
125
117
  def rewrite_legacy_verb!
126
118
  spec = LEGACY_VERBS[@argv.first]
127
119
  return unless spec
@@ -159,14 +151,11 @@ module Rigor
159
151
  0
160
152
  end
161
153
 
162
- # `rigor skill --full <name>` — the whole current procedure in one
163
- # call: the SKILL.md body followed by each `references/*.md` inline.
164
- # This is what the thinned SKILL bodies point a *frozen* copy at a
165
- # vendored copy of a skill (installed via `npx skills`) may lag the
166
- # gem, so re-fetching the complete body here guarantees the reader
167
- # follows the version that shipped with the installed Rigor, without
168
- # needing a file-reading tool or reading a possibly-stale co-located
169
- # `references/`.
154
+ # `rigor skill --full <name>` — the whole current procedure in one call: the SKILL.md body followed by each
155
+ # `references/*.md` inline. This is what the thinned SKILL bodies point a *frozen* copy at — a vendored copy of a
156
+ # skill (installed via `npx skills`) may lag the gem, so re-fetching the complete body here guarantees the reader
157
+ # follows the version that shipped with the installed Rigor, without needing a file-reading tool or reading a
158
+ # possibly-stale co-located `references/`.
170
159
  def run_full(name)
171
160
  return usage_error("`--full` requires a skill name") if name.nil?
172
161
 
@@ -197,13 +186,10 @@ module Rigor
197
186
  0
198
187
  end
199
188
 
200
- # `rigor skill --describe` (also spelled `describe`) — ADR-73's
201
- # live "brain", delegated to {SkillDescribe}: it probes the current
202
- # project's state with cheap presence checks (it never runs `rigor
203
- # check`), recommends the next skill to run, and prints every
204
- # bundled skill's current frontmatter description, so the
205
- # `rigor-next-steps` SKILL can route without copying any
206
- # version-coupled guidance into itself.
189
+ # `rigor skill --describe` (also spelled `describe`) — ADR-73's live "brain", delegated to {SkillDescribe}: it
190
+ # probes the current project's state with cheap presence checks (it never runs `rigor check`), recommends the next
191
+ # skill to run, and prints every bundled skill's current frontmatter description, so the `rigor-next-steps` SKILL
192
+ # can route without copying any version-coupled guidance into itself.
207
193
  def run_describe
208
194
  require_relative "skill_describe"
209
195
 
@@ -211,10 +197,9 @@ module Rigor
211
197
  0
212
198
  end
213
199
 
214
- # The header that precedes the SKILL.md body when an agent
215
- # runs `rigor skill <name>`. Kept as `# `-prefixed comment lines
216
- # so the combined output remains parseable as markdown — anything
217
- # below `---` (the SKILL frontmatter marker) is unchanged.
200
+ # The header that precedes the SKILL.md body when an agent runs `rigor skill <name>`. Kept as `# `-prefixed
201
+ # comment lines so the combined output remains parseable as markdown anything below `---` (the SKILL frontmatter
202
+ # marker) is unchanged.
218
203
  def render_print_header(skill)
219
204
  references_dir = File.join(File.dirname(skill.fetch(:path)), "references")
220
205
  ref_line = if File.directory?(references_dir)
@@ -247,14 +232,12 @@ module Rigor
247
232
  discover_skills.find { |s| s.fetch(:name) == name }
248
233
  end
249
234
 
250
- # The `references/*.md` files bundled alongside a skill, sorted so the
251
- # `NN-` prefixes drive read order.
235
+ # The `references/*.md` files bundled alongside a skill, sorted so the `NN-` prefixes drive read order.
252
236
  def reference_files(skill)
253
237
  dir = File.join(File.dirname(skill.fetch(:path)), "references")
254
238
  return [] unless File.directory?(dir)
255
239
 
256
- # `Dir.glob` returns lexicographically sorted paths (Ruby 3.0+),
257
- # so the `NN-` prefixes already drive read order.
240
+ # `Dir.glob` returns lexicographically sorted paths (Ruby 3.0+), so the `NN-` prefixes already drive read order.
258
241
  Dir.glob(File.join(dir, "*.md"))
259
242
  end
260
243
 
@@ -4,27 +4,21 @@ require "yaml"
4
4
 
5
5
  module Rigor
6
6
  class CLI
7
- # The presence-only project-state probe behind `rigor skill describe`
8
- # (ADR-73 WD2). It stats files and opens only config / CI / lockfiles
9
- # it runs no analysis so it is cheap and side-effect-free, and an
10
- # agent can run it freely at any point. Split from {SkillDescribe} so
11
- # the routing/rendering class stays focused.
7
+ # The presence-only project-state probe behind `rigor skill describe` (ADR-73 WD2). It stats files and opens only
8
+ # config / CI / lockfiles — it runs no analysis — so it is cheap and side-effect-free, and an agent can run it
9
+ # freely at any point. Split from {SkillDescribe} so the routing/rendering class stays focused.
12
10
  class ProjectStateProbe
13
- # Config / baseline filenames the probe stats for. `CONFIG_FILENAMES`
14
- # mirrors `Configuration::DISCOVERY_ORDER` (developer-local override
15
- # first, committed default second) so the probe agrees with what
16
- # `rigor check` would auto-discover.
11
+ # Config / baseline filenames the probe stats for. `CONFIG_FILENAMES` mirrors `Configuration::DISCOVERY_ORDER`
12
+ # (developer-local override first, committed default second) so the probe agrees with what `rigor check` would
13
+ # auto-discover.
17
14
  CONFIG_FILENAMES = %w[.rigor.yml .rigor.dist.yml].freeze
18
15
  BASELINE_FILENAME = ".rigor-baseline.yml"
19
- # `rbs collection install`'s lockfile — Rigor auto-detects it at the
20
- # project root and feeds each gem's community RBS into the signature
21
- # paths, so its presence is the signal that the gem-RBS gap has been
22
- # addressed.
16
+ # `rbs collection install`'s lockfile — Rigor auto-detects it at the project root and feeds each gem's community
17
+ # RBS into the signature paths, so its presence is the signal that the gem-RBS gap has been addressed.
23
18
  RBS_COLLECTION_LOCKFILE = "rbs_collection.lock.yaml"
24
19
 
25
- # `Gemfile.lock` substrings that mark a Rails app, and the bundled
26
- # Rails-family plugin ids used to spot a configured Rails project
27
- # that has not enabled any Rails plugin (a `rigor-plugin-tune` cue).
20
+ # `Gemfile.lock` substrings that mark a Rails app, and the bundled Rails-family plugin ids — used to spot a
21
+ # configured Rails project that has not enabled any Rails plugin (a `rigor-plugin-tune` cue).
28
22
  RAILS_LOCK_MARKERS = %w[railties actionpack activerecord actioncable].freeze
29
23
  RAILS_PLUGIN_MARKERS = %w[
30
24
  rigor-activerecord rigor-actionpack rigor-actionmailer rigor-activejob
@@ -53,11 +47,9 @@ module Rigor
53
47
 
54
48
  private
55
49
 
56
- # True when Rails is in `Gemfile.lock` but the (present) config
57
- # enables no Rails plugin so `rigor-plugin-tune` (wiring the
58
- # ActiveRecord / routes / i18n plugins) buys more than community RBS
59
- # would (the 20260620 field trial's strap case). Only fires on an
60
- # already-configured project; an un-configured Rails app routes to
50
+ # True when Rails is in `Gemfile.lock` but the (present) config enables no Rails plugin — so `rigor-plugin-tune`
51
+ # (wiring the ActiveRecord / routes / i18n plugins) buys more than community RBS would (the 20260620 field trial's
52
+ # strap case). Only fires on an already-configured project; an un-configured Rails app routes to
61
53
  # `rigor-project-init`, which selects the plugins itself.
62
54
  def rails_unconfigured?(config)
63
55
  return false if config.nil?
@@ -75,8 +67,7 @@ module Rigor
75
67
  false
76
68
  end
77
69
 
78
- # `:wired` (a CI config mentions `rigor`), `:unwired` (a CI config
79
- # exists but does not), or `:none`.
70
+ # `:wired` (a CI config mentions `rigor`), `:unwired` (a CI config exists but does not), or `:none`.
80
71
  def ci_state
81
72
  files = ci_config_files
82
73
  return :none if files.empty?
@@ -84,11 +75,9 @@ module Rigor
84
75
  files.any? { |path| file_mentions_rigor?(path) } ? :wired : :unwired
85
76
  end
86
77
 
87
- # Editor-LSP signal — `:wired` (a committed `.vscode/` config names
88
- # `rigor`), `:unwired` (a `.vscode/` is present but does not), or
89
- # `:none`. Only VS Code is detectable here: Neovim / Emacs / Helix
90
- # configs live in the user's home, not the repo, so editor-setup is
91
- # otherwise a catalogue-only destination.
78
+ # Editor-LSP signal — `:wired` (a committed `.vscode/` config names `rigor`), `:unwired` (a `.vscode/` is present
79
+ # but does not), or `:none`. Only VS Code is detectable here: Neovim / Emacs / Helix configs live in the user's
80
+ # home, not the repo, so editor-setup is otherwise a catalogue-only destination.
92
81
  def editor_state
93
82
  vscode = File.join(@root, ".vscode")
94
83
  return :none unless File.directory?(vscode)
@@ -99,11 +88,9 @@ module Rigor
99
88
  files.any? { |path| file_mentions_rigor?(path) } ? :wired : :unwired
100
89
  end
101
90
 
102
- # MCP-client signal — `:wired` (a committed project MCP config names
103
- # `rigor`), `:unwired` (one is present but does not), or `:none`.
104
- # Only the project-scoped configs are detectable (`.mcp.json`,
105
- # `.cursor/mcp.json`); user-level client configs live in $HOME, so
106
- # mcp-setup is otherwise a catalogue-only destination.
91
+ # MCP-client signal — `:wired` (a committed project MCP config names `rigor`), `:unwired` (one is present but does
92
+ # not), or `:none`. Only the project-scoped configs are detectable (`.mcp.json`, `.cursor/mcp.json`); user-level
93
+ # client configs live in $HOME, so mcp-setup is otherwise a catalogue-only destination.
107
94
  def mcp_state
108
95
  files = [".mcp.json", File.join(".cursor", "mcp.json")]
109
96
  .map { |rel| File.join(@root, rel) }
@@ -127,20 +114,16 @@ module Rigor
127
114
  end
128
115
  end
129
116
 
130
- # Builds the `rigor skill describe` report (ADR-73): a {ProjectStateProbe}
131
- # snapshot, a recommended next skill, and the live catalogue of bundled
132
- # skills with their current frontmatter descriptions. Extracted from
133
- # {SkillCommand} so the command stays a thin dispatcher and this — the
134
- # "live brain" — owns the routing.
117
+ # Builds the `rigor skill describe` report (ADR-73): a {ProjectStateProbe} snapshot, a recommended next skill, and
118
+ # the live catalogue of bundled skills with their current frontmatter descriptions. Extracted from {SkillCommand} so
119
+ # the command stays a thin dispatcher and this — the "live brain" — owns the routing.
135
120
  class SkillDescribe
136
- # The entry-point SKILL itself — excluded from the catalogue because
137
- # it is the skill being run, not a destination.
121
+ # The entry-point SKILL itself — excluded from the catalogue because it is the skill being run, not a destination.
138
122
  ENTRY_POINT_SKILL = "rigor-next-steps"
139
123
 
140
- # Adoption-journey order for the catalogue and the order the
141
- # recommendation decision tree walks. `rigor-ask` sits last: it is
142
- # the journey-agnostic "answer a question about Rigor" companion the
143
- # agent can offer at any point, never a presence-recommended step.
124
+ # Adoption-journey order for the catalogue and the order the recommendation decision tree walks. `rigor-ask` sits
125
+ # last: it is the journey-agnostic "answer a question about Rigor" companion the agent can offer at any point,
126
+ # never a presence-recommended step.
144
127
  CATALOG_ORDER = %w[
145
128
  rigor-project-init
146
129
  rigor-rbs-setup
@@ -180,17 +163,16 @@ module Rigor
180
163
 
181
164
  private
182
165
 
183
- # The skills offered as "what to do next", in adoption-journey order.
184
- # The entry-point skill is excluded, and unknown skills sort after
185
- # the known journey, alphabetically.
166
+ # The skills offered as "what to do next", in adoption-journey order. The entry-point skill is excluded, and
167
+ # unknown skills sort after the known journey, alphabetically.
186
168
  def catalog_skills
187
169
  @skills
188
170
  .reject { |skill| skill.fetch(:name) == ENTRY_POINT_SKILL }
189
171
  .sort_by { |skill| [CATALOG_ORDER.index(skill.fetch(:name)) || CATALOG_ORDER.size, skill.fetch(:name)] }
190
172
  end
191
173
 
192
- # The decision tree (ADR-73 WD2). Returns `{ skill:, reason: }` for
193
- # the recommended next step, or nil when no catalogue skill matches.
174
+ # The decision tree (ADR-73 WD2). Returns `{ skill:, reason: }` for the recommended next step, or nil when no
175
+ # catalogue skill matches.
194
176
  def recommend(state, catalog)
195
177
  name, reason = recommended_name_and_reason(state)
196
178
  skill = catalog.find { |candidate| candidate.fetch(:name) == name }
@@ -208,14 +190,11 @@ module Rigor
208
190
  ["rigor-rbs-setup", "your gems ship no community RBS yet — install it so Rigor stops typing them as Dynamic."]
209
191
  elsif state.fetch(:ci) != :wired
210
192
  ["rigor-ci-setup", "Rigor is configured but not wired into CI — lock in the regression guard."]
211
- # A present baseline is deliberately NOT a recommendation trigger.
212
- # A baseline is a healthy, finished onboarding state, not a problem to
213
- # work off; pushing every baselined project to "reduce it" turns a
214
- # working build into a chore and tempts scattering `# rigor:disable`
215
- # through the code to make a number go down — means over ends.
216
- # `rigor-baseline-reduce` stays in the catalogue for the intermediate
217
- # user who *chooses* to invest in it; the headline routes to genuinely
218
- # additive steps instead.
193
+ # A present baseline is deliberately NOT a recommendation trigger. A baseline is a healthy, finished onboarding
194
+ # state, not a problem to work off; pushing every baselined project to "reduce it" turns a working build into a
195
+ # chore and tempts scattering `# rigor:disable` through the code to make a number go down — means over ends.
196
+ # `rigor-baseline-reduce` stays in the catalogue for the intermediate user who *chooses* to invest in it; the
197
+ # headline routes to genuinely additive steps instead.
219
198
  elsif state.fetch(:editor) == :unwired
220
199
  ["rigor-editor-setup",
221
200
  "you have an editor config but no Rigor LSP — wire `rigor lsp` for live diagnostics and hover types."]
@@ -314,10 +293,8 @@ module Rigor
314
293
  PROMPT
315
294
  end
316
295
 
317
- # The one-line essence of a skill's frontmatter `description`, read
318
- # live from the SKILL.md so the catalogue can never go stale relative
319
- # to the shipped skill. Drops the `Triggers:` / `NOT for` tail and
320
- # caps the length.
296
+ # The one-line essence of a skill's frontmatter `description`, read live from the SKILL.md so the catalogue can
297
+ # never go stale relative to the shipped skill. Drops the `Triggers:` / `NOT for` tail and caps the length.
321
298
  def catalog_blurb(path)
322
299
  text = frontmatter(path).fetch("description", "").to_s.strip.tr("\n", " ").squeeze(" ")
323
300
  head = text.split(/\s*Triggers?:/, 2).first.to_s.strip
@@ -331,9 +308,8 @@ module Rigor
331
308
  "#{(text[0, limit] || '').rstrip}…"
332
309
  end
333
310
 
334
- # Parse a SKILL.md's leading `---`-delimited YAML frontmatter. Returns
335
- # {} on any missing or malformed block so the catalogue degrades to a
336
- # name-only entry rather than raising.
311
+ # Parse a SKILL.md's leading `---`-delimited YAML frontmatter. Returns {} on any missing or malformed block so the
312
+ # catalogue degrades to a name-only entry rather than raising.
337
313
  def frontmatter(path)
338
314
  text = File.read(path)
339
315
  return {} unless text.start_with?("---\n")
@@ -15,22 +15,17 @@ require_relative "trace_renderer"
15
15
 
16
16
  module Rigor
17
17
  class CLI
18
- # Executes the `rigor trace` command: re-runs the inference engine
19
- # over one file under {Rigor::Inference::FlowTracer} and replays the
20
- # recorded event streamscope binds, union formation, method
21
- # dispatch, and (with `--verbose`) every expression enter/result
22
- # as a step-through terminal animation. A teaching probe: it shows
23
- # HOW Rigor arrives at a type, where `rigor type-of` shows only the
24
- # answer.
18
+ # Executes the `rigor trace` command: re-runs the inference engine over one file under
19
+ # {Rigor::Inference::FlowTracer} and replays the recorded event stream — scope binds, union formation, method
20
+ # dispatch, and (with `--verbose`) every expression enter/result as a step-through terminal animation. A teaching
21
+ # probe: it shows HOW Rigor arrives at a type, where `rigor type-of` shows only the answer.
25
22
  #
26
- # The tracer is observational; this command never changes what
27
- # `rigor check` would infer for the same file.
23
+ # The tracer is observational; this command never changes what `rigor check` would infer for the same file.
28
24
  class TraceCommand < Command
29
25
  USAGE = "Usage: rigor trace [options] FILE"
30
26
 
31
- # Default frame kinds: the three teachable moments. :enter/:result
32
- # add one frame per literal and drown the signal; `--verbose`
33
- # opts into them.
27
+ # Default frame kinds: the three teachable moments. :enter/:result add one frame per literal and drown the signal;
28
+ # `--verbose` opts into them.
34
29
  DEFAULT_KINDS = %i[bind union dispatch].freeze
35
30
  VERBOSE_KINDS = (%i[enter result] + DEFAULT_KINDS).freeze
36
31
 
@@ -95,10 +90,8 @@ module Rigor
95
90
  0
96
91
  end
97
92
 
98
- # Mirrors the single-file path `rigor type-of` takes: a
99
- # project-aware environment, an empty seed scope, one
100
- # statement-level evaluation of the whole program — but recorded
101
- # under the FlowTracer.
93
+ # Mirrors the single-file path `rigor type-of` takes: a project-aware environment, an empty seed scope, one
94
+ # statement-level evaluation of the whole program — but recorded under the FlowTracer.
102
95
  def record_events(root, file, configuration)
103
96
  environment = Environment.for_project(
104
97
  libraries: configuration.libraries,
@@ -4,24 +4,19 @@ require_relative "prism_colorizer"
4
4
 
5
5
  module Rigor
6
6
  class CLI
7
- # Replays a `Rigor::Inference::FlowTracer` event stream as a terminal
8
- # animation for `rigor trace`. Each frame draws a box-drawn screen:
9
- # the source panel (syntax-coloured, current node range underlined)
10
- # side-by-side with the scope panel (locals accumulated from :bind
11
- # events), and an event panel describing the current step and the
12
- # expression stack.
7
+ # Replays a `Rigor::Inference::FlowTracer` event stream as a terminal animation for `rigor trace`. Each frame draws
8
+ # a box-drawn screen: the source panel (syntax-coloured, current node range underlined) side-by-side with the scope
9
+ # panel (locals accumulated from :bind events), and an event panel describing the current step and the expression
10
+ # stack.
13
11
  #
14
- # The frame is fitted to the measured terminal: the source panel
15
- # scrolls vertically (a window centred on the line under evaluation)
16
- # instead of overflowing the screen height, over-long rows are
17
- # clipped with an ellipsis instead of wrapping, and the event panel
18
- # keeps a minimum height of {EVENT_PANE_MIN} rows so the narration
19
- # never collapses to a sliver.
12
+ # The frame is fitted to the measured terminal: the source panel scrolls vertically (a window centred on the line
13
+ # under evaluation) instead of overflowing the screen height, over-long rows are clipped with an ellipsis instead of
14
+ # wrapping, and the event panel keeps a minimum height of {EVENT_PANE_MIN} rows so the narration never collapses to
15
+ # a sliver.
20
16
  #
21
- # Pure ANSI + `io/console` (both stdlib) per ADR-0's zero-runtime-
22
- # dependency policy. When the output is not a TTY the frames are
23
- # printed sequentially without cursor control against a default
24
- # 80×24 layout, which keeps the renderer deterministic under test.
17
+ # Pure ANSI + `io/console` (both stdlib) per ADR-0's zero-runtime-dependency policy. When the output is not a TTY
18
+ # the frames are printed sequentially without cursor control against a default 80×24 layout, which keeps the
19
+ # renderer deterministic under test.
25
20
  class TraceRenderer
26
21
  RESET = "\e[0m"
27
22
  DIM = "\e[90m"
@@ -68,9 +63,8 @@ module Rigor
68
63
 
69
64
  private
70
65
 
71
- # `IO.console` reflects the controlling terminal even when stdout
72
- # is, say, a StringIO under test which is why the measured size
73
- # is only used for interactive replays.
66
+ # `IO.console` reflects the controlling terminal even when stdout is, say, a StringIO under test — which is why
67
+ # the measured size is only used for interactive replays.
74
68
  def terminal_size
75
69
  require "io/console"
76
70
  size = IO.console&.winsize
@@ -81,9 +75,8 @@ module Rigor
81
75
  DEFAULT_SIZE
82
76
  end
83
77
 
84
- # Replays :bind events into a per-frame snapshot of the locals
85
- # panel, so frame N shows exactly the bindings visible after the
86
- # first N events.
78
+ # Replays :bind events into a per-frame snapshot of the locals panel, so frame N shows exactly the bindings
79
+ # visible after the first N events.
87
80
  def accumulate_locals(events)
88
81
  locals = {}
89
82
  events.map do |event|
@@ -109,34 +102,31 @@ module Rigor
109
102
  @out.puts(bottom_border(left_width + right_width + 1))
110
103
  end
111
104
 
112
- # Screen budget: borders (3 rows) + event pane + the key-hint row
113
- # when stepping interactively; whatever remains belongs to the
114
- # source/scope body, floored at {BODY_HEIGHT_MIN}.
105
+ # Screen budget: borders (3 rows) + event pane + the key-hint row when stepping interactively; whatever remains
106
+ # belongs to the source/scope body, floored at {BODY_HEIGHT_MIN}.
115
107
  def body_height_for(event_rows)
116
108
  chrome = 3 + event_rows + (@interactive ? 1 : 0)
117
109
  [@rows - chrome, BODY_HEIGHT_MIN].max
118
110
  end
119
111
 
120
- # The event pane keeps at least {EVENT_PANE_MIN} rows (padding with
121
- # blanks) so the bottom of the frame is visually stable across
122
- # event kinds; over-long lines are clipped to the frame width.
112
+ # The event pane keeps at least {EVENT_PANE_MIN} rows (padding with blanks) so the bottom of the frame is visually
113
+ # stable across event kinds; over-long lines are clipped to the frame width.
123
114
  def event_pane_lines(event, max_width)
124
115
  lines = [describe_event(event), stack_line(event)].compact
125
116
  lines << "" while lines.size < EVENT_PANE_MIN
126
117
  lines.map { |line| clip(line, max_width) }
127
118
  end
128
119
 
129
- # Source rows win the width contest; the scope column keeps its
130
- # minimum and absorbs whatever the source does not need.
120
+ # Source rows win the width contest; the scope column keeps its minimum and absorbs whatever the source does not
121
+ # need.
131
122
  def column_widths(source_rows, inner)
132
123
  left_need = (source_rows.map { |raw, _| raw.length }.max || 0) + 1
133
124
  left_width = left_need.clamp(24, [inner - SCOPE_WIDTH_MIN, 24].max)
134
125
  [left_width, [inner - left_width, SCOPE_WIDTH_MIN].max]
135
126
  end
136
127
 
137
- # Vertical scroll: when the panel is taller than the window, keep
138
- # a slice centred on the row under evaluation (the `▶` row, which
139
- # the marker row directly follows).
128
+ # Vertical scroll: when the panel is taller than the window, keep a slice centred on the row under evaluation (the
129
+ # `▶` row, which the marker row directly follows).
140
130
  def scroll_window(rows, height)
141
131
  return rows if rows.size <= height
142
132
 
@@ -149,15 +139,14 @@ module Rigor
149
139
  rows.map do |raw, painted|
150
140
  next [raw, painted] if raw.length <= width
151
141
 
152
- # Re-paint from the clipped raw text — clipping the painted
153
- # string directly would cut ANSI escapes in half.
142
+ # Re-paint from the clipped raw text — clipping the painted string directly would cut ANSI escapes in half.
154
143
  clipped = clip(raw, width)
155
144
  [clipped, repaint_clipped(clipped)]
156
145
  end
157
146
  end
158
147
 
159
- # A clipped row loses its highlight/marker alignment guarantees, so
160
- # it is repainted with plain syntax colouring (gutter dimmed).
148
+ # A clipped row loses its highlight/marker alignment guarantees, so it is repainted with plain syntax colouring
149
+ # (gutter dimmed).
161
150
  def repaint_clipped(clipped)
162
151
  gutter = clipped[0, 6]
163
152
  rest = clipped[6..] || ""
@@ -175,9 +164,8 @@ module Rigor
175
164
  "#{text[0, [width - 1, 0].max]}…"
176
165
  end
177
166
 
178
- # Each row is `[raw_text, painted_text]` so width math runs on the
179
- # escape-free string. The row under the event's source range gets a
180
- # `▔▔▔` marker row injected after it.
167
+ # Each row is `[raw_text, painted_text]` so width math runs on the escape-free string. The row under the event's
168
+ # source range gets a `▔▔▔` marker row injected after it.
181
169
  def source_panel_rows(event)
182
170
  rows = []
183
171
  location = event.location
@@ -200,9 +188,8 @@ module Rigor
200
188
  [indent + ("▔" * width), indent + BOLD + ("▔" * width) + RESET]
201
189
  end
202
190
 
203
- # Highlights the in-range slice with reverse video; everything else
204
- # gets Prism syntax colouring. The highlight is applied on the raw
205
- # slice (not the colorized string) so byte offsets stay honest.
191
+ # Highlights the in-range slice with reverse video; everything else gets Prism syntax colouring. The highlight is
192
+ # applied on the raw slice (not the colorized string) so byte offsets stay honest.
206
193
  def paint_line(line, location, number)
207
194
  return PrismColorizer.colorize(line) unless location && number == location[:start_line]
208
195
 
@@ -214,9 +201,8 @@ module Rigor
214
201
  PrismColorizer.colorize(after).chomp
215
202
  end
216
203
 
217
- # Prism columns are BYTE columns — split the line with byteslice
218
- # so a multibyte character earlier on the line cannot shift (or
219
- # overrun) the highlight range. Returns `[before, slice, after]`.
204
+ # Prism columns are BYTE columns — split the line with byteslice so a multibyte character earlier on the line
205
+ # cannot shift (or overrun) the highlight range. Returns `[before, slice, after]`.
220
206
  def split_at_range(line, location)
221
207
  from = [location[:start_column], line.bytesize].min
222
208
  to = location[:end_line] == location[:start_line] ? [location[:end_column], line.bytesize].min : line.bytesize
@@ -262,8 +248,7 @@ module Rigor
262
248
  "┌#{pad_label("─ #{@file} ", left)}┬#{pad_label('─ scope ', right)}┐"
263
249
  end
264
250
 
265
- # Pads `label` with `─` to exactly `width` cells (truncating an
266
- # over-long label so the frame never breaks).
251
+ # Pads `label` with `─` to exactly `width` cells (truncating an over-long label so the frame never breaks).
267
252
  def pad_label(label, width)
268
253
  label = "#{label[0, width - 2]} " if label.length > width
269
254
  label + ("─" * [width - label.length, 0].max)
@@ -289,9 +274,8 @@ module Rigor
289
274
  "└#{'─' * width}┘"
290
275
  end
291
276
 
292
- # Single-keystroke stepping via stdlib io/console; falls back to
293
- # line-buffered Enter when raw mode is unavailable (e.g. pipes that
294
- # still claim to be TTYs). Returns false when the user quits.
277
+ # Single-keystroke stepping via stdlib io/console; falls back to line-buffered Enter when raw mode is unavailable
278
+ # (e.g. pipes that still claim to be TTYs). Returns false when the user quits.
295
279
  def next_frame?
296
280
  @out.print("#{DIM} [any key: next · q: quit]#{RESET}")
297
281
  key = read_key
@@ -14,12 +14,10 @@ module Rigor
14
14
  class CLI
15
15
  # ADR-23 — executes `rigor triage`.
16
16
  #
17
- # Runs the same analysis as `rigor check`, then summarises the
18
- # diagnostic stream (rule distribution, per-file hotspots,
19
- # heuristic hints) instead of printing the raw per-line list.
20
- # Read-only and advisory (WD4): never edits config, never
21
- # writes a baseline. Always exits 0 — it is an inspection
22
- # command, not a gate (`rigor check` remains the gate).
17
+ # Runs the same analysis as `rigor check`, then summarises the diagnostic stream (rule distribution, per-file
18
+ # hotspots, heuristic hints) instead of printing the raw per-line list. Read-only and advisory (WD4): never edits
19
+ # config, never writes a baseline. Always exits 0 — it is an inspection command, not a gate (`rigor check` remains
20
+ # the gate).
23
21
  class TriageCommand < Command
24
22
  USAGE = "Usage: rigor triage [options] [paths]"
25
23
  DEFAULT_SECTIONS = %i[distribution selectors hotspots hints].freeze
@@ -70,10 +68,8 @@ module Rigor
70
68
  raise OptionParser::InvalidArgument, "unsupported format: #{options.fetch(:format)}"
71
69
  end
72
70
 
73
- # Sequential, cache-backed, no run-stats: triage only needs
74
- # the diagnostic stream, and sequential keeps the rule
75
- # distribution deterministic (the fork pool's cross-file
76
- # divergence would skew the histogram).
71
+ # Sequential, cache-backed, no run-stats: triage only needs the diagnostic stream, and sequential keeps the rule
72
+ # distribution deterministic (the fork pool's cross-file divergence would skew the histogram).
77
73
  def analyze(configuration)
78
74
  runner = Analysis::Runner.new(
79
75
  configuration: configuration,
@@ -6,8 +6,7 @@ require_relative "../triage"
6
6
 
7
7
  module Rigor
8
8
  class CLI
9
- # ADR-23 — renders a {Rigor::Triage::Report} as the `rigor
10
- # triage` text report or as `--format json`.
9
+ # ADR-23 — renders a {Rigor::Triage::Report} as the `rigor triage` text report or as `--format json`.
11
10
  class TriageRenderer
12
11
  BAR_WIDTH = 24
13
12
  SELECTOR_ROWS = 15 # text-output cap; `--format json` carries the full list