rigortype 0.2.0 → 0.2.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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +41 -6
  3. data/data/core_overlay/numeric.rbs +33 -0
  4. data/data/core_overlay/pathname.rbs +25 -0
  5. data/data/core_overlay/string_scanner.rbs +28 -0
  6. data/data/gem_overlay/activesupport/core_ext.rbs +473 -0
  7. data/data/vendored_gem_sigs/ast/ast.rbs +130 -0
  8. data/data/vendored_gem_sigs/bcrypt/bcrypt.rbs +47 -0
  9. data/data/vendored_gem_sigs/bundler/bundler.rbs +238 -0
  10. data/data/vendored_gem_sigs/cgi/cgi_extras.rbs +34 -0
  11. data/data/vendored_gem_sigs/did_you_mean/did_you_mean_extras.rbs +34 -0
  12. data/data/vendored_gem_sigs/idn-ruby/idn.rbs +54 -0
  13. data/data/vendored_gem_sigs/mysql2/client.rbs +55 -0
  14. data/data/vendored_gem_sigs/mysql2/error.rbs +5 -0
  15. data/data/vendored_gem_sigs/mysql2/result.rbs +31 -0
  16. data/data/vendored_gem_sigs/mysql2/statement.rbs +5 -0
  17. data/data/vendored_gem_sigs/nokogiri/nokogiri.rbs +2332 -0
  18. data/data/vendored_gem_sigs/nokogiri/nokogiri_html5.rbs +47 -0
  19. data/data/vendored_gem_sigs/pg/pg.rbs +212 -0
  20. data/data/vendored_gem_sigs/prism/prism_supplement.rbs +44 -0
  21. data/data/vendored_gem_sigs/redis/errors.rbs +50 -0
  22. data/data/vendored_gem_sigs/redis/future.rbs +5 -0
  23. data/data/vendored_gem_sigs/redis/redis.rbs +348 -0
  24. data/data/vendored_gem_sigs/redis/redis_extras.rbs +130 -0
  25. data/data/vendored_gem_sigs/rubygems/rubygems_extras.rbs +226 -0
  26. data/lib/rigor/cli/check_command.rb +25 -2
  27. data/lib/rigor/cli/coverage_command.rb +67 -92
  28. data/lib/rigor/cli/coverage_mutation.rb +149 -0
  29. data/lib/rigor/cli/fused_protection_renderer.rb +67 -0
  30. data/lib/rigor/cli/fused_protection_report.rb +76 -0
  31. data/lib/rigor/config_audit.rb +152 -0
  32. data/lib/rigor/configuration.rb +12 -0
  33. data/lib/rigor/environment/rbs_loader.rb +27 -0
  34. data/lib/rigor/environment.rb +49 -1
  35. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +17 -7
  36. data/lib/rigor/inference/statement_evaluator.rb +27 -0
  37. data/lib/rigor/protection/diagnostic_oracle.rb +51 -0
  38. data/lib/rigor/protection/mutation_scanner.rb +98 -38
  39. data/lib/rigor/protection/mutator.rb +21 -0
  40. data/lib/rigor/protection/test_suite_oracle.rb +68 -0
  41. data/lib/rigor/signature_path_audit.rb +92 -0
  42. data/lib/rigor/version.rb +1 -1
  43. metadata +31 -1
@@ -2,8 +2,8 @@
2
2
 
3
3
  require "prism"
4
4
 
5
- require_relative "../analysis/runner"
6
5
  require_relative "mutator"
6
+ require_relative "diagnostic_oracle"
7
7
 
8
8
  module Rigor
9
9
  module Protection
@@ -15,17 +15,16 @@ module Rigor
15
15
  # Mechanism (the ADR-62 warm loop, narrowed to per-file measurement):
16
16
  # generate the type-visible mutations ({Mutator}), keep only those whose
17
17
  # receiver Rigor holds a concrete type for (the type-aware filter — the
18
- # FP-safe meaning-maker; an unresolved receiver is kept), then for each:
19
- # re-analyse the mutated SOURCE against a clean baseline and read whether a
20
- # NEW diagnostic appears. A *killed* mutation is a caught breakage; a
21
- # *survived* one is a breakage Rigor missed an "add a type here" site.
18
+ # FP-safe meaning-maker; an unresolved receiver is kept), then for each ask
19
+ # the **kill oracle** whether the mutant is caught. The oracle is the ADR-69
20
+ # seam: {#scan_file} uses the {DiagnosticOracle} (a *new Rigor diagnostic* =
21
+ # a kill); {#scan_file_fused} additionally consults a {TestSuiteOracle} on the
22
+ # type-survivors (ADR-70 — the dynamic protection axis).
22
23
  #
23
24
  # The expensive builds (RBS environment + the whole-project pre-pass scan)
24
- # are paid ONCE by the caller and threaded in via `environment:` /
25
- # `project_scan:`; each mutant reuses them through
26
- # `Runner.new(prebuilt:)#run_source` (in-memory overlay, no disk write).
27
- # Passing `prebuilt:` disables the run-result cache (whose key digests the
28
- # *disk* file), so a mutant is never served a stale clean hit.
25
+ # are paid ONCE by the caller and threaded into the {DiagnosticOracle}; each
26
+ # mutant reuses them through `Runner.new(prebuilt:)#run_source` (in-memory
27
+ # overlay, no disk write).
29
28
  class MutationScanner
30
29
  # A surviving mutation site — a breakage Rigor did not catch.
31
30
  SurvivingSite = Data.define(:line, :receiver, :method_name, :operator)
@@ -39,18 +38,46 @@ module Rigor
39
38
  def ratio = total.zero? ? 1.0 : killed.to_f / total
40
39
  end
41
40
 
41
+ # ADR-70 — one type-survivor classified by the dynamic (test) axis.
42
+ # `protection` is `:test` (a test caught it) or `:none` (unprotected — the
43
+ # "add a type OR a test here" sites).
44
+ FusedSite = Data.define(:line, :receiver, :method_name, :operator, :protection)
45
+
46
+ # ADR-70 — the per-file fused classification. The gradual short-circuit
47
+ # collapses the conceptual "doubly-protected" bucket into `type_killed`:
48
+ # a mutant the type checker already kills never reaches the suite, because
49
+ # the static net already suffices and re-running the suite to learn a test
50
+ # *would also* catch it is wasted work. So the observed buckets are three.
51
+ FusedFileResult = Data.define(:path, :type_killed, :test_killed, :sites) do
52
+ # The unprotected sites (neither a type nor a test caught the breakage).
53
+ def unprotected = sites.size
54
+ def total = type_killed + test_killed + unprotected
55
+
56
+ # Fused protected ratio — caught by *either* axis.
57
+ def ratio = total.zero? ? 1.0 : (type_killed + test_killed).to_f / total
58
+ end
59
+
42
60
  # @param configuration [Rigor::Configuration]
43
61
  # @param environment [Rigor::Environment] pre-built once by the caller
44
62
  # @param project_scan [Rigor::Analysis::ProjectScan] pre-built once
45
63
  # @param limit [Integer, nil] optional per-file mutation cap (sampled with
46
64
  # `seed`); nil analyses every type-relevant mutation (deterministic).
47
65
  # @param seed [Integer] RNG seed for the optional sample.
48
- def initialize(configuration:, environment:, project_scan:, limit: nil, seed: 1)
49
- @configuration = configuration
66
+ # @param oracle [#baseline, #killed?, nil] the kill oracle (ADR-69 Seam 1);
67
+ # defaults to the {DiagnosticOracle} (the ADR-62/63 behaviour).
68
+ # @param site_selector [:biteable, :all] which sites to mutate (ADR-69
69
+ # Seam 2). `:biteable` (default) keeps only concrete-type sites Rigor can
70
+ # bite; `:all` also mutates Dynamic-receiver dispatch sites — use only
71
+ # with a {TestSuiteOracle} (the fused overlay), never the diagnostic path.
72
+ def initialize(configuration:, environment:, project_scan:, limit: nil, seed: 1, oracle: nil,
73
+ site_selector: :biteable)
50
74
  @environment = environment
51
- @project_scan = project_scan
52
75
  @limit = limit
53
76
  @seed = seed
77
+ @site_selector = site_selector
78
+ @oracle = oracle || DiagnosticOracle.new(
79
+ configuration: configuration, environment: environment, project_scan: project_scan
80
+ )
54
81
  end
55
82
 
56
83
  # @param path [String] the file to measure (used as the in-memory bind path)
@@ -58,21 +85,13 @@ module Rigor
58
85
  # @return [FileResult]
59
86
  def scan_file(path, source: nil)
60
87
  source ||= File.read(path, encoding: Encoding::UTF_8)
61
- mutator = Mutator.new(source)
62
- kept, = mutator.filter_by_type(mutator.mutations, environment: @environment, path: path)
63
- kept = sample(kept)
88
+ kept = kept_mutations(source, path)
64
89
  return FileResult.new(path: path, killed: 0, survived: 0, sites: []) if kept.empty?
65
90
 
66
- baseline = signatures(analyse(source, path))
67
- measure(source, path, kept, baseline)
68
- end
69
-
70
- private
71
-
72
- def measure(source, path, mutations, baseline)
91
+ baseline = @oracle.baseline(source: source, path: path)
73
92
  killed = 0
74
93
  sites = []
75
- mutations.each do |mut|
94
+ kept.each do |mut|
76
95
  case classify(source, path, mut, baseline)
77
96
  when :killed then killed += 1
78
97
  when :survived then sites << surviving_site(mut)
@@ -82,39 +101,80 @@ module Rigor
82
101
  FileResult.new(path: path, killed: killed, survived: sites.size, sites: sites)
83
102
  end
84
103
 
104
+ # ADR-70 — the fused static∪dynamic measurement. Runs the type pass
105
+ # (the {DiagnosticOracle}); for every mutant the type checker did **not**
106
+ # kill, asks `test_oracle` whether the project's test suite catches it.
107
+ # The expensive suite run is paid only for type-survivors (the gradual
108
+ # short-circuit), so the cost is proportional to the protection hole.
109
+ # @param test_oracle [TestSuiteOracle]
110
+ # @return [FusedFileResult]
111
+ def scan_file_fused(path, test_oracle:, source: nil)
112
+ source ||= File.read(path, encoding: Encoding::UTF_8)
113
+ kept = kept_mutations(source, path)
114
+ return FusedFileResult.new(path: path, type_killed: 0, test_killed: 0, sites: []) if kept.empty?
115
+
116
+ baseline = @oracle.baseline(source: source, path: path)
117
+ type_killed = 0
118
+ test_killed = 0
119
+ sites = []
120
+ kept.each do |mut|
121
+ case classify(source, path, mut, baseline)
122
+ when :killed then type_killed += 1
123
+ when :survived
124
+ if test_oracle.killed?(path: path, original: source, mutant_source: mut.apply(source))
125
+ test_killed += 1
126
+ else
127
+ sites << fused_site(mut, :none)
128
+ end
129
+ # :invalid — a parse-broken mutant; not a measurement, skip it.
130
+ end
131
+ end
132
+ FusedFileResult.new(path: path, type_killed: type_killed, test_killed: test_killed, sites: sites)
133
+ end
134
+
135
+ private
136
+
137
+ # The mutations to measure: the biteable filter (concrete-type sites only;
138
+ # the FP-safe default — an unresolved receiver is kept) or, under the
139
+ # `:all` selector (ADR-69 Seam 2), every dispatch site including Dynamic
140
+ # receivers. Optionally sampled.
141
+ def kept_mutations(source, path)
142
+ mutator = Mutator.new(source)
143
+ muts = mutator.mutations
144
+ kept =
145
+ if @site_selector == :all
146
+ mutator.dispatch_site_mutations(muts, environment: @environment, path: path)
147
+ else
148
+ mutator.filter_by_type(muts, environment: @environment, path: path).first
149
+ end
150
+ sample(kept)
151
+ end
152
+
85
153
  def classify(source, path, mut, baseline)
86
154
  mutant_source = mut.apply(source)
87
155
  return :invalid unless Prism.parse(mutant_source).success?
88
156
 
89
- new_diagnostics = analyse(mutant_source, path).reject { |d| baseline.include?(sig(d)) }
90
- new_diagnostics.empty? ? :survived : :killed
157
+ @oracle.killed?(mutant_source: mutant_source, path: path, baseline: baseline) ? :killed : :survived
91
158
  rescue StandardError
92
159
  # A harness-level failure on one mutant must not abort the file.
93
160
  :invalid
94
161
  end
95
162
 
96
- # cache_store: nil + prebuilt: scan ⇒ the run cache is bypassed and the
97
- # mutant is always re-analysed against the in-memory bytes.
98
- def analyse(source, path)
99
- Rigor::Analysis::Runner.new(
100
- configuration: @configuration, environment: @environment, prebuilt: @project_scan,
101
- cache_store: nil, collect_stats: false
102
- ).run_source(source: source, path: path).diagnostics
103
- end
104
-
105
163
  def sample(mutations)
106
164
  return mutations unless @limit
107
165
 
108
166
  mutations.sample(@limit, random: Random.new(@seed))
109
167
  end
110
168
 
111
- def signatures(diagnostics) = diagnostics.to_set { |d| sig(d) }
112
- def sig(diagnostic) = [diagnostic.rule, diagnostic.path, diagnostic.line, diagnostic.column, diagnostic.message]
113
-
114
169
  def surviving_site(mut)
115
170
  SurvivingSite.new(line: mut.line, receiver: mut.anchor_type,
116
171
  method_name: mut.method_name, operator: mut.operator.to_s)
117
172
  end
173
+
174
+ def fused_site(mut, protection)
175
+ FusedSite.new(line: mut.line, receiver: mut.anchor_type, method_name: mut.method_name,
176
+ operator: mut.operator.to_s, protection: protection)
177
+ end
118
178
  end
119
179
  end
120
180
  end
@@ -98,6 +98,27 @@ module Rigor
98
98
  [kept, mutations.size - kept.size]
99
99
  end
100
100
 
101
+ # ADR-69 Seam 2 (AllSites) — keep every *dispatch-site* mutation (a method
102
+ # call or a call-argument literal), Dynamic receiver included, annotating
103
+ # the anchor type where Rigor holds one. Drops only non-dispatch literals
104
+ # (a literal outside any call — no receiver contract to violate). The
105
+ # biteable {#filter_by_type} hides exactly the Dynamic sites a test-suite
106
+ # consumer most wants to probe: where Rigor cannot bite, a test is the only
107
+ # protection. Use only with a {TestSuiteOracle} — at a Dynamic site the
108
+ # type pass can never kill, so without the test axis these are all noise.
109
+ def dispatch_site_mutations(mutations, environment:, path:)
110
+ base = Rigor::Scope.empty(environment: environment, source_path: path)
111
+ index = Rigor::Inference::ScopeIndexer.index(@parse.value, default_scope: base)
112
+ cache = {}
113
+ mutations.select do |mut|
114
+ next false if mut.method_name.nil?
115
+
116
+ _keep, type = anchor_decision(mut.anchor, index, cache)
117
+ mut.anchor_type = type
118
+ true
119
+ end
120
+ end
121
+
101
122
  private
102
123
 
103
124
  def walk(node, &blk)
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rigor
4
+ module Protection
5
+ # ADR-70 — the **test-suite** kill oracle, the dynamic sibling of
6
+ # {DiagnosticOracle} on the ADR-69 seam. A mutant is killed iff applying its
7
+ # bytes to the file under test turns the project's test suite **red**. This is
8
+ # the dynamic half of the fused static∪dynamic protection map: a `Dynamic`
9
+ # site Rigor cannot bite (a type survivor) may still be fully guarded by a
10
+ # test.
11
+ #
12
+ # The suite command is the **runner hook** (`--test-command`, e.g.
13
+ # `bundle exec rake`). The runner is injectable so the decision logic is
14
+ # unit-testable without shelling out; the default shells out and reads the
15
+ # process exit status (0 = green / passed).
16
+ #
17
+ # I/O policy: {#killed?} writes the mutant to disk, runs the suite, and
18
+ # **always restores** the original bytes in an `ensure` — a normal exception
19
+ # never leaves a mutant on disk. (A hard interrupt mid-suite is the standard
20
+ # mutation-testing hazard the `ensure` cannot cover; callers running this in
21
+ # CI accept that, as `mutant` / Stryker do.)
22
+ class TestSuiteOracle
23
+ # @param command [Array<String>] the test command (the runner hook)
24
+ # @param runner [#call, nil] `runner.call(command) -> true iff the suite
25
+ # passed`. Defaults to shelling out via `system`.
26
+ def initialize(command:, runner: nil)
27
+ @command = command
28
+ @runner = runner || method(:shell_run)
29
+ end
30
+
31
+ # The baseline: the suite must pass on clean code, else "a mutant survived"
32
+ # is meaningless (every mutant would look killed, or none would). Run once
33
+ # before measuring.
34
+ def green?
35
+ @runner.call(@command)
36
+ end
37
+
38
+ # Killed iff the mutant turns the suite red. Restores `original` afterward.
39
+ # @param path [String] the file to (temporarily) overwrite with the mutant
40
+ # @param original [String] the clean bytes to restore
41
+ # @param mutant_source [String] the mutated bytes to test against
42
+ def killed?(path:, original:, mutant_source:)
43
+ File.write(path, mutant_source)
44
+ !@runner.call(@command)
45
+ ensure
46
+ File.write(path, original)
47
+ end
48
+
49
+ private
50
+
51
+ # Run the suite with Bundler's environment stripped, so a `bundle exec`
52
+ # test command resolves the **target** project's Gemfile — not whatever
53
+ # bundle Rigor itself was launched under. Running Rigor via `bundle exec`
54
+ # leaks `RUBYOPT=-rbundler/setup` + `GEM_HOME` / `BUNDLE_*` into a plain
55
+ # `system` subprocess, which then resolves the target's Gemfile against
56
+ # Rigor's gems and fails — so a green suite looks red and the run aborts.
57
+ # `with_unbundled_env` restores the pre-bundler env (a bare `env -u
58
+ # BUNDLE_GEMFILE` is not enough — the `BUNDLER_ORIG_*` preservers defeat
59
+ # it). Found validating ADR-70 on real projects (2026-06-17).
60
+ def shell_run(command)
61
+ run = -> { system(*command, out: File::NULL, err: File::NULL) }
62
+ return run.call unless defined?(Bundler) && Bundler.respond_to?(:with_unbundled_env)
63
+
64
+ Bundler.with_unbundled_env(&run)
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rigor
4
+ # Classifies each configured `signature_paths:` entry by what it
5
+ # actually contributes to the RBS environment, so a caller can warn
6
+ # when a configured path resolves to nothing.
7
+ #
8
+ # The failure this guards against is silent. {Environment::RbsLoader}
9
+ # `add`s a `signature_paths:` entry only when `path.directory?`, and
10
+ # only the `.rbs` files under it carry signatures — so a typo'd or
11
+ # moved path (or a directory holding no `.rbs`) loads zero signatures
12
+ # with no trace on stderr or in the run summary. The downstream symptom
13
+ # is the most authoritative diagnostics: every call into the extensions
14
+ # the missing RBS was meant to describe fires `call.undefined-method` at
15
+ # `evidence_tier: high`. A one-character path typo can manufacture
16
+ # hundreds of plausible-looking false positives; surfacing the empty
17
+ # entry makes the real cause visible.
18
+ #
19
+ # The audit deliberately mirrors the loader's own acceptance test
20
+ # (`path.directory?` + a recursive `**/*.rbs` glob) so a `:ok` verdict
21
+ # means the loader did load from it and a warning means it did not.
22
+ module SignaturePathAudit
23
+ # One configured `signature_paths:` entry's resolution status.
24
+ #
25
+ # `status` is one of:
26
+ # - `:ok` — a directory containing at least one `.rbs`.
27
+ # - `:missing` — the path does not exist.
28
+ # - `:not_directory` — the path exists but is not a directory (the
29
+ # loader only `add`s directories, so a `.rbs` file passed directly
30
+ # is silently ignored).
31
+ # - `:empty` — a directory with no `.rbs` file (recursive).
32
+ Entry = Data.define(:path, :status, :rbs_file_count) do
33
+ def ok?
34
+ status == :ok
35
+ end
36
+
37
+ def warning?
38
+ !ok?
39
+ end
40
+
41
+ # One-line, human-facing reason. The wording matches the loader's
42
+ # actual behaviour ("loaded nothing from it") rather than the
43
+ # filesystem error, so the message points at the consequence.
44
+ def message
45
+ case status
46
+ when :missing
47
+ "signature_paths: #{path.inspect} does not exist (no signatures loaded from it)"
48
+ when :not_directory
49
+ "signature_paths: #{path.inspect} is not a directory (no signatures loaded from it)"
50
+ when :empty
51
+ "signature_paths: #{path.inspect} matched 0 signature files"
52
+ else
53
+ "signature_paths: #{path.inspect} loaded #{rbs_file_count} signature file(s)"
54
+ end
55
+ end
56
+
57
+ def to_h
58
+ { "path" => path, "status" => status.to_s, "rbs_file_count" => rbs_file_count, "message" => message }
59
+ end
60
+ end
61
+
62
+ # Audits each configured entry. `signature_paths` is the
63
+ # {Configuration#signature_paths} array (absolute paths, already
64
+ # resolved against the config file's directory). Pass `nil` — the
65
+ # unset default, where Rigor auto-detects `<root>/sig` — to get an
66
+ # empty result: an absent auto-detected `sig/` is a normal setup, not
67
+ # a misconfiguration, so it is never audited.
68
+ #
69
+ # @param signature_paths [Array<String, Pathname>, nil]
70
+ # @return [Array<Entry>]
71
+ def self.audit(signature_paths)
72
+ Array(signature_paths).map { |path| classify(path.to_s) }
73
+ end
74
+
75
+ # The subset of {audit} that resolved to nothing — the entries worth
76
+ # warning about.
77
+ #
78
+ # @param signature_paths [Array<String, Pathname>, nil]
79
+ # @return [Array<Entry>]
80
+ def self.warnings(signature_paths)
81
+ audit(signature_paths).select(&:warning?)
82
+ end
83
+
84
+ def self.classify(path)
85
+ return Entry.new(path: path, status: :missing, rbs_file_count: 0) unless File.exist?(path)
86
+ return Entry.new(path: path, status: :not_directory, rbs_file_count: 0) unless File.directory?(path)
87
+
88
+ count = Dir.glob(File.join(path, "**", "*.rbs")).size
89
+ Entry.new(path: path, status: count.zero? ? :empty : :ok, rbs_file_count: count)
90
+ end
91
+ end
92
+ end
data/lib/rigor/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rigor
4
- VERSION = "0.2.0"
4
+ VERSION = "0.2.1"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rigortype
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rigor contributors
@@ -281,6 +281,29 @@ files:
281
281
  - data/builtins/ruby_core/string.yml
282
282
  - data/builtins/ruby_core/struct.yml
283
283
  - data/builtins/ruby_core/time.yml
284
+ - data/core_overlay/numeric.rbs
285
+ - data/core_overlay/pathname.rbs
286
+ - data/core_overlay/string_scanner.rbs
287
+ - data/gem_overlay/activesupport/core_ext.rbs
288
+ - data/vendored_gem_sigs/ast/ast.rbs
289
+ - data/vendored_gem_sigs/bcrypt/bcrypt.rbs
290
+ - data/vendored_gem_sigs/bundler/bundler.rbs
291
+ - data/vendored_gem_sigs/cgi/cgi_extras.rbs
292
+ - data/vendored_gem_sigs/did_you_mean/did_you_mean_extras.rbs
293
+ - data/vendored_gem_sigs/idn-ruby/idn.rbs
294
+ - data/vendored_gem_sigs/mysql2/client.rbs
295
+ - data/vendored_gem_sigs/mysql2/error.rbs
296
+ - data/vendored_gem_sigs/mysql2/result.rbs
297
+ - data/vendored_gem_sigs/mysql2/statement.rbs
298
+ - data/vendored_gem_sigs/nokogiri/nokogiri.rbs
299
+ - data/vendored_gem_sigs/nokogiri/nokogiri_html5.rbs
300
+ - data/vendored_gem_sigs/pg/pg.rbs
301
+ - data/vendored_gem_sigs/prism/prism_supplement.rbs
302
+ - data/vendored_gem_sigs/redis/errors.rbs
303
+ - data/vendored_gem_sigs/redis/future.rbs
304
+ - data/vendored_gem_sigs/redis/redis.rbs
305
+ - data/vendored_gem_sigs/redis/redis_extras.rbs
306
+ - data/vendored_gem_sigs/rubygems/rubygems_extras.rbs
284
307
  - exe/rigor
285
308
  - lib/rigor.rb
286
309
  - lib/rigor/analysis/baseline.rb
@@ -343,12 +366,15 @@ files:
343
366
  - lib/rigor/cli/ci_detector.rb
344
367
  - lib/rigor/cli/command.rb
345
368
  - lib/rigor/cli/coverage_command.rb
369
+ - lib/rigor/cli/coverage_mutation.rb
346
370
  - lib/rigor/cli/coverage_renderer.rb
347
371
  - lib/rigor/cli/coverage_report.rb
348
372
  - lib/rigor/cli/coverage_scan.rb
349
373
  - lib/rigor/cli/diagnostic_formats.rb
350
374
  - lib/rigor/cli/diff_command.rb
351
375
  - lib/rigor/cli/explain_command.rb
376
+ - lib/rigor/cli/fused_protection_renderer.rb
377
+ - lib/rigor/cli/fused_protection_report.rb
352
378
  - lib/rigor/cli/lsp_command.rb
353
379
  - lib/rigor/cli/mcp_command.rb
354
380
  - lib/rigor/cli/mutation_protection_renderer.rb
@@ -373,6 +399,7 @@ files:
373
399
  - lib/rigor/cli/type_scan_command.rb
374
400
  - lib/rigor/cli/type_scan_renderer.rb
375
401
  - lib/rigor/cli/type_scan_report.rb
402
+ - lib/rigor/config_audit.rb
376
403
  - lib/rigor/configuration.rb
377
404
  - lib/rigor/configuration/dependencies.rb
378
405
  - lib/rigor/configuration/severity_profile.rb
@@ -519,8 +546,10 @@ files:
519
546
  - lib/rigor/plugin/source_rbs_synthesis_reporter.rb
520
547
  - lib/rigor/plugin/trust_policy.rb
521
548
  - lib/rigor/plugin/type_node_resolver.rb
549
+ - lib/rigor/protection/diagnostic_oracle.rb
522
550
  - lib/rigor/protection/mutation_scanner.rb
523
551
  - lib/rigor/protection/mutator.rb
552
+ - lib/rigor/protection/test_suite_oracle.rb
524
553
  - lib/rigor/rbs_extended.rb
525
554
  - lib/rigor/rbs_extended/conformance_checker.rb
526
555
  - lib/rigor/rbs_extended/hkt_directives.rb
@@ -540,6 +569,7 @@ files:
540
569
  - lib/rigor/sig_gen/type_elaborator.rb
541
570
  - lib/rigor/sig_gen/write_result.rb
542
571
  - lib/rigor/sig_gen/writer.rb
572
+ - lib/rigor/signature_path_audit.rb
543
573
  - lib/rigor/source.rb
544
574
  - lib/rigor/source/constant_path.rb
545
575
  - lib/rigor/source/literals.rb