simplecov-mcp 0.1.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f2a8ecf4bfeeedc4b7377baaa3fcabf0b196ad9c17f2ccd29e5532b7a6104751
4
+ data.tar.gz: 8b52e3920dd3734add19862838f03b967bb73d17dd769539a66160ef9950e38f
5
+ SHA512:
6
+ metadata.gz: bbcec76c297750544657a18cec0366148f1cff30ebff2c9b5bbc593f2d9492fe32554d2b4859f1e89f7efd19f35d35e7345f8ecdfa05d1c32ffea86f42c68ed6
7
+ data.tar.gz: 2266b137c11b92fcaedc7337010c7a5b8c6814d09b4a652bc642abad2082e3f8cd20e8d33930808257176e020602249408be71c206d518adba3cc577dab7aa41
data/README.md ADDED
@@ -0,0 +1,97 @@
1
+ # simplecov-mcp
2
+
3
+ MCP server + CLI for inspecting SimpleCov coverage data.
4
+
5
+ This gem provides:
6
+
7
+ - An MCP (Model Context Protocol) server exposing tools to query coverage for files.
8
+ - A human-friendly CLI that prints a sorted table of file coverage.
9
+
10
+ ## Installation
11
+
12
+ Add to your Gemfile or install directly:
13
+
14
+ ```
15
+ gem install simplecov-mcp
16
+ ```
17
+
18
+ Require path is `simplecov/mcp` (also `simplecov_mcp`). Executable is `simplecov-mcp`.
19
+
20
+ ## Usage
21
+
22
+ Environment variable:
23
+
24
+ - `SIMPLECOV_RESULTSET` — optional explicit path to `.resultset.json`.
25
+
26
+ Search order for resultset:
27
+
28
+ 1. `.resultset.json`
29
+ 2. `coverage/.resultset.json`
30
+ 3. `tmp/.resultset.json`
31
+
32
+ ### CLI Mode
33
+
34
+ Run in a project directory with a SimpleCov resultset:
35
+
36
+ ```
37
+ simplecov-mcp
38
+ ```
39
+
40
+ Forces CLI mode:
41
+
42
+ ```
43
+ simplecov-mcp --cli
44
+ # or
45
+ COVERAGE_MCP_CLI=1 simplecov-mcp
46
+ ``;
47
+
48
+ Example output:
49
+
50
+ ```
51
+ ┌───────────────────────────┬──────────┬──────────┬────────┐
52
+ │ File │ % │ Covered │ Total │
53
+ ├───────────────────────────┼──────────┼──────────┼────────┤
54
+ │ lib/models/user.rb │ 92.31 │ 12 │ 13 │
55
+ │ lib/services/auth.rb │ 100.00 │ 8 │ 8 │
56
+ │ spec/user_spec.rb │ 85.71 │ 12 │ 14 │
57
+ └───────────────────────────┴──────────┴──────────┴────────┘
58
+ ```
59
+
60
+ Files are sorted by percentage (ascending), then by path.
61
+
62
+ ### MCP Server Mode
63
+
64
+ When stdin has data (e.g., from an MCP client), the program runs as an MCP server over stdio.
65
+
66
+ Available tools:
67
+
68
+ - `coverage_raw(path, root=".")`
69
+ - `coverage_summary(path, root=".")`
70
+ - `uncovered_lines(path, root=".")`
71
+ - `coverage_detailed(path, root=".")`
72
+ - `all_files_coverage(root=".")`
73
+
74
+ Example (manual):
75
+
76
+ ```
77
+ echo '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"coverage_summary","arguments":{"path":"lib/foo.rb"}}}' | simplecov-mcp
78
+ ```
79
+
80
+ ### Notes
81
+
82
+ - Library entrypoint: `require "simplecov/mcp"` or `require "simplecov_mcp"`
83
+ - Programmatic run: `Simplecov::Mcp.run(ARGV)`
84
+ - Logs basic diagnostics to `~/coverage_mcp.log`.
85
+
86
+ ## Development
87
+
88
+ Standard Ruby gem structure. After cloning:
89
+
90
+ ```
91
+ bundle install
92
+ ruby -Ilib exe/simplecov-mcp --cli
93
+ ```
94
+
95
+ ## License
96
+
97
+ MIT
data/exe/simplecov-mcp ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "simplecov_mcp"
5
+
6
+ Simplecov::Mcp.run(ARGV)
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Simplecov
4
+ module Mcp
5
+ class BaseTool < ::MCP::Tool
6
+ INPUT_SCHEMA = {
7
+ type: "object",
8
+ properties: {
9
+ path: { type: "string", description: "Absolute or project-relative file path" },
10
+ root: { type: "string", description: "Project root for resolution", default: "." }
11
+ },
12
+ required: ["path"]
13
+ }
14
+ def self.input_schema_def = INPUT_SCHEMA
15
+ end
16
+ end
17
+ end
18
+
@@ -0,0 +1,98 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Simplecov
4
+ module Mcp
5
+ class CoverageCLI
6
+ def initialize
7
+ @root = "."
8
+ end
9
+
10
+ def run(argv)
11
+ if force_cli?(argv)
12
+ show_default_report
13
+ else
14
+ run_mcp_server
15
+ end
16
+ rescue => e
17
+ CovUtil.log("CLI fatal error: #{e.class}: #{e.message}\n#{e.backtrace&.join("\n")}")
18
+ raise
19
+ end
20
+
21
+ private
22
+
23
+ def force_cli?(argv)
24
+ return true if ENV["COVERAGE_MCP_CLI"] == "1"
25
+ return true if argv.include?("--cli") || argv.include?("--report")
26
+ # If interactive TTY, prefer CLI; else (e.g., pipes), run MCP.
27
+ return STDIN.tty?
28
+ end
29
+
30
+ def show_default_report
31
+ model = CoverageModel.new(root: @root)
32
+ file_summaries = model.all_files(sort_order: :ascending).map do |row|
33
+ row.dup.tap do |h|
34
+ h[:file] = Pathname.new(h[:file]).relative_path_from(Pathname.new(Dir.pwd)).to_s
35
+ end
36
+ end
37
+
38
+ # Format as table with box-style borders
39
+ max_file_length = file_summaries.map { |f| f[:file].length }.max.to_i
40
+ max_file_length = [max_file_length, "File".length].max
41
+
42
+ # Calculate maximum numeric values for proper column widths
43
+ max_covered = file_summaries.map { |f| f[:covered].to_s.length }.max
44
+ max_total = file_summaries.map { |f| f[:total].to_s.length }.max
45
+
46
+ # Define column widths
47
+ file_width = max_file_length + 2 # Extra padding
48
+ pct_width = 8
49
+ covered_width = [max_covered, "Covered".length].max + 2
50
+ total_width = [max_total, "Total".length].max + 2
51
+
52
+ # Horizontal line for each column span
53
+ h_line = ->(col_width) { '─' * (col_width + 2) }
54
+
55
+ # Border line lambda
56
+ border_line = ->(left, middle, right) {
57
+ left + h_line.(file_width) +
58
+ middle + h_line.(pct_width) +
59
+ middle + h_line.(covered_width) +
60
+ middle + h_line.(total_width) +
61
+ right
62
+ }
63
+
64
+ # Top border
65
+ puts border_line.call("┌", "┬", "┐")
66
+
67
+ # Header row
68
+ printf "│ %-#{file_width}s │ %#{pct_width}s │ %#{covered_width}s │ %#{total_width}s │\n",
69
+ "File", " %", "Covered", "Total"
70
+
71
+ # Header separator
72
+ puts border_line.call("├", "┼", "┤")
73
+
74
+ # Data rows
75
+ file_summaries.each do |file_data|
76
+ printf "│ %-#{file_width}s │ %#{pct_width - 1}.2f%% │ %#{covered_width}d │ %#{total_width}d │\n",
77
+ file_data[:file],
78
+ file_data[:percentage],
79
+ file_data[:covered],
80
+ file_data[:total]
81
+ end
82
+
83
+ # Bottom border
84
+ puts border_line.call("└", "┴", "┘")
85
+ end
86
+
87
+ def run_mcp_server
88
+ server = ::MCP::Server.new(
89
+ name: "ruby_coverage_server",
90
+ version: Simplecov::Mcp::VERSION,
91
+ tools: [CoverageRaw, CoverageSummary, UncoveredLines, CoverageDetailed, AllFilesCoverage]
92
+ )
93
+ ::MCP::Server::Transports::StdioTransport.new(server).open
94
+ end
95
+ end
96
+ end
97
+ end
98
+
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Simplecov
4
+ module Mcp
5
+ class CoverageModel
6
+ def initialize(root: ".")
7
+ @root = File.absolute_path(root || ".")
8
+ @cov = CovUtil.load_latest_coverage(@root)
9
+ end
10
+
11
+ # Returns { file: <abs>, lines: [hits|nil,...] }
12
+ def raw_for(path)
13
+ abs, arr = resolve(path)
14
+ { file: abs, lines: arr }
15
+ end
16
+
17
+ # Returns { file: <abs>, summary: {"covered"=>, "total"=>, "pct"=>} }
18
+ def summary_for(path)
19
+ abs, arr = resolve(path)
20
+ { file: abs, summary: CovUtil.summary(arr) }
21
+ end
22
+
23
+ # Returns { file: <abs>, uncovered: [line,...], summary: {...} }
24
+ def uncovered_for(path)
25
+ abs, arr = resolve(path)
26
+ { file: abs, uncovered: CovUtil.uncovered(arr), summary: CovUtil.summary(arr) }
27
+ end
28
+
29
+ # Returns { file: <abs>, lines: [{line:,hits:,covered:},...], summary: {...} }
30
+ def detailed_for(path)
31
+ abs, arr = resolve(path)
32
+ { file: abs, lines: CovUtil.detailed(arr), summary: CovUtil.summary(arr) }
33
+ end
34
+
35
+ # Returns [ { file:, covered:, total:, percentage: }, ... ]
36
+ def all_files(sort_order: :ascending)
37
+ rows = @cov.map do |abs_path, data|
38
+ next unless data["lines"].is_a?(Array)
39
+ s = CovUtil.summary(data["lines"])
40
+ { file: abs_path, covered: s["covered"], total: s["total"], percentage: s["pct"] }
41
+ end.compact
42
+
43
+ rows.sort! do |a, b|
44
+ pct_cmp = (sort_order.to_s == "descending") ? (b[:percentage] <=> a[:percentage]) : (a[:percentage] <=> b[:percentage])
45
+ pct_cmp == 0 ? (a[:file] <=> b[:file]) : pct_cmp
46
+ end
47
+ rows
48
+ end
49
+
50
+ private
51
+
52
+ def resolve(path)
53
+ abs = File.absolute_path(path, @root)
54
+ [abs, CovUtil.lookup_lines(@cov, abs)]
55
+ end
56
+ end
57
+ end
58
+ end
59
+
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Simplecov
4
+ module Mcp
5
+ class AllFilesCoverage < ::MCP::Tool
6
+ description "Return coverage percentage for all files in the project"
7
+ input_schema(
8
+ type: "object",
9
+ properties: {
10
+ root: { type: "string", description: "Project root for resolution", default: "." },
11
+ sort_order: { type: "string", description: "Sort order for coverage percentage: ascending or descending", default: "ascending", enum: ["ascending", "descending"] }
12
+ }
13
+ )
14
+ class << self
15
+ def call(root: ".", sort_order: "ascending", server_context:)
16
+ model = CoverageModel.new(root: root)
17
+ files = model.all_files(sort_order: sort_order)
18
+ ::MCP::Tool::Response.new([{ type: "json", json: { files: files } }],
19
+ meta: { mimeType: "application/json" })
20
+ rescue => e
21
+ CovUtil.log("AllFilesCoverage error: #{e.class}: #{e.message}")
22
+ ::MCP::Tool::Response.new([{ type: "text", text: "Error: #{e.class}: #{e.message}" }])
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Simplecov
4
+ module Mcp
5
+ class CoverageDetailed < BaseTool
6
+ description "Verbose per-line objects [{line,hits,covered}] (token-heavy)"
7
+ input_schema(**input_schema_def)
8
+ class << self
9
+ def call(path:, root: ".", server_context:)
10
+ model = CoverageModel.new(root: root)
11
+ data = model.detailed_for(path)
12
+ ::MCP::Tool::Response.new([{ type: "json", json: data }],
13
+ meta: { mimeType: "application/json" })
14
+ rescue => e
15
+ CovUtil.log("CoverageDetailed error: #{e.class}: #{e.message}")
16
+ ::MCP::Tool::Response.new([{ type: "text", text: "Error: #{e.class}: #{e.message}" }])
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Simplecov
4
+ module Mcp
5
+ class CoverageRaw < BaseTool
6
+ description "Return the original SimpleCov 'lines' array for a file"
7
+ input_schema(**input_schema_def)
8
+ class << self
9
+ def call(path:, root: ".", server_context:)
10
+ model = CoverageModel.new(root: root)
11
+ data = model.raw_for(path)
12
+ ::MCP::Tool::Response.new([{ type: "json", json: data }],
13
+ meta: { mimeType: "application/json" })
14
+ rescue => e
15
+ CovUtil.log("CoverageRaw error: #{e.class}: #{e.message}")
16
+ ::MCP::Tool::Response.new([{ type: "text", text: "Error: #{e.class}: #{e.message}" }])
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Simplecov
4
+ module Mcp
5
+ class CoverageSummary < BaseTool
6
+ description "Return {covered,total,pct} for a file"
7
+ input_schema(**input_schema_def)
8
+ class << self
9
+ def call(path:, root: ".", server_context:)
10
+ model = CoverageModel.new(root: root)
11
+ data = model.summary_for(path)
12
+ ::MCP::Tool::Response.new([{ type: "json", json: data }],
13
+ meta: { mimeType: "application/json" })
14
+ rescue => e
15
+ CovUtil.log("CoverageSummary error: #{e.class}: #{e.message}")
16
+ ::MCP::Tool::Response.new([{ type: "text", text: "Error: #{e.class}: #{e.message}" }])
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Simplecov
4
+ module Mcp
5
+ class UncoveredLines < BaseTool
6
+ description "Return only uncovered executable line numbers plus a summary"
7
+ input_schema(**input_schema_def)
8
+ class << self
9
+ def call(path:, root: ".", server_context:)
10
+ model = CoverageModel.new(root: root)
11
+ data = model.uncovered_for(path)
12
+ ::MCP::Tool::Response.new([{ type: "json", json: data }],
13
+ meta: { mimeType: "application/json" })
14
+ rescue => e
15
+ CovUtil.log("UncoveredLines error: #{e.class}: #{e.message}")
16
+ ::MCP::Tool::Response.new([{ type: "text", text: "Error: #{e.class}: #{e.message}" }])
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Simplecov
4
+ module Mcp
5
+ RESULTSET_CANDIDATES = [
6
+ ".resultset.json",
7
+ "coverage/.resultset.json",
8
+ "tmp/.resultset.json"
9
+ ].freeze
10
+
11
+ module CovUtil
12
+ module_function
13
+
14
+ def log(msg)
15
+ path = File.expand_path("~/coverage_mcp.log")
16
+ File.open(path, "a") { |f| f.puts "[#{Time.now.iso8601}] #{msg}" }
17
+ rescue StandardError
18
+ # ignore logging failures
19
+ end
20
+
21
+ def find_resultset(root)
22
+ if (env = ENV["SIMPLECOV_RESULTSET"]) && !env.empty?
23
+ path = File.absolute_path(env, root)
24
+ return path if File.file?(path)
25
+ end
26
+ RESULTSET_CANDIDATES
27
+ .map { |p| File.absolute_path(p, root) }
28
+ .find { |p| File.file?(p) } or
29
+ raise "Could not find .resultset.json under #{root.inspect}; run tests or set SIMPLECOV_RESULTSET"
30
+ end
31
+
32
+ # returns { abs_path => {"lines" => [hits|nil,...]} }
33
+ def load_latest_coverage(root)
34
+ rs = find_resultset(root)
35
+ raw = JSON.parse(File.read(rs))
36
+ _suite, data = raw.max_by { |_k, v| (v["timestamp"] || v["created_at"] || 0).to_i }
37
+ cov = data["coverage"] or raise "No 'coverage' key in .resultset.json"
38
+ cov.transform_keys { |k| File.absolute_path(k, root) }
39
+ end
40
+
41
+ def lookup_lines(cov, file_abs)
42
+ if (h = cov[file_abs]) && h["lines"].is_a?(Array)
43
+ return h["lines"]
44
+ end
45
+
46
+ # try without current working directory prefix
47
+ cwd = Dir.pwd
48
+ without = file_abs.sub(/\A#{Regexp.escape(cwd)}\//, "")
49
+ if (h = cov[without]) && h["lines"].is_a?(Array)
50
+ return h["lines"]
51
+ end
52
+
53
+ # fallback: basename match
54
+ base = File.basename(file_abs)
55
+ kv = cov.find { |k, v| File.basename(k) == base && v["lines"].is_a?(Array) }
56
+ kv and return kv[1]["lines"]
57
+
58
+ raise "No coverage entry found for #{file_abs}"
59
+ end
60
+
61
+ def summary(arr)
62
+ total = 0
63
+ covered = 0
64
+ arr.each do |hits|
65
+ next if hits.nil?
66
+ total += 1
67
+ covered += 1 if hits.to_i > 0
68
+ end
69
+ pct = total.zero? ? 100.0 : ((covered.to_f * 100.0 / total) * 100).round / 100.0
70
+ { "covered" => covered, "total" => total, "pct" => pct }
71
+ end
72
+
73
+ def uncovered(arr)
74
+ out = []
75
+ arr.each_with_index do |hits, i|
76
+ next if hits.nil?
77
+ out << (i + 1) if hits.to_i.zero?
78
+ end
79
+ out
80
+ end
81
+
82
+ def detailed(arr)
83
+ rows = []
84
+ arr.each_with_index do |hits, i|
85
+ next if hits.nil?
86
+ h = hits.to_i
87
+ rows << { line: i + 1, hits: h, covered: h.positive? }
88
+ end
89
+ rows
90
+ end
91
+ end
92
+ end
93
+ end
94
+
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Simplecov
4
+ module Mcp
5
+ VERSION = "0.1.0"
6
+ end
7
+ end
8
+
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "time"
5
+ require "pathname"
6
+ require "mcp"
7
+ require "mcp/server/transports/stdio_transport"
8
+ require "awesome_print"
9
+
10
+ require_relative "mcp/version"
11
+ require_relative "mcp/util"
12
+ require_relative "mcp/model"
13
+ require_relative "mcp/base_tool"
14
+ require_relative "mcp/tools/coverage_raw"
15
+ require_relative "mcp/tools/coverage_summary"
16
+ require_relative "mcp/tools/uncovered_lines"
17
+ require_relative "mcp/tools/coverage_detailed"
18
+ require_relative "mcp/tools/all_files_coverage"
19
+ require_relative "mcp/cli"
20
+
21
+ module Simplecov
22
+ module Mcp
23
+ def self.run(argv)
24
+ CoverageCLI.new.run(argv)
25
+ end
26
+ end
27
+ end
28
+
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Convenience single-level require path
4
+ require "simplecov/mcp"
5
+
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ RSpec.describe Simplecov::Mcp::CoverageModel do
6
+ let(:root) { (FIXTURES / "project1").to_s }
7
+ subject(:model) { described_class.new(root: root) }
8
+
9
+ describe "raw_for" do
10
+ it "returns absolute file and lines array" do
11
+ data = model.raw_for("lib/foo.rb")
12
+ expect(data[:file]).to eq(File.expand_path("lib/foo.rb", root))
13
+ expect(data[:lines]).to eq([1, 0, nil, 2])
14
+ end
15
+ end
16
+
17
+ describe "summary_for" do
18
+ it "computes covered/total/pct" do
19
+ data = model.summary_for("lib/foo.rb")
20
+ expect(data[:summary]["total"]).to eq(3)
21
+ expect(data[:summary]["covered"]).to eq(2)
22
+ expect(data[:summary]["pct"]).to be_within(0.01).of(66.67)
23
+ end
24
+ end
25
+
26
+ describe "uncovered_for" do
27
+ it "lists uncovered executable line numbers" do
28
+ data = model.uncovered_for("lib/foo.rb")
29
+ expect(data[:uncovered]).to eq([2])
30
+ expect(data[:summary]["total"]).to eq(3)
31
+ end
32
+ end
33
+
34
+ describe "detailed_for" do
35
+ it "returns per-line details for non-nil lines" do
36
+ data = model.detailed_for("lib/foo.rb")
37
+ expect(data[:lines]).to eq([
38
+ { line: 1, hits: 1, covered: true },
39
+ { line: 2, hits: 0, covered: false },
40
+ { line: 4, hits: 2, covered: true }
41
+ ])
42
+ end
43
+ end
44
+
45
+ describe "all_files" do
46
+ it "sorts ascending by percentage then by file path" do
47
+ files = model.all_files(sort_order: :ascending)
48
+ expect(files.first[:file]).to eq(File.expand_path("lib/bar.rb", root))
49
+ expect(files.first[:percentage]).to be_within(0.01).of(33.33)
50
+ expect(files.last[:file]).to eq(File.expand_path("lib/foo.rb", root))
51
+ end
52
+ end
53
+ end
54
+
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ ENV.delete("SIMPLECOV_RESULTSET")
4
+
5
+ require "rspec"
6
+ require "pathname"
7
+ require "json"
8
+
9
+ require "simplecov/mcp"
10
+
11
+ FIXTURES = Pathname.new(File.expand_path("fixtures", __dir__))
12
+
13
+ RSpec.configure do |config|
14
+ config.example_status_persistence_file_path = ".rspec_status"
15
+ config.disable_monkey_patching!
16
+ config.order = :random
17
+ Kernel.srand config.seed
18
+ end
19
+
metadata ADDED
@@ -0,0 +1,106 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: simplecov-mcp
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Keith R. Bennett
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: mcp
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '0.2'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '0.2'
26
+ - !ruby/object:Gem::Dependency
27
+ name: awesome_print
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 1.9.2
33
+ - - "<"
34
+ - !ruby/object:Gem::Version
35
+ version: '2'
36
+ type: :runtime
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: 1.9.2
43
+ - - "<"
44
+ - !ruby/object:Gem::Version
45
+ version: '2'
46
+ - !ruby/object:Gem::Dependency
47
+ name: rspec
48
+ requirement: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - "~>"
51
+ - !ruby/object:Gem::Version
52
+ version: '3.0'
53
+ type: :development
54
+ prerelease: false
55
+ version_requirements: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: '3.0'
60
+ description: Provides an MCP (Model Context Protocol) server and a CLI to inspect
61
+ SimpleCov coverage, including per-file summaries and uncovered lines.
62
+ email:
63
+ - keithrbennett@gmail.com
64
+ executables:
65
+ - simplecov-mcp
66
+ extensions: []
67
+ extra_rdoc_files: []
68
+ files:
69
+ - README.md
70
+ - exe/simplecov-mcp
71
+ - lib/simplecov/mcp.rb
72
+ - lib/simplecov/mcp/base_tool.rb
73
+ - lib/simplecov/mcp/cli.rb
74
+ - lib/simplecov/mcp/model.rb
75
+ - lib/simplecov/mcp/tools/all_files_coverage.rb
76
+ - lib/simplecov/mcp/tools/coverage_detailed.rb
77
+ - lib/simplecov/mcp/tools/coverage_raw.rb
78
+ - lib/simplecov/mcp/tools/coverage_summary.rb
79
+ - lib/simplecov/mcp/tools/uncovered_lines.rb
80
+ - lib/simplecov/mcp/util.rb
81
+ - lib/simplecov/mcp/version.rb
82
+ - lib/simplecov_mcp.rb
83
+ - spec/simplecov_mcp_model_spec.rb
84
+ - spec/spec_helper.rb
85
+ homepage: https://github.com/keithrbennett/simplecov-mcp
86
+ licenses:
87
+ - MIT
88
+ metadata: {}
89
+ rdoc_options: []
90
+ require_paths:
91
+ - lib
92
+ required_ruby_version: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '3.2'
97
+ required_rubygems_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ requirements: []
103
+ rubygems_version: 3.7.1
104
+ specification_version: 4
105
+ summary: MCP server + CLI for SimpleCov coverage data
106
+ test_files: []