rigortype 0.1.9 → 0.1.10

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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/lib/rigor/analysis/runner.rb +67 -9
  4. data/lib/rigor/analysis/worker_session.rb +13 -4
  5. data/lib/rigor/cache/rbs_descriptor.rb +21 -2
  6. data/lib/rigor/cache/rbs_environment.rb +2 -1
  7. data/lib/rigor/cli/annotate_command.rb +57 -7
  8. data/lib/rigor/cli/coverage_command.rb +126 -0
  9. data/lib/rigor/cli/coverage_renderer.rb +162 -0
  10. data/lib/rigor/cli/coverage_report.rb +75 -0
  11. data/lib/rigor/cli/mcp_command.rb +70 -0
  12. data/lib/rigor/cli.rb +73 -3
  13. data/lib/rigor/environment/rbs_loader.rb +46 -5
  14. data/lib/rigor/environment/reporters.rb +3 -2
  15. data/lib/rigor/environment.rb +159 -4
  16. data/lib/rigor/inference/def_return_typer.rb +98 -0
  17. data/lib/rigor/inference/expression_typer.rb +143 -12
  18. data/lib/rigor/inference/method_dispatcher/constant_folding.rb +5 -0
  19. data/lib/rigor/inference/method_dispatcher/literal_string_folding.rb +62 -15
  20. data/lib/rigor/inference/method_dispatcher/shape_dispatch.rb +115 -7
  21. data/lib/rigor/inference/precision_scanner.rb +131 -0
  22. data/lib/rigor/inference/statement_evaluator.rb +26 -2
  23. data/lib/rigor/mcp/loop.rb +43 -0
  24. data/lib/rigor/mcp/server.rb +263 -0
  25. data/lib/rigor/mcp.rb +16 -0
  26. data/lib/rigor/plugin/base.rb +28 -5
  27. data/lib/rigor/plugin/manifest.rb +33 -5
  28. data/lib/rigor/plugin/registry.rb +21 -0
  29. data/lib/rigor/plugin/source_rbs_synthesis_reporter.rb +51 -0
  30. data/lib/rigor/sig_gen/generator.rb +150 -75
  31. data/lib/rigor/type/combinator.rb +57 -0
  32. data/lib/rigor/version.rb +1 -1
  33. data/sig/rigor/analysis/baseline.rbs +39 -0
  34. data/sig/rigor/environment.rbs +3 -2
  35. data/sig/rigor/type.rbs +4 -0
  36. data/sig/rigor.rbs +2 -0
  37. metadata +32 -1
@@ -0,0 +1,162 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Rigor
6
+ class CLI
7
+ # Renders a `CoverageReport` as terminal-friendly text or JSON.
8
+ class CoverageRenderer
9
+ TIER_LABELS = {
10
+ constant: "constant",
11
+ nominal: "nominal",
12
+ shaped: "shaped (Tuple/Hash/Range/generic)",
13
+ refined: "refined",
14
+ bot: "bot (unreachable)",
15
+ dynamic_specific: "dynamic — partial info",
16
+ dynamic_top: "dynamic — opaque (untyped)",
17
+ top: "top"
18
+ }.freeze
19
+
20
+ def initialize(out:)
21
+ @out = out
22
+ end
23
+
24
+ def render(report, format:)
25
+ case format
26
+ when "text" then render_text(report)
27
+ when "json" then render_json(report)
28
+ else raise OptionParser::InvalidArgument, "unsupported format: #{format}"
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def render_text(report)
35
+ render_text_header(report)
36
+ render_text_summary(report)
37
+ render_text_tier_table(report)
38
+ render_text_per_file(report) if report.per_file.size > 1
39
+ render_text_parse_errors(report)
40
+ end
41
+
42
+ def render_text_header(report)
43
+ n = report.files.size
44
+ suffix = n == 1 ? "" : "s"
45
+ @out.puts("Type coverage: #{n} file#{suffix}")
46
+ report.files.first(5).each { |f| @out.puts(" - #{f}") }
47
+ @out.puts(" ... (#{n - 5} more)") if n > 5
48
+ @out.puts
49
+ end
50
+
51
+ def render_text_summary(report)
52
+ g = report.grand_total
53
+ p = report.precise_count
54
+ o = report.opaque_count
55
+ @out.puts("Summary:")
56
+ @out.puts(" files processed: #{report.files.size - report.parse_errors.size}")
57
+ @out.puts(" parse errors: #{report.parse_errors.size}")
58
+ @out.puts(" expressions typed: #{g}")
59
+ @out.puts(" precise: #{p}#{pct(p, g)}")
60
+ @out.puts(" dynamic (opaque): #{o}#{pct(o, g)}")
61
+ @out.puts(" precision ratio: #{(report.precision_ratio * 100).round(2)}%")
62
+ @out.puts
63
+ end
64
+
65
+ def render_text_tier_table(report)
66
+ @out.puts("Tier breakdown:")
67
+ g = report.grand_total
68
+ Inference::PrecisionScanner::TIERS.each do |tier|
69
+ n = report.tier_count(tier)
70
+ next if n.zero?
71
+
72
+ label = TIER_LABELS.fetch(tier, tier.to_s).ljust(36)
73
+ @out.puts(" #{label} #{n.to_s.rjust(7)}#{pct(n, g)}")
74
+ end
75
+ @out.puts
76
+ end
77
+
78
+ def render_text_per_file(report)
79
+ @out.puts("Per-file breakdown:")
80
+ width = report.per_file.map { |e| e[:file].size }.max || 0
81
+ report.per_file.sort_by { |e| e[:result].precision_ratio }.each do |entry|
82
+ r = entry[:result]
83
+ next if r.total.zero?
84
+
85
+ ratio_str = "#{(r.precision_ratio * 100).round(1)}%".rjust(6)
86
+ @out.puts(" #{entry[:file].ljust(width)} #{ratio_str} (#{r.precise_count}/#{r.total})")
87
+ end
88
+ @out.puts
89
+ end
90
+
91
+ def render_text_parse_errors(report)
92
+ return if report.parse_errors.empty?
93
+
94
+ @out.puts("Parse errors:")
95
+ report.parse_errors.each do |entry|
96
+ @out.puts(" #{entry[:file]}: #{entry[:errors].join('; ')}")
97
+ end
98
+ end
99
+
100
+ def render_json(report)
101
+ @out.puts(JSON.pretty_generate(json_payload(report)))
102
+ end
103
+
104
+ def json_payload(report)
105
+ g = report.grand_total
106
+ {
107
+ summary: json_summary(report, g),
108
+ by_tier: tier_payload(g) { |tier| report.tier_count(tier) },
109
+ by_file: report.per_file.map { |e| file_payload(e) },
110
+ parse_errors: report.parse_errors.map { |e| { file: e[:file], errors: e[:errors] } }
111
+ }
112
+ end
113
+
114
+ def json_summary(report, grand_total)
115
+ g = grand_total
116
+ dsc = report.total.dynamic_specific_count
117
+ {
118
+ files_processed: report.files.size - report.parse_errors.size,
119
+ parse_errors: report.parse_errors.size,
120
+ expressions_typed: g,
121
+ precise_count: report.precise_count,
122
+ precise_ratio: ratio_f(report.precision_ratio),
123
+ dynamic_opaque_count: report.opaque_count,
124
+ dynamic_opaque_ratio: ratio_f(report.opaque_ratio),
125
+ dynamic_specific_count: dsc,
126
+ dynamic_specific_ratio: ratio_f(dsc.fdiv(g.nonzero? || 1))
127
+ }
128
+ end
129
+
130
+ def tier_payload(grand_total)
131
+ g = grand_total
132
+ Inference::PrecisionScanner::TIERS.to_h do |tier|
133
+ n = yield tier
134
+ [tier, { count: n, ratio: ratio_f(n.fdiv(g.nonzero? || 1)) }]
135
+ end
136
+ end
137
+
138
+ def file_payload(entry)
139
+ r = entry[:result]
140
+ {
141
+ file: entry[:file],
142
+ expressions_typed: r.total,
143
+ precise_count: r.precise_count,
144
+ precise_ratio: ratio_f(r.precision_ratio),
145
+ dynamic_opaque_count: r.opaque_count,
146
+ dynamic_opaque_ratio: ratio_f(r.opaque_ratio),
147
+ by_tier: tier_payload(r.total) { |tier| r.tier_counts.fetch(tier, 0) }
148
+ }
149
+ end
150
+
151
+ def pct(numerator, denominator)
152
+ return "" if denominator.zero?
153
+
154
+ " (#{(numerator.fdiv(denominator) * 100).round(1)}%)"
155
+ end
156
+
157
+ def ratio_f(val)
158
+ val.round(4)
159
+ end
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rigor
4
+ class CLI
5
+ # Aggregated precision-coverage report assembled by `CoverageCommand`.
6
+ # Holds per-file breakdowns and accumulated totals; consumed by
7
+ # `CoverageRenderer` for text and JSON output.
8
+ class CoverageReport < Data.define(
9
+ :files,
10
+ :parse_errors,
11
+ :per_file,
12
+ :total
13
+ )
14
+ # Sum of all per-file totals.
15
+ def grand_total
16
+ total.total
17
+ end
18
+
19
+ def precise_count
20
+ total.precise_count
21
+ end
22
+
23
+ def opaque_count
24
+ total.opaque_count
25
+ end
26
+
27
+ def precision_ratio
28
+ total.precision_ratio
29
+ end
30
+
31
+ def opaque_ratio
32
+ total.opaque_ratio
33
+ end
34
+
35
+ def tier_count(tier)
36
+ total.tier_counts.fetch(tier, 0)
37
+ end
38
+ end
39
+
40
+ # Mutable accumulator used while scanning files.
41
+ class CoverageAccumulator
42
+ require_relative "../inference/precision_scanner"
43
+
44
+ def initialize
45
+ @per_file = []
46
+ @parse_errors = []
47
+ # Accumulated totals across all files.
48
+ @total_total = 0
49
+ @total_tier_counts = Inference::PrecisionScanner::TIERS.to_h { |t| [t, 0] }
50
+ end
51
+
52
+ def absorb(path, file_result)
53
+ @per_file << { file: path, result: file_result }
54
+ @total_total += file_result.total
55
+ file_result.tier_counts.each { |tier, n| @total_tier_counts[tier] += n }
56
+ end
57
+
58
+ def record_parse_error(path, errors)
59
+ @parse_errors << { file: path, errors: errors.map(&:message) }
60
+ end
61
+
62
+ def to_report(files, _options)
63
+ CoverageReport.new(
64
+ files: files,
65
+ parse_errors: @parse_errors,
66
+ per_file: @per_file,
67
+ total: Inference::PrecisionScanner::FileResult.new(
68
+ total: @total_total,
69
+ tier_counts: @total_tier_counts
70
+ )
71
+ )
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "optionparser"
4
+
5
+ module Rigor
6
+ class CLI
7
+ # Executes the `rigor mcp` command.
8
+ #
9
+ # Starts a long-running MCP (Model Context Protocol) server over stdio.
10
+ # The server exposes Rigor's analysis tools as MCP tool calls over a
11
+ # newline-delimited JSON-RPC 2.0 stream. See ADR-33.
12
+ #
13
+ # Slice 1 ships the stdio transport with seven read-only tools:
14
+ # rigor_check, rigor_type_of, rigor_triage, rigor_annotate,
15
+ # rigor_sig_gen, rigor_explain, rigor_coverage.
16
+ class McpCommand
17
+ USAGE = "Usage: rigor mcp [options]"
18
+
19
+ def initialize(argv:, out:, err:)
20
+ @argv = argv
21
+ @out = out
22
+ @err = err
23
+ end
24
+
25
+ # @return [Integer] CLI exit status.
26
+ def run
27
+ options = parse_options
28
+ return CLI::EXIT_USAGE if options == :usage_error
29
+
30
+ transport = options.fetch(:transport)
31
+ unless transport == "stdio"
32
+ @err.puts("rigor mcp: unsupported transport: #{transport.inspect} (only `stdio` is supported in v1)")
33
+ return CLI::EXIT_USAGE
34
+ end
35
+
36
+ require_relative "../mcp"
37
+ require_relative "../version"
38
+
39
+ server = MCP::Server.new(config_path: options.fetch(:config), err: $stderr)
40
+ loop_runner = MCP::Loop.new(input: $stdin, output: $stdout, server: server)
41
+ loop_runner.run
42
+ 0
43
+ end
44
+
45
+ private
46
+
47
+ def parse_options
48
+ options = { transport: "stdio", config: nil }
49
+
50
+ parser = OptionParser.new do |opts|
51
+ opts.banner = USAGE
52
+ opts.on("--transport=NAME",
53
+ "Transport (default: stdio; only stdio is supported in v1)") do |value|
54
+ options[:transport] = value
55
+ end
56
+ opts.on("--config=PATH",
57
+ "Session-level default config path (individual tool calls may override)") do |value|
58
+ options[:config] = value
59
+ end
60
+ end
61
+ parser.parse!(@argv)
62
+ options
63
+ rescue OptionParser::ParseError => e
64
+ @err.puts(e.message)
65
+ @err.puts(USAGE)
66
+ :usage_error
67
+ end
68
+ end
69
+ end
70
+ end
data/lib/rigor/cli.rb CHANGED
@@ -28,8 +28,10 @@ module Rigor
28
28
  "diff" => :run_diff,
29
29
  "sig-gen" => :run_sig_gen,
30
30
  "lsp" => :run_lsp,
31
+ "mcp" => :run_mcp,
31
32
  "baseline" => :run_baseline,
32
- "triage" => :run_triage
33
+ "triage" => :run_triage,
34
+ "coverage" => :run_coverage
33
35
  }.freeze
34
36
 
35
37
  def self.start(argv = ARGV, out: $stdout, err: $stderr)
@@ -81,7 +83,7 @@ module Rigor
81
83
  buffer = resolve_buffer_binding(options)
82
84
  return EXIT_USAGE if buffer == :usage_error
83
85
 
84
- configuration = Configuration.load(options.fetch(:config))
86
+ configuration = load_check_configuration(options)
85
87
  cache_root = configuration.cache_path
86
88
  handle_clear_cache(cache_root) if options.fetch(:clear_cache)
87
89
 
@@ -281,7 +283,16 @@ module Rigor
281
283
  baseline: :unset,
282
284
  # ADR-22 slice 5 — `--baseline-strict` CI gate: fail the
283
285
  # run on any baseline drift, in either direction.
284
- baseline_strict: false
286
+ baseline_strict: false,
287
+ # ADR-32 WD10 carry-over — `--treat-all-as-inline-rbs`
288
+ # forces the `rigor-rbs-inline` plugin into the loaded
289
+ # plugin set with `require_magic_comment: false` so a
290
+ # single ad-hoc `rigor check` invocation treats every
291
+ # analysed file as inline-RBS without the user editing
292
+ # `.rigor.yml`. Intended for single-file / ad-hoc CI use;
293
+ # ordinary projects should configure the plugin in
294
+ # `.rigor.yml`.
295
+ treat_all_as_inline_rbs: false
285
296
  }
286
297
  parser = OptionParser.new do |opts| # rubocop:disable Metrics/BlockLength
287
298
  opts.banner = "Usage: rigor check [options] [paths]"
@@ -319,11 +330,56 @@ module Rigor
319
330
  "ADR-22: fail the run on any baseline drift (CI gate)") do
320
331
  options[:baseline_strict] = true
321
332
  end
333
+ opts.on("--treat-all-as-inline-rbs",
334
+ "ADR-32: force-load rigor-rbs-inline with require_magic_comment: false") do
335
+ options[:treat_all_as_inline_rbs] = true
336
+ end
322
337
  end
323
338
  parser.parse!(@argv)
324
339
  options
325
340
  end
326
341
 
342
+ # ADR-32 WD10 carry-over — wraps `Configuration.load` so the
343
+ # CLI's `--treat-all-as-inline-rbs` flag can inject a
344
+ # `rigor-rbs-inline` plugin entry with
345
+ # `require_magic_comment: false` into the loaded plugin
346
+ # set. Re-runs the include-aware YAML load and applies the
347
+ # injection before `Configuration.new` so the new entry
348
+ # follows the normal coercion path. A pre-existing
349
+ # `rigor-rbs-inline` entry (by gem name or `id: rbs-inline`)
350
+ # is removed first so the synthesised entry's
351
+ # `require_magic_comment: false` wins unconditionally.
352
+ def load_check_configuration(options)
353
+ return Configuration.load(options.fetch(:config)) unless options.fetch(:treat_all_as_inline_rbs)
354
+
355
+ path = options.fetch(:config) || Configuration.discover
356
+ data = path && File.exist?(path) ? Configuration.load_with_includes(path) : {}
357
+ data = data.dup
358
+ data["plugins"] = inject_treat_all_as_inline_rbs(Array(data["plugins"]))
359
+ Configuration.new(Configuration::DEFAULTS.merge(data))
360
+ end
361
+
362
+ def inject_treat_all_as_inline_rbs(entries)
363
+ filtered = entries.reject { |entry| rigor_rbs_inline_entry?(entry) }
364
+ filtered + [{
365
+ "gem" => "rigor-rbs-inline",
366
+ "id" => "rbs-inline",
367
+ "config" => { "require_magic_comment" => false }
368
+ }]
369
+ end
370
+
371
+ def rigor_rbs_inline_entry?(entry)
372
+ case entry
373
+ when String
374
+ entry == "rigor-rbs-inline"
375
+ when Hash
376
+ string_keyed = entry.to_h { |k, v| [k.to_s, v] }
377
+ string_keyed["gem"] == "rigor-rbs-inline" || string_keyed["id"] == "rbs-inline"
378
+ else
379
+ false
380
+ end
381
+ end
382
+
327
383
  def handle_clear_cache(cache_root)
328
384
  if File.directory?(cache_root)
329
385
  FileUtils.rm_rf(cache_root)
@@ -516,6 +572,12 @@ module Rigor
516
572
  LspCommand.new(argv: @argv, out: @out, err: @err).run
517
573
  end
518
574
 
575
+ def run_mcp
576
+ require_relative "cli/mcp_command"
577
+
578
+ McpCommand.new(argv: @argv, out: @out, err: @err).run
579
+ end
580
+
519
581
  def run_baseline
520
582
  require_relative "cli/baseline_command"
521
583
 
@@ -528,6 +590,12 @@ module Rigor
528
590
  CLI::TriageCommand.new(argv: @argv, out: @out, err: @err).run
529
591
  end
530
592
 
593
+ def run_coverage
594
+ require_relative "cli/coverage_command"
595
+
596
+ CLI::CoverageCommand.new(argv: @argv, out: @out, err: @err).run
597
+ end
598
+
531
599
  def write_result(result, format)
532
600
  case format
533
601
  when "json"
@@ -570,7 +638,9 @@ module Rigor
570
638
  diff Compare current diagnostics to a saved baseline JSON
571
639
  sig-gen Emit RBS skeletons inferred from .rb sources (ADR-14)
572
640
  lsp Run the Rigor Language Server (LSP) over stdio
641
+ mcp Run the Rigor MCP server over stdio (ADR-33)
573
642
  triage Summarise diagnostics: distribution, hotspots, hints (ADR-23)
643
+ coverage Report type-precision coverage (precise vs Dynamic ratio)
574
644
  version Print the Rigor version
575
645
  help Print this help
576
646
  HELP
@@ -56,7 +56,7 @@ module Rigor
56
56
  # run. The gem stubs are intentionally read-only and
57
57
  # appended LAST so user-supplied `signature_paths` win on
58
58
  # name conflicts.
59
- def build_env_for(libraries:, signature_paths:)
59
+ def build_env_for(libraries:, signature_paths:, virtual_rbs: [])
60
60
  rbs_loader = RBS::EnvironmentLoader.new
61
61
  libraries.each do |library|
62
62
  next unless rbs_loader.has_library?(library: library, version: nil)
@@ -70,7 +70,36 @@ module Rigor
70
70
  vendored_gem_sig_paths.each do |path|
71
71
  rbs_loader.add(path: path) if path.directory?
72
72
  end
73
- RBS::Environment.from_loader(rbs_loader).resolve_type_names
73
+ env = RBS::Environment.from_loader(rbs_loader)
74
+ add_virtual_rbs(env, virtual_rbs)
75
+ env.resolve_type_names
76
+ end
77
+
78
+ # ADR-32 WD4 — merge synthesised-from-source RBS strings
79
+ # into the freshly-built environment. Each entry is a
80
+ # `[virtual_filename, rbs_source]` pair. `virtual_filename`
81
+ # is purely for diagnostic provenance (RBS parse errors
82
+ # cite it) — it is not a real file path. Per WD6 the
83
+ # synthesizer-emit path is responsible for catching its
84
+ # own parse errors and returning `nil` rather than
85
+ # garbage; this method assumes its input is parseable
86
+ # and only rescues `RBS::ParsingError` as a fail-soft.
87
+ def add_virtual_rbs(env, virtual_rbs)
88
+ return if virtual_rbs.nil? || virtual_rbs.empty?
89
+
90
+ virtual_rbs.each do |filename, content|
91
+ next if content.nil? || content.empty?
92
+
93
+ buffer = ::RBS::Buffer.new(name: filename.to_s, content: content.to_s)
94
+ _, directives, decls = ::RBS::Parser.parse_signature(buffer)
95
+ source = ::RBS::Source::RBS.new(buffer, directives || [], decls || [])
96
+ env.add_source(source)
97
+ rescue ::RBS::BaseError
98
+ # WD6 fail-soft: a single broken virtual RBS contribution
99
+ # does not pull the whole env down. The plugin layer
100
+ # records a `source-rbs-synthesis-failed` info diagnostic
101
+ # in slice 2; here we just skip the entry.
102
+ end
74
103
  end
75
104
 
76
105
  # Per-gem `data/vendored_gem_sigs/<gem>/` directories that
@@ -95,7 +124,7 @@ module Rigor
95
124
  end
96
125
  end
97
126
 
98
- attr_reader :libraries, :signature_paths, :cache_store
127
+ attr_reader :libraries, :signature_paths, :cache_store, :virtual_rbs
99
128
 
100
129
  # @param libraries [Array<String, Symbol>] stdlib library names to
101
130
  # load on top of core (e.g., `["pathname", "json"]`). Empty by
@@ -114,10 +143,18 @@ module Rigor
114
143
  # reflection artefacts). Pass `nil` (the default) to skip
115
144
  # the cache entirely; the runner threads its own Store
116
145
  # through here when caching is enabled.
117
- def initialize(libraries: [], signature_paths: [], cache_store: nil)
146
+ # @param virtual_rbs [Array<[String, String]>] ADR-32 WD4 —
147
+ # `[virtual_filename, rbs_source]` pairs synthesised from
148
+ # project source by a plugin's
149
+ # `Manifest#source_rbs_synthesizer`. Merged into the env
150
+ # after `signature_paths:` and the vendored stubs. Pass
151
+ # `[]` (the default) when no synthesizer-emitting plugin
152
+ # is loaded.
153
+ def initialize(libraries: [], signature_paths: [], cache_store: nil, virtual_rbs: [])
118
154
  @libraries = libraries.map(&:to_s).freeze
119
155
  @signature_paths = signature_paths.map { |p| Pathname(p) }.freeze
120
156
  @cache_store = cache_store
157
+ @virtual_rbs = virtual_rbs.map { |name, content| [name.to_s.dup.freeze, content.to_s.dup.freeze].freeze }.freeze
121
158
  # Per-loader memoization bucket. Held as a single
122
159
  # mutable Hash so the loader instance itself can be
123
160
  # `.freeze`d (per ADR-15 reflection-facade contract)
@@ -642,7 +679,11 @@ module Rigor
642
679
  end
643
680
 
644
681
  def build_env
645
- self.class.build_env_for(libraries: @libraries, signature_paths: @signature_paths)
682
+ self.class.build_env_for(
683
+ libraries: @libraries,
684
+ signature_paths: @signature_paths,
685
+ virtual_rbs: @virtual_rbs
686
+ )
646
687
  end
647
688
 
648
689
  def build_instance_definition(class_name)
@@ -29,11 +29,12 @@ module Rigor
29
29
  # return nil, and the consumer sites short-circuit on
30
30
  # `reporter.nil?`.
31
31
  class Reporters
32
- attr_accessor :rbs_extended, :boundary_cross
32
+ attr_accessor :rbs_extended, :boundary_cross, :source_rbs_synthesis
33
33
 
34
- def initialize(rbs_extended: nil, boundary_cross: nil)
34
+ def initialize(rbs_extended: nil, boundary_cross: nil, source_rbs_synthesis: nil)
35
35
  @rbs_extended = rbs_extended
36
36
  @boundary_cross = boundary_cross
37
+ @source_rbs_synthesis = source_rbs_synthesis
37
38
  end
38
39
  end
39
40
  end