rigortype 0.2.9 → 0.3.1
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.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/data/builtins/ruby_core/array.yml +416 -392
- data/data/builtins/ruby_core/file.yml +42 -42
- data/data/builtins/ruby_core/hash.yml +302 -302
- data/data/builtins/ruby_core/io.yml +191 -191
- data/data/builtins/ruby_core/numeric.yml +321 -366
- data/data/builtins/ruby_core/proc.yml +124 -124
- data/data/builtins/ruby_core/range.yml +21 -21
- data/data/builtins/ruby_core/rational.yml +39 -39
- data/data/builtins/ruby_core/re.yml +65 -65
- data/data/builtins/ruby_core/set.yml +106 -106
- data/data/builtins/ruby_core/struct.yml +14 -14
- data/data/core_overlay/string_scanner.rbs +6 -5
- data/docs/handbook/01-getting-started.md +22 -34
- data/docs/handbook/03-narrowing.md +2 -1
- data/docs/handbook/04-tuples-and-shapes.md +7 -3
- data/docs/handbook/06-classes.md +19 -10
- data/docs/handbook/07-rbs-and-extended.md +76 -101
- data/docs/handbook/08-understanding-errors.md +114 -246
- data/docs/handbook/09-plugins.md +54 -144
- data/docs/handbook/11-sig-gen.md +11 -1
- data/docs/handbook/README.md +5 -3
- data/docs/handbook/appendix-liskov.md +4 -2
- data/docs/handbook/appendix-mypy.md +4 -3
- data/docs/handbook/appendix-phpstan.md +14 -7
- data/docs/handbook/appendix-steep.md +4 -2
- data/docs/handbook/appendix-type-theory.md +3 -1
- data/docs/install.md +14 -6
- data/docs/manual/01-installation.md +98 -4
- data/docs/manual/02-cli-reference.md +81 -12
- data/docs/manual/03-configuration.md +34 -2
- data/docs/manual/04-diagnostics.md +59 -4
- data/docs/manual/06-baseline.md +35 -1
- data/docs/manual/07-plugins.md +4 -4
- data/docs/manual/08-skills.md +6 -1
- data/docs/manual/09-editor-integration.md +7 -6
- data/docs/manual/11-ci.md +51 -0
- data/docs/manual/12-caching.md +65 -0
- data/docs/manual/plugins/rigor-actioncable.md +32 -0
- data/docs/manual/plugins/rigor-devise.md +4 -2
- data/lib/rigor/analysis/check_rules/always_truthy_condition_collector.rb +9 -1
- data/lib/rigor/analysis/check_rules/dead_assignment_collector.rb +5 -3
- data/lib/rigor/analysis/check_rules/duplicate_hash_key_collector.rb +128 -0
- data/lib/rigor/analysis/check_rules/inferred_param_guard.rb +60 -0
- data/lib/rigor/analysis/check_rules/ivar_write_collector.rb +3 -2
- data/lib/rigor/analysis/check_rules/return_in_ensure_collector.rb +117 -0
- data/lib/rigor/analysis/check_rules/rule_ids.rb +125 -0
- data/lib/rigor/analysis/check_rules/rule_walk.rb +2 -1
- data/lib/rigor/analysis/check_rules/self_closedness_scanner.rb +2 -1
- data/lib/rigor/analysis/check_rules/shadowed_rescue_collector.rb +283 -0
- data/lib/rigor/analysis/check_rules/unreachable_clause_collector.rb +10 -1
- data/lib/rigor/analysis/check_rules/void_value_use_collector.rb +112 -0
- data/lib/rigor/analysis/check_rules.rb +581 -98
- data/lib/rigor/analysis/dependency_recorder.rb +93 -9
- data/lib/rigor/analysis/dependency_source_inference/walker.rb +2 -1
- data/lib/rigor/analysis/incremental_session.rb +456 -51
- data/lib/rigor/analysis/path_expansion.rb +42 -0
- data/lib/rigor/analysis/plugin_fact_fingerprint.rb +188 -0
- data/lib/rigor/analysis/rule_catalog.rb +226 -1
- data/lib/rigor/analysis/run_cache_key.rb +78 -0
- data/lib/rigor/analysis/run_cache_probe.rb +72 -0
- data/lib/rigor/analysis/runner/diagnostic_aggregator.rb +181 -28
- data/lib/rigor/analysis/runner/pool_coordinator.rb +77 -15
- data/lib/rigor/analysis/runner/project_pre_passes.rb +95 -49
- data/lib/rigor/analysis/runner/run_snapshots.rb +9 -2
- data/lib/rigor/analysis/runner.rb +344 -68
- data/lib/rigor/analysis/severity_stamp.rb +43 -0
- data/lib/rigor/analysis/worker_session.rb +30 -2
- data/lib/rigor/bleeding_edge.rb +37 -8
- data/lib/rigor/builtins/regex_refinement.rb +36 -1
- data/lib/rigor/cache/descriptor.rb +88 -28
- data/lib/rigor/cache/file_digest.rb +158 -0
- data/lib/rigor/cache/incremental_snapshot.rb +41 -5
- data/lib/rigor/cache/rbs_cache_producer.rb +11 -1
- data/lib/rigor/cache/rbs_descriptor.rb +56 -10
- data/lib/rigor/cache/rbs_environment_marshal_patch.rb +38 -0
- data/lib/rigor/cache/store.rb +132 -27
- data/lib/rigor/ci_detector.rb +90 -0
- data/lib/rigor/cli/annotate_command.rb +15 -9
- data/lib/rigor/cli/baseline_command.rb +4 -1
- data/lib/rigor/cli/check_command.rb +223 -32
- data/lib/rigor/cli/check_invocation.rb +84 -0
- data/lib/rigor/cli/coverage_command.rb +6 -1
- data/lib/rigor/cli/docs_command.rb +0 -30
- data/lib/rigor/cli/doctor_command.rb +169 -31
- data/lib/rigor/cli/lsp_command.rb +5 -0
- data/lib/rigor/cli/mcp_command.rb +5 -0
- data/lib/rigor/cli/plugins_command.rb +36 -6
- data/lib/rigor/cli/plugins_renderer.rb +44 -12
- data/lib/rigor/cli/probe_environment.rb +85 -0
- data/lib/rigor/cli/sig_gen_command.rb +36 -9
- data/lib/rigor/cli/skill_command.rb +22 -33
- data/lib/rigor/cli/skill_deep_probe.rb +172 -0
- data/lib/rigor/cli/skill_describe.rb +75 -9
- data/lib/rigor/cli/trace_command.rb +5 -6
- data/lib/rigor/cli/type_of_command.rb +9 -8
- data/lib/rigor/cli/type_scan_command.rb +8 -8
- data/lib/rigor/cli.rb +15 -4
- data/lib/rigor/config_audit.rb +48 -2
- data/lib/rigor/configuration/severity_profile.rb +31 -0
- data/lib/rigor/configuration.rb +156 -8
- data/lib/rigor/environment/default_libraries.rb +36 -0
- data/lib/rigor/environment/rbs_loader.rb +164 -5
- data/lib/rigor/environment.rb +29 -28
- data/lib/rigor/inference/budget_trace.rb +77 -2
- data/lib/rigor/inference/closure_escape_analyzer.rb +14 -1
- data/lib/rigor/inference/def_handle.rb +23 -0
- data/lib/rigor/inference/def_node_resolver.rb +90 -0
- data/lib/rigor/inference/def_return_typer.rb +2 -1
- data/lib/rigor/inference/expression_typer.rb +361 -113
- data/lib/rigor/inference/method_dispatcher/data_folding.rb +30 -5
- data/lib/rigor/inference/method_dispatcher/kernel_dispatch.rb +200 -0
- data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +22 -16
- data/lib/rigor/inference/method_dispatcher/member_shape_projection.rb +33 -0
- data/lib/rigor/inference/method_dispatcher/overload_selector.rb +17 -1
- data/lib/rigor/inference/method_dispatcher/rbs_dispatch.rb +42 -9
- data/lib/rigor/inference/method_dispatcher/receiver_affinity.rb +5 -0
- data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +28 -35
- data/lib/rigor/inference/method_dispatcher/struct_folding.rb +40 -8
- data/lib/rigor/inference/method_dispatcher.rb +57 -1
- data/lib/rigor/inference/mutation_widening.rb +42 -8
- data/lib/rigor/inference/narrowing.rb +127 -17
- data/lib/rigor/inference/parameter_inference_collector.rb +15 -0
- data/lib/rigor/inference/project_patched_scanner.rb +2 -1
- data/lib/rigor/inference/rbs_type_translator.rb +8 -1
- data/lib/rigor/inference/scope_indexer.rb +451 -36
- data/lib/rigor/inference/statement_evaluator.rb +64 -15
- data/lib/rigor/inference/struct_fold_safety.rb +48 -11
- data/lib/rigor/inference/synthetic_method_scanner.rb +15 -14
- data/lib/rigor/inference/void_origin.rb +25 -0
- data/lib/rigor/inference/void_tail_summary.rb +220 -0
- data/lib/rigor/language_server/buffer_resolution.rb +6 -3
- data/lib/rigor/language_server/buffer_table.rb +46 -6
- data/lib/rigor/language_server/completion_provider.rb +2 -1
- data/lib/rigor/language_server/diagnostic_publisher.rb +4 -0
- data/lib/rigor/language_server/document_symbol_provider.rb +2 -1
- data/lib/rigor/language_server/folding_range_provider.rb +2 -1
- data/lib/rigor/language_server/incremental_sync.rb +159 -0
- data/lib/rigor/language_server/selection_range_provider.rb +2 -1
- data/lib/rigor/language_server/server.rb +19 -9
- data/lib/rigor/language_server/signature_help_provider.rb +2 -1
- data/lib/rigor/language_server.rb +1 -0
- data/lib/rigor/plugin/base.rb +38 -24
- data/lib/rigor/plugin/inflector.rb +12 -3
- data/lib/rigor/plugin/io_boundary.rb +5 -1
- data/lib/rigor/plugin/isolation.rb +81 -11
- data/lib/rigor/plugin/load_error.rb +10 -1
- data/lib/rigor/plugin/loader.rb +96 -14
- data/lib/rigor/plugin/node_rule_walk.rb +5 -3
- data/lib/rigor/plugin/registry.rb +32 -23
- data/lib/rigor/plugin.rb +26 -0
- data/lib/rigor/protection/mutator.rb +3 -2
- data/lib/rigor/reflection.rb +64 -0
- data/lib/rigor/runtime/jit.rb +128 -0
- data/lib/rigor/scope/discovery_index.rb +12 -2
- data/lib/rigor/scope.rb +122 -28
- data/lib/rigor/sig_gen/classification.rb +5 -1
- data/lib/rigor/sig_gen/generator.rb +38 -6
- data/lib/rigor/sig_gen/observation_collector.rb +4 -3
- data/lib/rigor/sig_gen/rbs_validity.rb +44 -0
- data/lib/rigor/sig_gen/renderer.rb +10 -0
- data/lib/rigor/sig_gen/write_result.rb +9 -4
- data/lib/rigor/sig_gen/writer.rb +207 -49
- data/lib/rigor/source/node_children.rb +116 -0
- data/lib/rigor/source/node_locator.rb +3 -1
- data/lib/rigor/source/node_walker.rb +4 -2
- data/lib/rigor/source.rb +1 -0
- data/lib/rigor/type/difference.rb +28 -24
- data/lib/rigor/type/hash_shape.rb +34 -10
- data/lib/rigor/version.rb +1 -1
- data/lib/rigortype.rb +24 -0
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable/channel_discoverer.rb +4 -2
- data/plugins/rigor-actioncable/lib/rigor/plugin/actioncable.rb +51 -1
- data/plugins/rigor-actionmailer/lib/rigor/plugin/actionmailer/mailer_discoverer.rb +6 -4
- data/plugins/rigor-actionpack/lib/rigor/plugin/actionpack/controller_discoverer.rb +5 -3
- data/plugins/rigor-activejob/lib/rigor/plugin/activejob/job_discoverer.rb +4 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/analyzer.rb +3 -1
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/model_discoverer.rb +4 -2
- data/plugins/rigor-activerecord/lib/rigor/plugin/activerecord/schema_parser.rb +4 -2
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/analyzer.rb +3 -1
- data/plugins/rigor-activestorage/lib/rigor/plugin/activestorage/attachment_discoverer.rb +4 -2
- data/plugins/rigor-dry-types/lib/rigor/plugin/dry_types.rb +38 -6
- data/plugins/rigor-factorybot/lib/rigor/plugin/factorybot/factory_discoverer.rb +3 -1
- data/plugins/rigor-hanami/lib/rigor/plugin/hanami/action_checker.rb +3 -1
- data/plugins/rigor-mangrove/lib/rigor/plugin/mangrove.rb +10 -0
- data/plugins/rigor-minitest/lib/rigor/plugin/minitest.rb +11 -0
- data/plugins/rigor-pundit/lib/rigor/plugin/pundit/policy_discoverer.rb +3 -1
- data/plugins/rigor-rails/lib/rigor-rails.rb +11 -12
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/grape_api_discoverer.rb +3 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/helper_discoverer.rb +3 -1
- data/plugins/rigor-rails-routes/lib/rigor/plugin/rails_routes/routes_parser.rb +7 -5
- data/plugins/rigor-rbs-inline/lib/rigor/plugin/rbs_inline.rb +83 -9
- data/plugins/rigor-rbs-inline/lib/rigor-rbs-inline.rb +9 -5
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/let_scope_index.rb +3 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec/scope_walker.rb +3 -1
- data/plugins/rigor-rspec/lib/rigor/plugin/rspec.rb +11 -0
- data/plugins/rigor-rspec-rails/lib/rigor/plugin/rspec_rails/http_status_codes.rb +4 -1
- data/plugins/rigor-sidekiq/lib/rigor/plugin/sidekiq/worker_discoverer.rb +4 -2
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet/catalog_walker.rb +3 -1
- data/plugins/rigor-sorbet/lib/rigor/plugin/sorbet.rb +74 -14
- data/sig/prism_node_children.rbs +9 -0
- data/sig/rigor/cache.rbs +6 -0
- data/sig/rigor/inference/void_origin.rbs +18 -0
- data/sig/rigor/inference.rbs +7 -0
- data/sig/rigor/plugin/base.rbs +6 -6
- data/sig/rigor/reflection.rbs +1 -0
- data/sig/rigor/scope.rbs +12 -1
- data/skills/rigor-ci-setup/SKILL.md +10 -0
- data/skills/rigor-editor-setup/SKILL.md +11 -0
- data/skills/rigor-next-steps/SKILL.md +23 -2
- data/skills/rigor-plugin-author/SKILL.md +3 -3
- data/skills/rigor-plugin-author/references/02-walker-and-types.md +2 -2
- data/skills/rigor-plugin-review/SKILL.md +2 -2
- data/skills/rigor-plugin-review/references/01-best-practices-checklist.md +5 -4
- data/skills/rigor-project-init/SKILL.md +29 -3
- data/skills/rigor-project-init/references/01-detect.md +1 -1
- data/skills/rigor-project-init/references/02-configure.md +39 -5
- data/skills/rigor-project-init/references/03-baseline-and-bugs.md +20 -0
- data/skills/rigor-project-init/references/05-jit-performance.md +125 -0
- metadata +48 -26
- data/lib/rigor/cli/ci_detector.rb +0 -89
|
@@ -42,21 +42,43 @@ module Rigor
|
|
|
42
42
|
paths = @argv.empty? ? configuration.paths : @argv
|
|
43
43
|
|
|
44
44
|
observations = collect_observations(configuration, options)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
generator = SigGen::Generator.new(configuration: configuration, paths: paths,
|
|
46
|
+
observations: observations,
|
|
47
|
+
include_private: options.fetch(:include_private))
|
|
48
|
+
candidates = generator.run
|
|
48
49
|
mode = options.fetch(:mode).to_sym
|
|
49
50
|
|
|
50
|
-
if mode == :write
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
status = if mode == :write
|
|
52
|
+
dispatch_write(candidates, configuration, options)
|
|
53
|
+
else
|
|
54
|
+
dispatch_print_or_diff(candidates, mode, options)
|
|
55
|
+
0
|
|
56
|
+
end
|
|
57
|
+
report_unrenderable(generator.unrenderable)
|
|
58
|
+
status
|
|
56
59
|
end
|
|
57
60
|
|
|
58
61
|
private
|
|
59
62
|
|
|
63
|
+
# A method whose rendered RBS does not parse is a Rigor rendering defect, not a fact about the user's
|
|
64
|
+
# code — the generator skipped it (so the rest of the signatures are still usable and still valid), but
|
|
65
|
+
# staying silent would leave the user with a quietly incomplete `sig/` and us with an unreported bug.
|
|
66
|
+
# Reported on stderr so it never contaminates `--print` output being piped into a file.
|
|
67
|
+
def report_unrenderable(unrenderable)
|
|
68
|
+
return if unrenderable.empty?
|
|
69
|
+
|
|
70
|
+
@err.puts(
|
|
71
|
+
"rigor sig-gen: skipped #{unrenderable.size} method(s) whose generated RBS does not parse. " \
|
|
72
|
+
"This is a bug in Rigor's RBS rendering, not in your code — please report it. " \
|
|
73
|
+
"The remaining signatures are unaffected."
|
|
74
|
+
)
|
|
75
|
+
unrenderable.each do |method|
|
|
76
|
+
@err.puts(" #{method.path}: #{method.class_name}##{method.method_name}")
|
|
77
|
+
@err.puts(" rendered: #{method.rbs}")
|
|
78
|
+
@err.puts(" #{method.error}")
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
60
82
|
def dispatch_print_or_diff(candidates, mode, options)
|
|
61
83
|
SigGen::Renderer.new(out: @out).render(
|
|
62
84
|
candidates: candidates,
|
|
@@ -66,6 +88,7 @@ module Rigor
|
|
|
66
88
|
)
|
|
67
89
|
end
|
|
68
90
|
|
|
91
|
+
# @return [Integer] exit status — non-zero when a file the user asked to write could not be written.
|
|
69
92
|
def dispatch_write(candidates, configuration, options)
|
|
70
93
|
layout_index = SigGen::LayoutIndex.new(signature_paths: configuration.signature_paths)
|
|
71
94
|
path_mapper = SigGen::PathMapper.new(configuration: configuration, layout_index: layout_index)
|
|
@@ -74,6 +97,10 @@ module Rigor
|
|
|
74
97
|
results = writer.write_all(candidates)
|
|
75
98
|
|
|
76
99
|
SigGen::Renderer.new(out: @out).render_write(results: results, format: options.fetch(:format))
|
|
100
|
+
# An assembled file that does not parse is refused rather than written. The user asked for a write and
|
|
101
|
+
# did not get one, so the command must not report success — a green `sig-gen --write` in CI would
|
|
102
|
+
# otherwise mean nothing.
|
|
103
|
+
results.any? { |result| result.action == :skipped_invalid_rbs } ? 1 : 0
|
|
77
104
|
end
|
|
78
105
|
|
|
79
106
|
# Slice 3 — collect call-site argument observations when `--params=observed` is set. When `--observe=PATH` is not
|
|
@@ -36,10 +36,15 @@ module Rigor
|
|
|
36
36
|
# version-coupled guidance is frozen into
|
|
37
37
|
# the SKILL. Also spelled `describe`, and
|
|
38
38
|
# surfaced top-level as `rigor describe`.
|
|
39
|
+
# - `rigor skill describe --deep`
|
|
40
|
+
# — the same report, except it runs `rigor
|
|
41
|
+
# check` first and lets the result pick the
|
|
42
|
+
# headline. Opt-in because that is slow and
|
|
43
|
+
# writes the cache; the un-flagged form keeps
|
|
44
|
+
# WD2's presence-only, side-effect-free
|
|
45
|
+
# contract exactly (issue #148).
|
|
39
46
|
#
|
|
40
|
-
#
|
|
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`.
|
|
47
|
+
# `describe` is a no-argument action, not a name-slot verb, so it stays first-class alongside `--describe`.
|
|
43
48
|
class SkillCommand < Command
|
|
44
49
|
USAGE = <<~USAGE
|
|
45
50
|
Usage: rigor skill [<name>] [--full <name>] [--path <name>] [--list] [--describe]
|
|
@@ -53,6 +58,10 @@ module Rigor
|
|
|
53
58
|
rigor skill --path <name> Print the absolute path of the SKILL.md file for <name>
|
|
54
59
|
rigor skill --list List bundled skills (name + absolute path)
|
|
55
60
|
rigor skill --describe Report project state + recommend the next skill to run
|
|
61
|
+
(presence-only probe — never runs `rigor check`)
|
|
62
|
+
rigor skill describe --deep
|
|
63
|
+
Same, but run `rigor check` first and route the
|
|
64
|
+
recommendation on its result (slow; writes the cache)
|
|
56
65
|
|
|
57
66
|
Examples:
|
|
58
67
|
rigor skill
|
|
@@ -60,30 +69,15 @@ module Rigor
|
|
|
60
69
|
rigor skill --full rigor-baseline-reduce
|
|
61
70
|
rigor skill --path rigor-baseline-reduce
|
|
62
71
|
rigor skill --describe (also: rigor describe)
|
|
63
|
-
|
|
64
|
-
Deprecated (removed in v0.3.0) — use the forms above:
|
|
65
|
-
rigor skill list -> rigor skill --list
|
|
66
|
-
rigor skill print <name> -> rigor skill <name>
|
|
67
|
-
rigor skill path <name> -> rigor skill --path <name>
|
|
72
|
+
rigor skill describe --deep (also: rigor describe --deep)
|
|
68
73
|
USAGE
|
|
69
74
|
|
|
70
75
|
# The bundled skills live at `<gem_root>/skills/`. From `lib/rigor/cli/skill_command.rb` that is three directories
|
|
71
76
|
# up.
|
|
72
77
|
SKILLS_ROOT = File.expand_path("../../../skills", __dir__)
|
|
73
78
|
|
|
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.
|
|
76
|
-
LEGACY_VERB_REMOVAL = "v0.3.0"
|
|
77
|
-
LEGACY_VERBS = {
|
|
78
|
-
"list" => { old: "list", advice: "--list", flag: "--list" },
|
|
79
|
-
"print" => { old: "print <name>", advice: "<name>", flag: "--print" },
|
|
80
|
-
"path" => { old: "path <name>", advice: "--path <name>", flag: "--path" }
|
|
81
|
-
}.freeze
|
|
82
|
-
|
|
83
79
|
# @return [Integer] CLI exit status.
|
|
84
80
|
def run
|
|
85
|
-
rewrite_legacy_verb!
|
|
86
|
-
|
|
87
81
|
case @argv.first
|
|
88
82
|
when nil
|
|
89
83
|
run_list
|
|
@@ -112,19 +106,6 @@ module Rigor
|
|
|
112
106
|
|
|
113
107
|
private
|
|
114
108
|
|
|
115
|
-
# Translate a deprecated verb spelling into its flag form, warning once on stderr, so the dispatch above only
|
|
116
|
-
# handles canonical forms.
|
|
117
|
-
def rewrite_legacy_verb!
|
|
118
|
-
spec = LEGACY_VERBS[@argv.first]
|
|
119
|
-
return unless spec
|
|
120
|
-
|
|
121
|
-
@err.puts(
|
|
122
|
-
"rigor skill: `#{spec.fetch(:old)}` is deprecated and will be removed in " \
|
|
123
|
-
"#{LEGACY_VERB_REMOVAL}; use `rigor skill #{spec.fetch(:advice)}` instead."
|
|
124
|
-
)
|
|
125
|
-
@argv[0] = spec.fetch(:flag)
|
|
126
|
-
end
|
|
127
|
-
|
|
128
109
|
def run_list
|
|
129
110
|
skills = discover_skills
|
|
130
111
|
if skills.empty?
|
|
@@ -190,10 +171,18 @@ module Rigor
|
|
|
190
171
|
# probes the current project's state with cheap presence checks (it never runs `rigor check`), recommends the next
|
|
191
172
|
# skill to run, and prints every bundled skill's current frontmatter description, so the `rigor-next-steps` SKILL
|
|
192
173
|
# can route without copying any version-coupled guidance into itself.
|
|
174
|
+
#
|
|
175
|
+
# `--deep` is the one opt-in that breaks the "never runs `rigor check`" guarantee — and only for the invocation
|
|
176
|
+
# that asks for it (issue #148 / ADR-73 § "Field-trial follow-ups"). It runs a real check and lets the result
|
|
177
|
+
# pick the headline; the un-flagged command is unchanged, down to loading no analysis code at all.
|
|
193
178
|
def run_describe
|
|
179
|
+
deep = !@argv.delete("--deep").nil?
|
|
180
|
+
unknown = @argv.first
|
|
181
|
+
return usage_error("unknown option for `describe`: #{unknown}") unless unknown.nil?
|
|
182
|
+
|
|
194
183
|
require_relative "skill_describe"
|
|
195
184
|
|
|
196
|
-
@out.puts(SkillDescribe.new(skills: discover_skills).render)
|
|
185
|
+
@out.puts(SkillDescribe.new(skills: discover_skills, deep: deep).render)
|
|
197
186
|
0
|
|
198
187
|
end
|
|
199
188
|
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "check_invocation"
|
|
4
|
+
|
|
5
|
+
module Rigor
|
|
6
|
+
class CLI
|
|
7
|
+
# `rigor skill describe --deep` — the one part of `describe` that runs an analysis (issue #148; ADR-73
|
|
8
|
+
# § "Field-trial follow-ups", the "headline check-awareness" open decision, shape (b)).
|
|
9
|
+
#
|
|
10
|
+
# **The boundary is this file.** ADR-73 WD2 makes `describe` presence-only and side-effect-free, and that
|
|
11
|
+
# guardrail binds the *un-flagged* command exactly as before: {SkillDescribe} requires this file only when
|
|
12
|
+
# `--deep` was passed, so a default `rigor skill describe` never loads the inference engine, never opens a
|
|
13
|
+
# project source file, and writes nothing. `--deep` is the opt-in that trades that purity away: it runs a real
|
|
14
|
+
# `rigor check` over the configured paths using the configured cache and worker count, so it is as slow as
|
|
15
|
+
# `rigor check` on a cold cache and it WRITES `.rigor/cache` just like `rigor check` does.
|
|
16
|
+
#
|
|
17
|
+
# The routing vocabulary is deliberately the one `describe`'s "For the agent" section already teaches (errors →
|
|
18
|
+
# `rigor-baseline-reduce`, a proven monkey-patch cluster → `rigor-monkeypatch-resolve`, an empty RBS environment
|
|
19
|
+
# or a `configuration-error` → `rigor-doctor`). `--deep` makes the headline compute the call the agent would
|
|
20
|
+
# otherwise make from the same evidence; it does not introduce a second taxonomy.
|
|
21
|
+
class SkillDeepProbe
|
|
22
|
+
# Diagnostics the engine emits when the *setup* is broken rather than the code — the `rigor-doctor` signal.
|
|
23
|
+
CONFIGURATION_ERROR_RULE = "configuration-error"
|
|
24
|
+
|
|
25
|
+
# The share of a run's errors that must be proven monkey-patches before the headline routes to
|
|
26
|
+
# `rigor-monkeypatch-resolve` rather than `rigor-baseline-reduce`. The agent-prompt vocabulary this reuses says
|
|
27
|
+
# "a monkey-patch *cluster*", and a cluster is what makes the workflow the right one: `pre_eval:` clears those
|
|
28
|
+
# sites wholesale, so it is the shortest path only when clearing them materially changes the count. One proven
|
|
29
|
+
# site beside four hundred unrelated errors is a true finding pointing at the wrong workflow — the report says
|
|
30
|
+
# so in the reason line either way, so nothing is lost by leaving the headline on the bigger problem.
|
|
31
|
+
MONKEY_PATCH_CLUSTER_SHARE = Rational(1, 3)
|
|
32
|
+
|
|
33
|
+
# What the deep check concluded.
|
|
34
|
+
#
|
|
35
|
+
# - `status` — `:skipped` (no config to check), `:error` (the check could not run), `:analyzed` (it ran).
|
|
36
|
+
# - `detail` — the human line printed under "## Deep check"; on `:error` it says so outright, because a check
|
|
37
|
+
# that could not run is NOT a clean check and the report must never let it read as one.
|
|
38
|
+
# - `route` / `reason` — the headline override, or nil to leave the presence-only recommendation standing.
|
|
39
|
+
Report = Data.define(:status, :detail, :route, :reason) do
|
|
40
|
+
# @return [Boolean] true when the check did not complete — the caller must then say so rather than let the
|
|
41
|
+
# fallback recommendation read as an all-clear.
|
|
42
|
+
def failed?
|
|
43
|
+
status == :error
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# @param config [String, nil] the config filename the presence probe found, relative to `root`.
|
|
48
|
+
# @param root [String] project root (the analysis itself resolves paths against the process cwd, as
|
|
49
|
+
# `rigor check` does).
|
|
50
|
+
def initialize(config:, root: Dir.pwd)
|
|
51
|
+
@config = config
|
|
52
|
+
@root = root
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# @return [Report] never raises — every failure degrades to a `:error`/`:skipped` report whose `route` is nil,
|
|
56
|
+
# leaving the presence-only headline in charge.
|
|
57
|
+
def run
|
|
58
|
+
if @config.nil?
|
|
59
|
+
return Report.new(
|
|
60
|
+
status: :skipped, route: nil, reason: nil,
|
|
61
|
+
detail: "skipped — this project has no Rigor configuration, so there is nothing to check yet."
|
|
62
|
+
)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
outcome = CheckInvocation.attempt(config_path: File.join(@root, @config))
|
|
66
|
+
return failed(outcome.error) unless outcome.ran?
|
|
67
|
+
|
|
68
|
+
classify(outcome.result)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
# A check that could not run at all. Reported as a failure, not as a clean result, and with the raw error text
|
|
74
|
+
# so the user can act on it — the un-run check is itself a finding, but not one precise enough to route on.
|
|
75
|
+
def failed(error)
|
|
76
|
+
Report.new(
|
|
77
|
+
status: :error, route: nil, reason: nil,
|
|
78
|
+
detail: "the check could NOT run: #{error}. This is not a clean result — the recommendation below " \
|
|
79
|
+
"falls back to the presence-only probe. Run `rigor check` (or `rigor doctor`) to see what failed."
|
|
80
|
+
)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def classify(result)
|
|
84
|
+
sites = monkey_patch_diagnostics(result)
|
|
85
|
+
if broken_environment?(result)
|
|
86
|
+
doctor_report(result)
|
|
87
|
+
elsif monkey_patch_cluster?(result, sites)
|
|
88
|
+
monkeypatch_report(result, sites)
|
|
89
|
+
elsif result.error_count.positive?
|
|
90
|
+
baseline_report(result, sites)
|
|
91
|
+
else
|
|
92
|
+
Report.new(
|
|
93
|
+
status: :analyzed, route: nil, reason: nil,
|
|
94
|
+
detail: "the check ran clean — 0 error diagnostics. The recommendation below is the presence-only one."
|
|
95
|
+
)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# The `rigor-doctor` signal, read exactly as `rigor doctor` itself reads it: an RBS environment that built to
|
|
100
|
+
# zero classes (analysis is hollow, so every other count is meaningless), or a `configuration-error` the run
|
|
101
|
+
# already emitted. Checked first — with a broken setup the remaining diagnostics are not evidence of anything.
|
|
102
|
+
def broken_environment?(result)
|
|
103
|
+
result.stats&.rbs_classes_total&.zero? ||
|
|
104
|
+
result.diagnostics.any? { |diagnostic| diagnostic.rule == CONFIGURATION_ERROR_RULE }
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# ADR-17 / the `project-monkey-patch-known` triage recogniser: `call.undefined-method` sets
|
|
108
|
+
# `project_definition_site` when the project itself defines the called method on the receiver class somewhere
|
|
109
|
+
# in the analysed file set. That is engine-proven evidence, not a spread heuristic, which is why it routes with
|
|
110
|
+
# no count threshold — and why the *unproven* shapes (a bare `call.unresolved-toplevel` count, "framework calls
|
|
111
|
+
# typing as Dynamic") are deliberately not routed on here. Guessing a workflow from a weak signal is worse than
|
|
112
|
+
# leaving the generic recommendation standing.
|
|
113
|
+
def monkey_patch_diagnostics(result)
|
|
114
|
+
result.diagnostics.select(&:project_definition_site)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Whether the proven sites dominate the run enough for `pre_eval:` to be the shortest path. Any proven site is
|
|
118
|
+
# real evidence — `project_definition_site` is engine-proven, not a spread heuristic — but evidence of a *site*
|
|
119
|
+
# is not evidence that clearing it is the next thing to do. Below the share, `baseline_report` still names the
|
|
120
|
+
# sites, so the finding survives even when the headline does not follow it.
|
|
121
|
+
def monkey_patch_cluster?(result, sites)
|
|
122
|
+
return false if sites.empty?
|
|
123
|
+
return true unless result.error_count.positive?
|
|
124
|
+
|
|
125
|
+
Rational(sites.size, result.error_count) >= MONKEY_PATCH_CLUSTER_SHARE
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def doctor_report(result)
|
|
129
|
+
classes = result.stats&.rbs_classes_total
|
|
130
|
+
cause = if classes&.zero?
|
|
131
|
+
"the RBS environment built to 0 classes"
|
|
132
|
+
else
|
|
133
|
+
"the run emitted a `#{CONFIGURATION_ERROR_RULE}` diagnostic"
|
|
134
|
+
end
|
|
135
|
+
Report.new(
|
|
136
|
+
status: :analyzed, route: "rigor-doctor",
|
|
137
|
+
reason: "a `--deep` check found a broken setup (#{cause}) — the analysis is hollow until that is fixed, " \
|
|
138
|
+
"so no other finding is trustworthy yet.",
|
|
139
|
+
detail: "the check found a broken setup — #{cause}."
|
|
140
|
+
)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def monkeypatch_report(result, sites)
|
|
144
|
+
files = sites.filter_map { |d| d.project_definition_site&.sub(/:\d+\z/, "") }.uniq.sort
|
|
145
|
+
Report.new(
|
|
146
|
+
status: :analyzed, route: "rigor-monkeypatch-resolve",
|
|
147
|
+
reason: "a `--deep` check found #{sites.size} call site(s) that resolve to the project's own definitions " \
|
|
148
|
+
"in #{files.first(3).join(', ')} — reopened classes Rigor does not apply cross-file. Listing " \
|
|
149
|
+
"them in `pre_eval:` clears them wholesale.",
|
|
150
|
+
detail: "the check reported #{result.error_count} error diagnostic(s), #{sites.size} of them proven " \
|
|
151
|
+
"project monkey-patches (#{files.size} file(s))."
|
|
152
|
+
)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def baseline_report(result, sites = [])
|
|
156
|
+
aside = if sites.empty?
|
|
157
|
+
""
|
|
158
|
+
else
|
|
159
|
+
" #{sites.size} of them are proven project monkey-patches — too small a share to make `pre_eval:` " \
|
|
160
|
+
"the shortest path, but `rigor-monkeypatch-resolve` clears them if you want them gone first."
|
|
161
|
+
end
|
|
162
|
+
Report.new(
|
|
163
|
+
status: :analyzed, route: "rigor-baseline-reduce",
|
|
164
|
+
reason: "a `--deep` check reported #{result.error_count} error diagnostic(s) — work them down (or record " \
|
|
165
|
+
"them in a baseline) before adding more surface.#{aside}",
|
|
166
|
+
detail: "the check reported #{result.error_count} error diagnostic(s), " \
|
|
167
|
+
"#{sites.empty? ? 'none' : sites.size} of them proven project monkey-patches."
|
|
168
|
+
)
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
@@ -142,27 +142,43 @@ module Rigor
|
|
|
142
142
|
|
|
143
143
|
# @param skills [Array<Hash>] discovered skills, each `{name:, path:}`.
|
|
144
144
|
# @param root [String] project root to probe (defaults to the cwd).
|
|
145
|
-
|
|
145
|
+
# @param deep [Boolean] opt into `--deep`: run a real `rigor check` and let its result pick the headline. Off by
|
|
146
|
+
# default, and off is ADR-73 WD2's contract — see {SkillDeepProbe} for why the whole analysis path lives
|
|
147
|
+
# behind this one flag and in its own file.
|
|
148
|
+
def initialize(skills:, root: Dir.pwd, deep: false)
|
|
146
149
|
@skills = skills
|
|
147
150
|
@root = root
|
|
151
|
+
@deep = deep
|
|
148
152
|
end
|
|
149
153
|
|
|
150
154
|
# @return [String] the full describe report.
|
|
151
155
|
def render
|
|
152
156
|
catalog = catalog_skills
|
|
153
157
|
state = ProjectStateProbe.new(@root).to_h
|
|
154
|
-
|
|
158
|
+
deep = deep_report(state)
|
|
159
|
+
recommendation = recommend(state, catalog, deep)
|
|
155
160
|
[
|
|
156
161
|
title,
|
|
157
162
|
state_section(state),
|
|
163
|
+
deep_section(deep),
|
|
158
164
|
recommendation_section(recommendation),
|
|
159
165
|
catalog_section(catalog),
|
|
160
|
-
agent_prompt(recommendation)
|
|
161
|
-
].join("\n")
|
|
166
|
+
agent_prompt(recommendation, deep)
|
|
167
|
+
].compact.join("\n")
|
|
162
168
|
end
|
|
163
169
|
|
|
164
170
|
private
|
|
165
171
|
|
|
172
|
+
# The `--deep` analysis, or nil when the flag was not given. `require` sits here rather than at the top of the
|
|
173
|
+
# file so the default, presence-only path never even loads the check-invocation plumbing — WD2's "runs no
|
|
174
|
+
# analysis" guarantee is enforced by what gets loaded, not only by what gets called.
|
|
175
|
+
def deep_report(state)
|
|
176
|
+
return nil unless @deep
|
|
177
|
+
|
|
178
|
+
require_relative "skill_deep_probe"
|
|
179
|
+
SkillDeepProbe.new(config: state.fetch(:config), root: @root).run
|
|
180
|
+
end
|
|
181
|
+
|
|
166
182
|
# The skills offered as "what to do next", in adoption-journey order. The entry-point skill is excluded, and
|
|
167
183
|
# unknown skills sort after the known journey, alphabetically.
|
|
168
184
|
def catalog_skills
|
|
@@ -173,8 +189,12 @@ module Rigor
|
|
|
173
189
|
|
|
174
190
|
# The decision tree (ADR-73 WD2). Returns `{ skill:, reason: }` for the recommended next step, or nil when no
|
|
175
191
|
# catalogue skill matches.
|
|
176
|
-
|
|
177
|
-
|
|
192
|
+
#
|
|
193
|
+
# A `--deep` report overrides the presence-only tree only when it actually routed: a check that was skipped,
|
|
194
|
+
# could not run, or ran clean leaves the tree's answer standing untouched. So the un-flagged behaviour is
|
|
195
|
+
# literally the `deep == nil` branch of this one method.
|
|
196
|
+
def recommend(state, catalog, deep = nil)
|
|
197
|
+
name, reason = deep&.route ? [deep.route, deep.reason] : recommended_name_and_reason(state)
|
|
178
198
|
skill = catalog.find { |candidate| candidate.fetch(:name) == name }
|
|
179
199
|
skill.nil? ? nil : { skill: skill, reason: reason }
|
|
180
200
|
end
|
|
@@ -248,6 +268,18 @@ module Rigor
|
|
|
248
268
|
STATE
|
|
249
269
|
end
|
|
250
270
|
|
|
271
|
+
# Printed only under `--deep`, so the un-flagged report is byte-identical to what it has always been.
|
|
272
|
+
def deep_section(deep)
|
|
273
|
+
return nil if deep.nil?
|
|
274
|
+
|
|
275
|
+
<<~DEEP
|
|
276
|
+
## Deep check (--deep)
|
|
277
|
+
A real `rigor check` was run for this recommendation — unlike the probe above it
|
|
278
|
+
is slow and it writes `.rigor/cache`, exactly as `rigor check` does.
|
|
279
|
+
- Result: #{deep.detail}
|
|
280
|
+
DEEP
|
|
281
|
+
end
|
|
282
|
+
|
|
251
283
|
def recommendation_section(recommendation)
|
|
252
284
|
return "## Recommended next step\n- (no bundled skill matched the current state)\n" if recommendation.nil?
|
|
253
285
|
|
|
@@ -267,7 +299,7 @@ module Rigor
|
|
|
267
299
|
"## All skills you can run next\n#{lines.join("\n")}\n"
|
|
268
300
|
end
|
|
269
301
|
|
|
270
|
-
def agent_prompt(recommendation)
|
|
302
|
+
def agent_prompt(recommendation, deep = nil)
|
|
271
303
|
opener =
|
|
272
304
|
if recommendation.nil?
|
|
273
305
|
"Ask the user what they would like to do next"
|
|
@@ -279,6 +311,33 @@ module Rigor
|
|
|
279
311
|
#{opener}, then run `rigor skill <name>` for the chosen skill and
|
|
280
312
|
follow its body top to bottom.
|
|
281
313
|
|
|
314
|
+
#{check_awareness(deep)}
|
|
315
|
+
|
|
316
|
+
Re-run `rigor skill describe` whenever you need the next step — it always
|
|
317
|
+
reflects the project's current state.
|
|
318
|
+
PROMPT
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
# The check-aware routing block. Without `--deep` it teaches the agent to make the call itself from a check it
|
|
322
|
+
# has already run (the 2026-06-20 field-trial fix, unchanged); with `--deep` the headline already made that
|
|
323
|
+
# call, so the block reports what happened instead of re-teaching it. The routing vocabulary is one taxonomy
|
|
324
|
+
# shared by both branches and by {SkillDeepProbe}.
|
|
325
|
+
def check_awareness(deep)
|
|
326
|
+
return presence_only_check_awareness if deep.nil?
|
|
327
|
+
return deep_failure_check_awareness if deep.failed?
|
|
328
|
+
|
|
329
|
+
<<~DEEP.chomp
|
|
330
|
+
The recommendation above already factors in the `--deep` check result, using the
|
|
331
|
+
same routing this command teaches without `--deep`: proven project monkey-patches
|
|
332
|
+
→ rigor-monkeypatch-resolve; `RBS classes available: 0` or a `configuration-error`
|
|
333
|
+
→ rigor-doctor; remaining error diagnostics → rigor-baseline-reduce. It does not
|
|
334
|
+
route on weaker signals (framework calls typing as Dynamic → rigor-plugin-tune is
|
|
335
|
+
still yours to judge from the check output).
|
|
336
|
+
DEEP
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
def presence_only_check_awareness
|
|
340
|
+
<<~PROMPT.chomp
|
|
282
341
|
The recommendation above is from a presence-only probe — it does not run
|
|
283
342
|
`rigor check`. If you have run (or now run) `rigor check`, let its findings
|
|
284
343
|
refine the choice:
|
|
@@ -287,9 +346,16 @@ module Rigor
|
|
|
287
346
|
- framework calls (ActiveRecord, routes, i18n …) typing as Dynamic with no
|
|
288
347
|
matching plugins enabled → rigor-plugin-tune
|
|
289
348
|
- `RBS classes available: 0` or a `configuration-error` diagnostic → rigor-doctor
|
|
349
|
+
(`rigor skill describe --deep` runs that check for you and applies this routing
|
|
350
|
+
to the headline itself — at the cost of a full analysis.)
|
|
351
|
+
PROMPT
|
|
352
|
+
end
|
|
290
353
|
|
|
291
|
-
|
|
292
|
-
|
|
354
|
+
def deep_failure_check_awareness
|
|
355
|
+
<<~PROMPT.chomp
|
|
356
|
+
The `--deep` check did NOT complete, so the recommendation above is the
|
|
357
|
+
presence-only one. Do not treat the project as clean: run `rigor doctor`
|
|
358
|
+
(or `rigor check`) and fix what it reports before trusting any routing.
|
|
293
359
|
PROMPT
|
|
294
360
|
end
|
|
295
361
|
|
|
@@ -12,6 +12,7 @@ require_relative "../inference/flow_tracer"
|
|
|
12
12
|
require_relative "../inference/scope_indexer"
|
|
13
13
|
require_relative "command"
|
|
14
14
|
require_relative "trace_renderer"
|
|
15
|
+
require_relative "probe_environment"
|
|
15
16
|
|
|
16
17
|
module Rigor
|
|
17
18
|
class CLI
|
|
@@ -90,13 +91,11 @@ module Rigor
|
|
|
90
91
|
0
|
|
91
92
|
end
|
|
92
93
|
|
|
93
|
-
# Mirrors the single-file path `rigor type-of` takes:
|
|
94
|
-
#
|
|
94
|
+
# Mirrors the single-file path `rigor type-of` takes: the plugin-aware environment (so a trace replays the
|
|
95
|
+
# same dispatch `rigor check` would, including plugin-synthesized RBS — see {ProbeEnvironment}), an empty
|
|
96
|
+
# seed scope, one statement-level evaluation of the whole program — but recorded under the FlowTracer.
|
|
95
97
|
def record_events(root, file, configuration)
|
|
96
|
-
environment =
|
|
97
|
-
libraries: configuration.libraries,
|
|
98
|
-
signature_paths: configuration.signature_paths
|
|
99
|
-
)
|
|
98
|
+
environment = ProbeEnvironment.build(configuration: configuration, source_files: [file])
|
|
100
99
|
scope = Scope.empty(environment: environment, source_path: file)
|
|
101
100
|
Inference::FlowTracer.record { scope.evaluate(root) }
|
|
102
101
|
end
|
|
@@ -13,6 +13,7 @@ require_relative "../inference/scope_indexer"
|
|
|
13
13
|
require_relative "type_of_renderer"
|
|
14
14
|
require_relative "command"
|
|
15
15
|
require_relative "options"
|
|
16
|
+
require_relative "probe_environment"
|
|
16
17
|
|
|
17
18
|
module Rigor
|
|
18
19
|
class CLI
|
|
@@ -92,14 +93,14 @@ module Rigor
|
|
|
92
93
|
0
|
|
93
94
|
end
|
|
94
95
|
|
|
95
|
-
# Builds
|
|
96
|
-
#
|
|
97
|
-
#
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
)
|
|
96
|
+
# Builds the plugin-aware environment relative to the probed file, so the reported type matches what `rigor
|
|
97
|
+
# check` computes for the same position — including types synthesized from inline RBS annotations by the
|
|
98
|
+
# ADR-93 auto-wired `rigor-rbs-inline` plugin (see {ProbeEnvironment} for the #162 misattribution this
|
|
99
|
+
# closes). The probed file is threaded as the synthesizer's `source_files:`. Project-RBS auto-detection
|
|
100
|
+
# roots at CWD today; future work will walk parent directories to find the enclosing `Gemfile`/`*.gemspec`
|
|
101
|
+
# so probes against files outside the current process's CWD still see the right `sig/` tree.
|
|
102
|
+
def project_environment(file, configuration)
|
|
103
|
+
ProbeEnvironment.build(configuration: configuration, source_files: [file])
|
|
103
104
|
end
|
|
104
105
|
|
|
105
106
|
def file_exists?(file)
|
|
@@ -11,6 +11,7 @@ require_relative "../scope"
|
|
|
11
11
|
require_relative "type_scan_renderer"
|
|
12
12
|
require_relative "type_scan_report"
|
|
13
13
|
require_relative "command"
|
|
14
|
+
require_relative "probe_environment"
|
|
14
15
|
|
|
15
16
|
module Rigor
|
|
16
17
|
class CLI
|
|
@@ -69,20 +70,19 @@ module Rigor
|
|
|
69
70
|
|
|
70
71
|
def scan_paths(paths, options)
|
|
71
72
|
configuration = Configuration.load(options.fetch(:config))
|
|
72
|
-
scope = Scope.empty(environment: project_environment(configuration))
|
|
73
|
+
scope = Scope.empty(environment: project_environment(configuration, paths))
|
|
73
74
|
scanner = Inference::CoverageScanner.new(scope: scope)
|
|
74
75
|
accumulator = ScanAccumulator.new
|
|
75
76
|
paths.each { |path| scan_one(path, scanner, accumulator, configuration) }
|
|
76
77
|
accumulator.to_report(paths, options)
|
|
77
78
|
end
|
|
78
79
|
|
|
79
|
-
# Builds
|
|
80
|
-
# `libraries:` / `signature_paths:` keys when present.
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
)
|
|
80
|
+
# Builds the plugin-aware environment that auto-detects `<cwd>/sig` by default and honours the
|
|
81
|
+
# configuration's `libraries:` / `signature_paths:` keys when present. The scanned `paths` are threaded as
|
|
82
|
+
# the plugin `source_rbs_synthesizer` inputs so coverage reflects the same synthesized RBS `rigor check`
|
|
83
|
+
# sees (see {ProbeEnvironment}).
|
|
84
|
+
def project_environment(configuration, source_files)
|
|
85
|
+
ProbeEnvironment.build(configuration: configuration, source_files: source_files)
|
|
86
86
|
end
|
|
87
87
|
|
|
88
88
|
def scan_one(path, scanner, accumulator, configuration)
|
data/lib/rigor/cli.rb
CHANGED
|
@@ -11,7 +11,7 @@ require_relative "analysis/diagnostic"
|
|
|
11
11
|
require_relative "analysis/result"
|
|
12
12
|
require_relative "cli/options"
|
|
13
13
|
require_relative "cli/diagnostic_formats"
|
|
14
|
-
require_relative "
|
|
14
|
+
require_relative "ci_detector"
|
|
15
15
|
|
|
16
16
|
module Rigor
|
|
17
17
|
# The CLI class is a dispatcher: each `run_*` method delegates to a command-specific class once the command grows
|
|
@@ -20,6 +20,16 @@ module Rigor
|
|
|
20
20
|
class CLI # rubocop:disable Metrics/ClassLength
|
|
21
21
|
EXIT_USAGE = 64
|
|
22
22
|
|
|
23
|
+
# The published location of `schemas/rigor-config.schema.json`, written into the `.rigor.yml` that
|
|
24
|
+
# `rigor init` generates so an editor validates the file as the user types.
|
|
25
|
+
#
|
|
26
|
+
# This MUST equal the schema's own `$id`, and it MUST resolve. Both are gated
|
|
27
|
+
# (`spec/rigor/config_schema_spec.rb`) because neither failure has a symptom: an unreachable schema
|
|
28
|
+
# is indistinguishable from a schema with no complaints, so a stale URL here silently disables
|
|
29
|
+
# validation for every project that ran `rigor init`. It pointed at a nonexistent org until
|
|
30
|
+
# 2026-07-17 and nothing noticed. See `docs/internal-spec/config.md`.
|
|
31
|
+
CONFIG_SCHEMA_URL = "https://github.com/rigortype/rigor/raw/master/schemas/rigor-config.schema.json"
|
|
32
|
+
|
|
23
33
|
HANDLERS = {
|
|
24
34
|
"check" => :run_check,
|
|
25
35
|
"init" => :run_init,
|
|
@@ -138,9 +148,10 @@ module Rigor
|
|
|
138
148
|
# most likely to want to edit.
|
|
139
149
|
def init_template
|
|
140
150
|
<<~YAML
|
|
141
|
-
# yaml-language-server: $schema
|
|
142
|
-
# Rigor configuration.
|
|
143
|
-
#
|
|
151
|
+
# yaml-language-server: $schema=#{CONFIG_SCHEMA_URL}
|
|
152
|
+
# Rigor configuration. The full key reference is
|
|
153
|
+
# https://github.com/rigortype/rigor/blob/master/docs/manual/03-configuration.md
|
|
154
|
+
# (or run `rigor docs configuration`).
|
|
144
155
|
#
|
|
145
156
|
# Keys you may want to edit:
|
|
146
157
|
# - target_ruby: minimum Ruby version your project targets.
|