simplecov-mcp 0.1.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.
- checksums.yaml +4 -4
- data/README.md +379 -32
- data/exe/simplecov-mcp +19 -2
- data/lib/simple_cov/mcp.rb +9 -0
- data/lib/simple_cov_mcp/base_tool.rb +54 -0
- data/lib/simple_cov_mcp/cli.rb +390 -0
- data/lib/simple_cov_mcp/error_handler.rb +131 -0
- data/lib/simple_cov_mcp/error_handler_factory.rb +38 -0
- data/lib/simple_cov_mcp/errors.rb +176 -0
- data/lib/simple_cov_mcp/mcp_server.rb +30 -0
- data/lib/simple_cov_mcp/model.rb +104 -0
- data/lib/simple_cov_mcp/staleness_checker.rb +125 -0
- data/lib/simple_cov_mcp/tools/all_files_coverage_tool.rb +65 -0
- data/lib/simple_cov_mcp/tools/coverage_detailed_tool.rb +29 -0
- data/lib/simple_cov_mcp/tools/coverage_raw_tool.rb +29 -0
- data/lib/simple_cov_mcp/tools/coverage_summary_tool.rb +29 -0
- data/lib/simple_cov_mcp/tools/coverage_table_tool.rb +61 -0
- data/lib/simple_cov_mcp/tools/help_tool.rb +136 -0
- data/lib/simple_cov_mcp/tools/uncovered_lines_tool.rb +29 -0
- data/lib/simple_cov_mcp/tools/version_tool.rb +31 -0
- data/lib/simple_cov_mcp/util.rb +122 -0
- data/lib/simple_cov_mcp/version.rb +5 -0
- data/lib/simple_cov_mcp.rb +102 -0
- data/lib/simplecov_mcp.rb +2 -3
- data/spec/all_files_coverage_tool_spec.rb +46 -0
- data/spec/base_tool_spec.rb +58 -0
- data/spec/cli_error_spec.rb +103 -0
- data/spec/cli_source_spec.rb +37 -0
- data/spec/cli_spec.rb +72 -0
- data/spec/cli_table_spec.rb +28 -0
- data/spec/cli_usage_spec.rb +58 -0
- data/spec/coverage_table_tool_spec.rb +64 -0
- data/spec/error_handler_spec.rb +72 -0
- data/spec/errors_stale_spec.rb +49 -0
- data/spec/fixtures/project1/lib/bar.rb +4 -0
- data/spec/fixtures/project1/lib/foo.rb +5 -0
- data/spec/help_tool_spec.rb +47 -0
- data/spec/legacy_shim_spec.rb +13 -0
- data/spec/mcp_server_spec.rb +78 -0
- data/spec/model_staleness_spec.rb +49 -0
- data/spec/simplecov_mcp_model_spec.rb +51 -32
- data/spec/spec_helper.rb +37 -7
- data/spec/staleness_more_spec.rb +39 -0
- data/spec/util_spec.rb +78 -0
- data/spec/version_spec.rb +10 -0
- metadata +59 -17
- data/lib/simplecov/mcp/base_tool.rb +0 -18
- data/lib/simplecov/mcp/cli.rb +0 -98
- data/lib/simplecov/mcp/model.rb +0 -59
- data/lib/simplecov/mcp/tools/all_files_coverage.rb +0 -28
- data/lib/simplecov/mcp/tools/coverage_detailed.rb +0 -22
- data/lib/simplecov/mcp/tools/coverage_raw.rb +0 -22
- data/lib/simplecov/mcp/tools/coverage_summary.rb +0 -22
- data/lib/simplecov/mcp/tools/uncovered_lines.rb +0 -22
- data/lib/simplecov/mcp/util.rb +0 -94
- data/lib/simplecov/mcp/version.rb +0 -8
- data/lib/simplecov/mcp.rb +0 -28
data/spec/spec_helper.rb
CHANGED
@@ -1,19 +1,49 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
# Enable SimpleCov for this project (coverage output in ./coverage)
|
4
|
+
begin
|
5
|
+
require 'simplecov'
|
6
|
+
SimpleCov.start do
|
7
|
+
enable_coverage :branch if SimpleCov.respond_to?(:enable_coverage)
|
8
|
+
add_filter %r{^/spec/}
|
9
|
+
track_files 'lib/**/*.rb'
|
10
|
+
end
|
11
|
+
rescue LoadError
|
12
|
+
warn 'SimpleCov not available; skipping coverage'
|
13
|
+
end
|
14
|
+
|
15
|
+
ENV.delete('SIMPLECOV_RESULTSET')
|
4
16
|
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
17
|
+
require 'rspec'
|
18
|
+
require 'pathname'
|
19
|
+
require 'json'
|
8
20
|
|
9
|
-
require
|
21
|
+
require 'simple_cov_mcp'
|
10
22
|
|
11
|
-
FIXTURES = Pathname.new(File.expand_path(
|
23
|
+
FIXTURES = Pathname.new(File.expand_path('fixtures', __dir__))
|
12
24
|
|
13
25
|
RSpec.configure do |config|
|
14
|
-
config.example_status_persistence_file_path =
|
26
|
+
config.example_status_persistence_file_path = '.rspec_status'
|
15
27
|
config.disable_monkey_patching!
|
16
28
|
config.order = :random
|
17
29
|
Kernel.srand config.seed
|
18
30
|
end
|
19
31
|
|
32
|
+
# Shared test helpers
|
33
|
+
module TestIOHelpers
|
34
|
+
# Suppress stdout/stderr within the given block, yielding the StringIOs
|
35
|
+
def silence_output
|
36
|
+
original_stdout = $stdout
|
37
|
+
original_stderr = $stderr
|
38
|
+
$stdout = StringIO.new
|
39
|
+
$stderr = StringIO.new
|
40
|
+
yield $stdout, $stderr
|
41
|
+
ensure
|
42
|
+
$stdout = original_stdout
|
43
|
+
$stderr = original_stderr
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
RSpec.configure do |config|
|
48
|
+
config.include TestIOHelpers
|
49
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe 'Additional staleness cases' do
|
6
|
+
let(:root) { (FIXTURES / 'project1').to_s }
|
7
|
+
|
8
|
+
describe SimpleCovMcp::CoverageModel do
|
9
|
+
it 'raises file-level stale when source and coverage lengths differ' do
|
10
|
+
# Ensure time is not the triggering factor
|
11
|
+
allow(SimpleCovMcp::CovUtil).to receive(:latest_timestamp).and_return(Time.now.to_i)
|
12
|
+
model = SimpleCovMcp::CoverageModel.new(root: root, resultset: 'coverage', staleness: 'error')
|
13
|
+
# bar.rb has 3 coverage entries but 4 source lines in fixtures
|
14
|
+
expect {
|
15
|
+
model.summary_for('lib/bar.rb')
|
16
|
+
}.to raise_error(SimpleCovMcp::CoverageDataStaleError, /stale/i)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe SimpleCovMcp::StalenessChecker do
|
21
|
+
it 'flags deleted files present only in coverage' do
|
22
|
+
checker = described_class.new(root: root, resultset: 'coverage', mode: 'error')
|
23
|
+
coverage_map = {
|
24
|
+
File.join(root, 'lib', 'does_not_exist_anymore.rb') => { 'lines' => [1] }
|
25
|
+
}
|
26
|
+
expect {
|
27
|
+
checker.check_project!(coverage_map)
|
28
|
+
}.to raise_error(SimpleCovMcp::CoverageDataProjectStaleError)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'does not raise for empty tracked_globs when nothing else is stale' do
|
32
|
+
allow(SimpleCovMcp::CovUtil).to receive(:latest_timestamp).and_return(Time.now.to_i)
|
33
|
+
checker = described_class.new(root: root, resultset: 'coverage', mode: 'error', tracked_globs: [])
|
34
|
+
expect {
|
35
|
+
checker.check_project!({})
|
36
|
+
}.not_to raise_error
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/spec/util_spec.rb
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe SimpleCovMcp::CovUtil do
|
6
|
+
let(:root) { (FIXTURES / 'project1').to_s }
|
7
|
+
let(:resultset_file) { File.join(root, 'coverage', '.resultset.json') }
|
8
|
+
|
9
|
+
it 'latest_timestamp returns integer from fixture' do
|
10
|
+
ts = described_class.latest_timestamp(root, resultset: 'coverage')
|
11
|
+
expect(ts).to be_a(Integer)
|
12
|
+
expect(ts).to eq(1_720_000_000)
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'find_resultset honors SIMPLECOV_RESULTSET file path' do
|
16
|
+
begin
|
17
|
+
ENV['SIMPLECOV_RESULTSET'] = resultset_file
|
18
|
+
path = described_class.find_resultset(root)
|
19
|
+
expect(path).to eq(File.absolute_path(resultset_file, root))
|
20
|
+
ensure
|
21
|
+
ENV.delete('SIMPLECOV_RESULTSET')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'lookup_lines supports cwd-stripping and basename fallbacks' do
|
26
|
+
lines = [1, 0]
|
27
|
+
|
28
|
+
# Exact key
|
29
|
+
cov = { '/abs/path/foo.rb' => { 'lines' => lines } }
|
30
|
+
expect(described_class.lookup_lines(cov, '/abs/path/foo.rb')).to eq(lines)
|
31
|
+
|
32
|
+
# CWD strip fallback
|
33
|
+
begin
|
34
|
+
allow(Dir).to receive(:pwd).and_return('/cwd')
|
35
|
+
cov = { 'sub/foo.rb' => { 'lines' => lines } }
|
36
|
+
expect(described_class.lookup_lines(cov, '/cwd/sub/foo.rb')).to eq(lines)
|
37
|
+
ensure
|
38
|
+
# no-op
|
39
|
+
end
|
40
|
+
|
41
|
+
# Basename fallback
|
42
|
+
cov = { '/some/where/else/foo.rb' => { 'lines' => lines } }
|
43
|
+
expect(described_class.lookup_lines(cov, '/another/place/foo.rb')).to eq(lines)
|
44
|
+
|
45
|
+
# Missing raises a helpful string error
|
46
|
+
cov = {}
|
47
|
+
expect {
|
48
|
+
described_class.lookup_lines(cov, '/nowhere/foo.rb')
|
49
|
+
}.to raise_error(RuntimeError, /No coverage entry found/)
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'summary handles edge cases and coercion' do
|
53
|
+
expect(described_class.summary([])).to include('pct' => 100.0, 'total' => 0, 'covered' => 0)
|
54
|
+
expect(described_class.summary([nil, nil])).to include('pct' => 100.0, 'total' => 0, 'covered' => 0)
|
55
|
+
expect(described_class.summary(['1', '0', nil])).to include('pct' => 50.0, 'total' => 2, 'covered' => 1)
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'uncovered and detailed ignore nils' do
|
59
|
+
arr = [1, 0, nil, 2]
|
60
|
+
expect(described_class.uncovered(arr)).to eq([2])
|
61
|
+
expect(described_class.detailed(arr)).to eq([
|
62
|
+
{ 'line' => 1, 'hits' => 1, 'covered' => true },
|
63
|
+
{ 'line' => 2, 'hits' => 0, 'covered' => false },
|
64
|
+
{ 'line' => 4, 'hits' => 2, 'covered' => true }
|
65
|
+
])
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'load_latest_coverage raises CoverageDataError on invalid JSON via model' do
|
69
|
+
Dir.mktmpdir do |dir|
|
70
|
+
bad = File.join(dir, '.resultset.json')
|
71
|
+
File.write(bad, '{not-json')
|
72
|
+
expect {
|
73
|
+
SimpleCovMcp::CoverageModel.new(root: root, resultset: dir)
|
74
|
+
}.to raise_error(SimpleCovMcp::CoverageDataError, /Invalid coverage data format/)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
|
5
|
+
RSpec.describe 'Version constant' do
|
6
|
+
it 'exposes a semver-like version string' do
|
7
|
+
expect(SimpleCovMcp::VERSION).to be_a(String)
|
8
|
+
expect(SimpleCovMcp::VERSION).to match(/\A\d+\.\d+\.\d+(?:[.-][0-9A-Za-z]+)?\z/)
|
9
|
+
end
|
10
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: simplecov-mcp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Keith R. Bennett
|
@@ -13,16 +13,16 @@ dependencies:
|
|
13
13
|
name: mcp
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
15
15
|
requirements:
|
16
|
-
- - "
|
16
|
+
- - ">="
|
17
17
|
- !ruby/object:Gem::Version
|
18
|
-
version: '0.
|
18
|
+
version: '0.3'
|
19
19
|
type: :runtime
|
20
20
|
prerelease: false
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
22
22
|
requirements:
|
23
|
-
- - "
|
23
|
+
- - ">="
|
24
24
|
- !ruby/object:Gem::Version
|
25
|
-
version: '0.
|
25
|
+
version: '0.3'
|
26
26
|
- !ruby/object:Gem::Dependency
|
27
27
|
name: awesome_print
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
@@ -57,6 +57,20 @@ dependencies:
|
|
57
57
|
- - "~>"
|
58
58
|
- !ruby/object:Gem::Version
|
59
59
|
version: '3.0'
|
60
|
+
- !ruby/object:Gem::Dependency
|
61
|
+
name: simplecov
|
62
|
+
requirement: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0.21'
|
67
|
+
type: :development
|
68
|
+
prerelease: false
|
69
|
+
version_requirements: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0.21'
|
60
74
|
description: Provides an MCP (Model Context Protocol) server and a CLI to inspect
|
61
75
|
SimpleCov coverage, including per-file summaries and uncovered lines.
|
62
76
|
email:
|
@@ -68,20 +82,48 @@ extra_rdoc_files: []
|
|
68
82
|
files:
|
69
83
|
- README.md
|
70
84
|
- exe/simplecov-mcp
|
71
|
-
- lib/
|
72
|
-
- lib/
|
73
|
-
- lib/
|
74
|
-
- lib/
|
75
|
-
- lib/
|
76
|
-
- lib/
|
77
|
-
- lib/
|
78
|
-
- lib/
|
79
|
-
- lib/
|
80
|
-
- lib/
|
81
|
-
- lib/
|
85
|
+
- lib/simple_cov/mcp.rb
|
86
|
+
- lib/simple_cov_mcp.rb
|
87
|
+
- lib/simple_cov_mcp/base_tool.rb
|
88
|
+
- lib/simple_cov_mcp/cli.rb
|
89
|
+
- lib/simple_cov_mcp/error_handler.rb
|
90
|
+
- lib/simple_cov_mcp/error_handler_factory.rb
|
91
|
+
- lib/simple_cov_mcp/errors.rb
|
92
|
+
- lib/simple_cov_mcp/mcp_server.rb
|
93
|
+
- lib/simple_cov_mcp/model.rb
|
94
|
+
- lib/simple_cov_mcp/staleness_checker.rb
|
95
|
+
- lib/simple_cov_mcp/tools/all_files_coverage_tool.rb
|
96
|
+
- lib/simple_cov_mcp/tools/coverage_detailed_tool.rb
|
97
|
+
- lib/simple_cov_mcp/tools/coverage_raw_tool.rb
|
98
|
+
- lib/simple_cov_mcp/tools/coverage_summary_tool.rb
|
99
|
+
- lib/simple_cov_mcp/tools/coverage_table_tool.rb
|
100
|
+
- lib/simple_cov_mcp/tools/help_tool.rb
|
101
|
+
- lib/simple_cov_mcp/tools/uncovered_lines_tool.rb
|
102
|
+
- lib/simple_cov_mcp/tools/version_tool.rb
|
103
|
+
- lib/simple_cov_mcp/util.rb
|
104
|
+
- lib/simple_cov_mcp/version.rb
|
82
105
|
- lib/simplecov_mcp.rb
|
106
|
+
- spec/all_files_coverage_tool_spec.rb
|
107
|
+
- spec/base_tool_spec.rb
|
108
|
+
- spec/cli_error_spec.rb
|
109
|
+
- spec/cli_source_spec.rb
|
110
|
+
- spec/cli_spec.rb
|
111
|
+
- spec/cli_table_spec.rb
|
112
|
+
- spec/cli_usage_spec.rb
|
113
|
+
- spec/coverage_table_tool_spec.rb
|
114
|
+
- spec/error_handler_spec.rb
|
115
|
+
- spec/errors_stale_spec.rb
|
116
|
+
- spec/fixtures/project1/lib/bar.rb
|
117
|
+
- spec/fixtures/project1/lib/foo.rb
|
118
|
+
- spec/help_tool_spec.rb
|
119
|
+
- spec/legacy_shim_spec.rb
|
120
|
+
- spec/mcp_server_spec.rb
|
121
|
+
- spec/model_staleness_spec.rb
|
83
122
|
- spec/simplecov_mcp_model_spec.rb
|
84
123
|
- spec/spec_helper.rb
|
124
|
+
- spec/staleness_more_spec.rb
|
125
|
+
- spec/util_spec.rb
|
126
|
+
- spec/version_spec.rb
|
85
127
|
homepage: https://github.com/keithrbennett/simplecov-mcp
|
86
128
|
licenses:
|
87
129
|
- MIT
|
@@ -100,7 +142,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
100
142
|
- !ruby/object:Gem::Version
|
101
143
|
version: '0'
|
102
144
|
requirements: []
|
103
|
-
rubygems_version: 3.7.
|
145
|
+
rubygems_version: 3.7.2
|
104
146
|
specification_version: 4
|
105
147
|
summary: MCP server + CLI for SimpleCov coverage data
|
106
148
|
test_files: []
|
@@ -1,18 +0,0 @@
|
|
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
|
-
|
data/lib/simplecov/mcp/cli.rb
DELETED
@@ -1,98 +0,0 @@
|
|
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
|
-
|
data/lib/simplecov/mcp/model.rb
DELETED
@@ -1,59 +0,0 @@
|
|
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
|
-
|
@@ -1,28 +0,0 @@
|
|
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
|
-
|
@@ -1,22 +0,0 @@
|
|
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
|
-
|
@@ -1,22 +0,0 @@
|
|
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
|
-
|
@@ -1,22 +0,0 @@
|
|
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
|
-
|
@@ -1,22 +0,0 @@
|
|
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
|
-
|