simplecov-mcp 0.3.0 → 1.0.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.
Files changed (152) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +21 -0
  3. data/README.md +173 -356
  4. data/docs/ADVANCED_USAGE.md +967 -0
  5. data/docs/ARCHITECTURE.md +79 -0
  6. data/docs/BRANCH_ONLY_COVERAGE.md +81 -0
  7. data/docs/CLI_USAGE.md +637 -0
  8. data/docs/DEVELOPMENT.md +82 -0
  9. data/docs/ERROR_HANDLING.md +93 -0
  10. data/docs/EXAMPLES.md +430 -0
  11. data/docs/INSTALLATION.md +352 -0
  12. data/docs/LIBRARY_API.md +635 -0
  13. data/docs/MCP_INTEGRATION.md +488 -0
  14. data/docs/TROUBLESHOOTING.md +276 -0
  15. data/docs/arch-decisions/001-x-arch-decision.md +93 -0
  16. data/docs/arch-decisions/002-x-arch-decision.md +157 -0
  17. data/docs/arch-decisions/003-x-arch-decision.md +163 -0
  18. data/docs/arch-decisions/004-x-arch-decision.md +199 -0
  19. data/docs/arch-decisions/005-x-arch-decision.md +187 -0
  20. data/docs/arch-decisions/README.md +60 -0
  21. data/docs/presentations/simplecov-mcp-presentation.md +249 -0
  22. data/exe/simplecov-mcp +4 -4
  23. data/lib/simplecov_mcp/app_context.rb +26 -0
  24. data/lib/simplecov_mcp/base_tool.rb +74 -0
  25. data/lib/simplecov_mcp/cli.rb +234 -0
  26. data/lib/simplecov_mcp/cli_config.rb +56 -0
  27. data/lib/simplecov_mcp/commands/base_command.rb +78 -0
  28. data/lib/simplecov_mcp/commands/command_factory.rb +39 -0
  29. data/lib/simplecov_mcp/commands/detailed_command.rb +24 -0
  30. data/lib/simplecov_mcp/commands/list_command.rb +13 -0
  31. data/lib/simplecov_mcp/commands/raw_command.rb +22 -0
  32. data/lib/simplecov_mcp/commands/summary_command.rb +24 -0
  33. data/lib/simplecov_mcp/commands/uncovered_command.rb +26 -0
  34. data/lib/simplecov_mcp/commands/version_command.rb +18 -0
  35. data/lib/simplecov_mcp/constants.rb +22 -0
  36. data/lib/simplecov_mcp/error_handler.rb +124 -0
  37. data/lib/simplecov_mcp/error_handler_factory.rb +31 -0
  38. data/lib/simplecov_mcp/errors.rb +179 -0
  39. data/lib/simplecov_mcp/formatters/source_formatter.rb +148 -0
  40. data/lib/simplecov_mcp/mcp_server.rb +40 -0
  41. data/lib/simplecov_mcp/mode_detector.rb +55 -0
  42. data/lib/simplecov_mcp/model.rb +300 -0
  43. data/lib/simplecov_mcp/option_normalizers.rb +92 -0
  44. data/lib/simplecov_mcp/option_parser_builder.rb +134 -0
  45. data/lib/simplecov_mcp/option_parsers/env_options_parser.rb +50 -0
  46. data/lib/simplecov_mcp/option_parsers/error_helper.rb +109 -0
  47. data/lib/simplecov_mcp/path_relativizer.rb +61 -0
  48. data/lib/simplecov_mcp/presenters/base_coverage_presenter.rb +44 -0
  49. data/lib/simplecov_mcp/presenters/coverage_detailed_presenter.rb +16 -0
  50. data/lib/simplecov_mcp/presenters/coverage_raw_presenter.rb +16 -0
  51. data/lib/simplecov_mcp/presenters/coverage_summary_presenter.rb +16 -0
  52. data/lib/simplecov_mcp/presenters/coverage_uncovered_presenter.rb +16 -0
  53. data/lib/simplecov_mcp/presenters/project_coverage_presenter.rb +52 -0
  54. data/lib/simplecov_mcp/resolvers/coverage_line_resolver.rb +126 -0
  55. data/lib/simplecov_mcp/resolvers/resolver_factory.rb +28 -0
  56. data/lib/simplecov_mcp/resolvers/resultset_path_resolver.rb +78 -0
  57. data/lib/simplecov_mcp/resultset_loader.rb +136 -0
  58. data/lib/simplecov_mcp/staleness_checker.rb +243 -0
  59. data/lib/{simple_cov_mcp → simplecov_mcp}/tools/all_files_coverage_tool.rb +31 -13
  60. data/lib/{simple_cov_mcp → simplecov_mcp}/tools/coverage_detailed_tool.rb +7 -7
  61. data/lib/{simple_cov_mcp → simplecov_mcp}/tools/coverage_raw_tool.rb +7 -7
  62. data/lib/{simple_cov_mcp → simplecov_mcp}/tools/coverage_summary_tool.rb +7 -7
  63. data/lib/simplecov_mcp/tools/coverage_table_tool.rb +90 -0
  64. data/lib/{simple_cov_mcp → simplecov_mcp}/tools/help_tool.rb +13 -4
  65. data/lib/{simple_cov_mcp → simplecov_mcp}/tools/uncovered_lines_tool.rb +7 -7
  66. data/lib/{simple_cov_mcp → simplecov_mcp}/tools/version_tool.rb +11 -3
  67. data/lib/simplecov_mcp/util.rb +82 -0
  68. data/lib/{simple_cov_mcp → simplecov_mcp}/version.rb +1 -1
  69. data/lib/simplecov_mcp.rb +144 -2
  70. data/spec/MCP_INTEGRATION_TESTS_README.md +111 -0
  71. data/spec/TIMESTAMPS.md +48 -0
  72. data/spec/all_files_coverage_tool_spec.rb +29 -25
  73. data/spec/base_tool_spec.rb +11 -10
  74. data/spec/cli/show_default_report_spec.rb +33 -0
  75. data/spec/cli_config_spec.rb +137 -0
  76. data/spec/cli_enumerated_options_spec.rb +68 -0
  77. data/spec/cli_error_spec.rb +105 -47
  78. data/spec/cli_source_spec.rb +82 -23
  79. data/spec/cli_spec.rb +140 -5
  80. data/spec/cli_success_predicate_spec.rb +141 -0
  81. data/spec/cli_table_spec.rb +1 -1
  82. data/spec/cli_usage_spec.rb +10 -26
  83. data/spec/commands/base_command_spec.rb +187 -0
  84. data/spec/commands/command_factory_spec.rb +72 -0
  85. data/spec/commands/detailed_command_spec.rb +48 -0
  86. data/spec/commands/raw_command_spec.rb +46 -0
  87. data/spec/commands/summary_command_spec.rb +47 -0
  88. data/spec/commands/uncovered_command_spec.rb +49 -0
  89. data/spec/constants_spec.rb +61 -0
  90. data/spec/coverage_table_tool_spec.rb +17 -33
  91. data/spec/error_handler_spec.rb +22 -13
  92. data/spec/error_mode_spec.rb +143 -0
  93. data/spec/errors_edge_cases_spec.rb +239 -0
  94. data/spec/errors_stale_spec.rb +2 -2
  95. data/spec/file_based_mcp_tools_spec.rb +99 -0
  96. data/spec/fixtures/project1/lib/bar.rb +0 -1
  97. data/spec/fixtures/project1/lib/foo.rb +0 -1
  98. data/spec/help_tool_spec.rb +11 -17
  99. data/spec/integration_spec.rb +845 -0
  100. data/spec/logging_fallback_spec.rb +128 -0
  101. data/spec/mcp_logging_spec.rb +44 -0
  102. data/spec/mcp_server_integration_spec.rb +23 -0
  103. data/spec/mcp_server_spec.rb +15 -4
  104. data/spec/mode_detector_spec.rb +148 -0
  105. data/spec/model_error_handling_spec.rb +210 -0
  106. data/spec/model_staleness_spec.rb +40 -10
  107. data/spec/option_normalizers_spec.rb +204 -0
  108. data/spec/option_parsers/env_options_parser_spec.rb +233 -0
  109. data/spec/option_parsers/error_helper_spec.rb +222 -0
  110. data/spec/path_relativizer_spec.rb +83 -0
  111. data/spec/presenters/coverage_detailed_presenter_spec.rb +19 -0
  112. data/spec/presenters/coverage_raw_presenter_spec.rb +15 -0
  113. data/spec/presenters/coverage_summary_presenter_spec.rb +15 -0
  114. data/spec/presenters/coverage_uncovered_presenter_spec.rb +16 -0
  115. data/spec/presenters/project_coverage_presenter_spec.rb +86 -0
  116. data/spec/resolvers/coverage_line_resolver_spec.rb +57 -0
  117. data/spec/resolvers/resolver_factory_spec.rb +61 -0
  118. data/spec/resolvers/resultset_path_resolver_spec.rb +55 -0
  119. data/spec/resultset_loader_spec.rb +167 -0
  120. data/spec/shared_examples/README.md +115 -0
  121. data/spec/shared_examples/coverage_presenter_examples.rb +66 -0
  122. data/spec/shared_examples/file_based_mcp_tools.rb +174 -0
  123. data/spec/shared_examples/mcp_tool_text_json_response.rb +16 -0
  124. data/spec/simple_cov_mcp_module_spec.rb +16 -0
  125. data/spec/simplecov_mcp_model_spec.rb +340 -9
  126. data/spec/simplecov_mcp_opts_spec.rb +182 -0
  127. data/spec/spec_helper.rb +147 -4
  128. data/spec/staleness_checker_spec.rb +373 -0
  129. data/spec/staleness_more_spec.rb +16 -13
  130. data/spec/support/mcp_runner.rb +64 -0
  131. data/spec/tools_error_handling_spec.rb +144 -0
  132. data/spec/util_spec.rb +109 -34
  133. data/spec/version_spec.rb +117 -9
  134. data/spec/version_tool_spec.rb +131 -10
  135. metadata +120 -63
  136. data/lib/simple_cov/mcp.rb +0 -9
  137. data/lib/simple_cov_mcp/base_tool.rb +0 -70
  138. data/lib/simple_cov_mcp/cli.rb +0 -390
  139. data/lib/simple_cov_mcp/error_handler.rb +0 -131
  140. data/lib/simple_cov_mcp/error_handler_factory.rb +0 -38
  141. data/lib/simple_cov_mcp/errors.rb +0 -176
  142. data/lib/simple_cov_mcp/mcp_server.rb +0 -30
  143. data/lib/simple_cov_mcp/model.rb +0 -104
  144. data/lib/simple_cov_mcp/staleness_checker.rb +0 -125
  145. data/lib/simple_cov_mcp/tools/coverage_table_tool.rb +0 -61
  146. data/lib/simple_cov_mcp/util.rb +0 -122
  147. data/lib/simple_cov_mcp.rb +0 -102
  148. data/spec/coverage_detailed_tool_spec.rb +0 -36
  149. data/spec/coverage_raw_tool_spec.rb +0 -32
  150. data/spec/coverage_summary_tool_spec.rb +0 -39
  151. data/spec/legacy_shim_spec.rb +0 -13
  152. data/spec/uncovered_lines_tool_spec.rb +0 -33
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require_relative 'shared_examples/file_based_mcp_tools'
5
+
6
+ # Load all the tool classes that will be tested
7
+ require 'simplecov_mcp/tools/coverage_summary_tool'
8
+ require 'simplecov_mcp/tools/coverage_raw_tool'
9
+ require 'simplecov_mcp/tools/uncovered_lines_tool'
10
+ require 'simplecov_mcp/tools/coverage_detailed_tool'
11
+
12
+ RSpec.describe 'File-based MCP Tools' do
13
+ # Test each file-based tool using the shared example with its specific configuration
14
+ FILE_BASED_TOOL_CONFIGS.each do |tool_name, config|
15
+ describe config[:tool_class] do
16
+ it_behaves_like 'a file-based MCP tool', config
17
+ end
18
+ end
19
+
20
+ # Test that all file-based tools handle the same parameters consistently
21
+ describe 'parameter consistency' do
22
+ let(:server_context) { instance_double('ServerContext').as_null_object }
23
+
24
+ before do
25
+ setup_mcp_response_stub
26
+ end
27
+
28
+ it 'all file-based tools accept the same basic parameters' do
29
+ # Test that all tools can be called with the same parameter signature
30
+ FILE_BASED_TOOL_CONFIGS.each do |tool_name, config|
31
+ model = instance_double(SimpleCovMcp::CoverageModel)
32
+ allow(SimpleCovMcp::CoverageModel).to receive(:new).and_return(model)
33
+ allow(model).to receive(config[:model_method]).and_return(config[:mock_data])
34
+ allow(model).to receive(:relativize) { |payload| payload }
35
+ allow(model).to receive(:staleness_for).and_return(false)
36
+
37
+ expect do
38
+ config[:tool_class].call(
39
+ path: 'lib/example.rb',
40
+ root: '.',
41
+ resultset: 'coverage',
42
+ server_context: server_context
43
+ )
44
+ end.not_to raise_error
45
+ end
46
+ end
47
+
48
+ it 'all file-based tools return JSON resources with consistent structure' do
49
+ FILE_BASED_TOOL_CONFIGS.each do |tool_name, config|
50
+ model = instance_double(SimpleCovMcp::CoverageModel)
51
+ allow(SimpleCovMcp::CoverageModel).to receive(:new).and_return(model)
52
+ allow(model).to receive(config[:model_method]).and_return(config[:mock_data])
53
+ allow(model).to receive(:relativize) { |payload| payload }
54
+ allow(model).to receive(:staleness_for).and_return(false)
55
+
56
+ response = config[:tool_class].call(path: 'lib/foo.rb', server_context: server_context)
57
+
58
+ # All should have the same basic MCP text structure
59
+ expect(response.payload).to be_an(Array)
60
+ expect(response.payload.first['type']).to eq('text')
61
+ expect(response.payload.first).to have_key('text')
62
+
63
+ # All should return valid JSON
64
+ expect { JSON.parse(response.payload.first['text']) }.not_to raise_error
65
+ end
66
+ end
67
+ end
68
+
69
+ # Performance/behavior comparison tests
70
+ describe 'cross-tool consistency' do
71
+ let(:server_context) { instance_double('ServerContext').as_null_object }
72
+
73
+ before do
74
+ setup_mcp_response_stub
75
+ end
76
+
77
+ it 'tools that include summary data return consistent summary format' do
78
+ summary_tools = FILE_BASED_TOOL_CONFIGS.select do |_, config|
79
+ config[:expected_keys].include?('summary')
80
+ end
81
+
82
+ summary_tools.each do |tool_name, config|
83
+ model = instance_double(SimpleCovMcp::CoverageModel)
84
+ allow(SimpleCovMcp::CoverageModel).to receive(:new).and_return(model)
85
+ allow(model).to receive(config[:model_method]).and_return(config[:mock_data])
86
+ allow(model).to receive(:relativize) { |payload| payload }
87
+ allow(model).to receive(:staleness_for).and_return(false)
88
+
89
+ response = config[:tool_class].call(path: 'lib/foo.rb', server_context: server_context)
90
+ data = JSON.parse(response.payload.first['text'])
91
+
92
+ if data.key?('summary')
93
+ expect(data['summary']).to include('covered', 'total', 'pct')
94
+ expect(data['summary']['pct']).to be_a(Numeric)
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -1,4 +1,3 @@
1
1
  puts 'bar line 1 (uncovered)'
2
2
  puts 'bar line 2 (uncovered)'
3
3
  puts 'bar line 3'
4
-
@@ -2,4 +2,3 @@ puts 'foo line 1'
2
2
  puts 'foo line 2 (uncovered)'
3
3
 
4
4
  puts 'foo line 4'
5
-
@@ -1,21 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'spec_helper'
4
- require 'simple_cov_mcp/tools/help_tool'
4
+ require 'simplecov_mcp/tools/help_tool'
5
5
 
6
6
  RSpec.describe SimpleCovMcp::Tools::HelpTool do
7
7
  let(:server_context) { instance_double('ServerContext').as_null_object }
8
8
 
9
9
  before do
10
- response_class = Class.new do
11
- attr_reader :payload, :meta
12
-
13
- def initialize(payload)
14
- @payload = payload
15
- @meta = nil
16
- end
17
- end
18
- stub_const('MCP::Tool::Response', response_class)
10
+ setup_mcp_response_stub
19
11
  end
20
12
 
21
13
  it 'returns guidance for each registered tool' do
@@ -23,24 +15,26 @@ RSpec.describe SimpleCovMcp::Tools::HelpTool do
23
15
  expect(response.meta).to be_nil
24
16
 
25
17
  payload = response.payload.first
26
- expect(payload['type']).to eq('resource')
27
- expect(payload['resource']).to include('mimeType' => 'application/json')
28
- data = JSON.parse(payload['resource']['text'])
18
+ expect(payload['type']).to eq('text')
19
+ data = JSON.parse(payload['text'])
29
20
  tool_names = data['tools'].map { |entry| entry['tool'] }
30
21
 
31
- expect(tool_names).to include('coverage_summary_tool', 'uncovered_lines_tool', 'all_files_coverage_tool', 'coverage_table_tool', 'version_tool')
22
+ expect(tool_names).to include('coverage_summary_tool', 'uncovered_lines_tool',
23
+ 'all_files_coverage_tool', 'coverage_table_tool', 'version_tool')
32
24
  expect(data['tools']).to all(include('use_when', 'avoid_when', 'inputs', 'example'))
33
25
  end
34
26
 
35
27
  it 'filters entries when a query is provided' do
36
28
  response = described_class.call(query: 'uncovered', server_context: server_context)
37
29
  payload = response.payload.first
38
- expect(payload['type']).to eq('resource')
39
- data = JSON.parse(payload['resource']['text'])
30
+ expect(payload['type']).to eq('text')
31
+ data = JSON.parse(payload['text'])
40
32
 
41
33
  expect(data['tools']).not_to be_empty
42
34
  expect(data['tools']).to all(satisfy do |entry|
43
- combined = [entry['tool'], entry['label'], entry['use_when'], entry['avoid_when']].compact.join(' ').downcase
35
+ combined =
36
+ [entry['tool'], entry['label'], entry['use_when'], entry['avoid_when']]
37
+ .compact.join(' ').downcase
44
38
  combined.include?('uncovered')
45
39
  end)
46
40
  expect(data['tools'].map { |entry| entry['tool'] }).to include('uncovered_lines_tool')