simplecov-mcp 1.0.1 → 2.0.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 (164) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +98 -50
  3. data/docs/{ARCHITECTURE.md → dev/ARCHITECTURE.md} +11 -10
  4. data/docs/dev/BRANCH_ONLY_COVERAGE.md +158 -0
  5. data/docs/{DEVELOPMENT.md → dev/DEVELOPMENT.md} +2 -1
  6. data/docs/dev/README.md +10 -0
  7. data/docs/dev/RELEASING.md +146 -0
  8. data/docs/{arch-decisions → dev/arch-decisions}/001-x-arch-decision.md +3 -1
  9. data/docs/{arch-decisions → dev/arch-decisions}/002-x-arch-decision.md +7 -5
  10. data/docs/{arch-decisions → dev/arch-decisions}/003-x-arch-decision.md +2 -0
  11. data/docs/{arch-decisions → dev/arch-decisions}/004-x-arch-decision.md +6 -2
  12. data/docs/{arch-decisions → dev/arch-decisions}/005-x-arch-decision.md +4 -2
  13. data/docs/{arch-decisions → dev/arch-decisions}/README.md +3 -3
  14. data/docs/{presentations → dev/presentations}/simplecov-mcp-presentation.md +28 -22
  15. data/docs/fixtures/demo_project/README.md +9 -0
  16. data/docs/{ADVANCED_USAGE.md → user/ADVANCED_USAGE.md} +129 -319
  17. data/docs/user/CLI_FALLBACK_FOR_LLMS.md +34 -0
  18. data/docs/user/CLI_USAGE.md +750 -0
  19. data/docs/{ERROR_HANDLING.md → user/ERROR_HANDLING.md} +12 -12
  20. data/docs/user/EXAMPLES.md +588 -0
  21. data/docs/user/INSTALLATION.md +130 -0
  22. data/docs/{LIBRARY_API.md → user/LIBRARY_API.md} +90 -32
  23. data/docs/{MCP_INTEGRATION.md → user/MCP_INTEGRATION.md} +36 -34
  24. data/docs/user/README.md +14 -0
  25. data/docs/{TROUBLESHOOTING.md → user/TROUBLESHOOTING.md} +21 -100
  26. data/docs/user/V2-BREAKING-CHANGES.md +472 -0
  27. data/exe/simplecov-mcp +1 -1
  28. data/lib/simplecov_mcp/{cli_config.rb → app_config.rb} +12 -12
  29. data/lib/simplecov_mcp/app_context.rb +1 -1
  30. data/lib/simplecov_mcp/base_tool.rb +66 -38
  31. data/lib/simplecov_mcp/cli.rb +67 -123
  32. data/lib/simplecov_mcp/commands/base_command.rb +16 -27
  33. data/lib/simplecov_mcp/commands/command_factory.rb +8 -2
  34. data/lib/simplecov_mcp/commands/detailed_command.rb +16 -2
  35. data/lib/simplecov_mcp/commands/list_command.rb +1 -1
  36. data/lib/simplecov_mcp/commands/raw_command.rb +18 -2
  37. data/lib/simplecov_mcp/commands/summary_command.rb +20 -3
  38. data/lib/simplecov_mcp/commands/totals_command.rb +53 -0
  39. data/lib/simplecov_mcp/commands/uncovered_command.rb +24 -5
  40. data/lib/simplecov_mcp/commands/validate_command.rb +60 -0
  41. data/lib/simplecov_mcp/commands/version_command.rb +19 -4
  42. data/lib/simplecov_mcp/config_parser.rb +32 -0
  43. data/lib/simplecov_mcp/constants.rb +3 -3
  44. data/lib/simplecov_mcp/coverage_reporter.rb +31 -0
  45. data/lib/simplecov_mcp/error_handler.rb +81 -40
  46. data/lib/simplecov_mcp/error_handler_factory.rb +2 -2
  47. data/lib/simplecov_mcp/errors.rb +12 -19
  48. data/lib/simplecov_mcp/formatters/source_formatter.rb +23 -19
  49. data/lib/simplecov_mcp/formatters.rb +51 -0
  50. data/lib/simplecov_mcp/mcp_server.rb +9 -7
  51. data/lib/simplecov_mcp/mode_detector.rb +6 -5
  52. data/lib/simplecov_mcp/model.rb +122 -88
  53. data/lib/simplecov_mcp/option_normalizers.rb +39 -18
  54. data/lib/simplecov_mcp/option_parser_builder.rb +85 -72
  55. data/lib/simplecov_mcp/option_parsers/env_options_parser.rb +3 -5
  56. data/lib/simplecov_mcp/option_parsers/error_helper.rb +18 -17
  57. data/lib/simplecov_mcp/path_relativizer.rb +17 -14
  58. data/lib/simplecov_mcp/predicate_evaluator.rb +72 -0
  59. data/lib/simplecov_mcp/presenters/base_coverage_presenter.rb +1 -3
  60. data/lib/simplecov_mcp/presenters/coverage_detailed_presenter.rb +1 -3
  61. data/lib/simplecov_mcp/presenters/coverage_raw_presenter.rb +1 -3
  62. data/lib/simplecov_mcp/presenters/coverage_summary_presenter.rb +1 -3
  63. data/lib/simplecov_mcp/presenters/coverage_uncovered_presenter.rb +1 -3
  64. data/lib/simplecov_mcp/presenters/project_coverage_presenter.rb +1 -3
  65. data/lib/simplecov_mcp/presenters/project_totals_presenter.rb +27 -0
  66. data/lib/simplecov_mcp/resolvers/coverage_line_resolver.rb +14 -18
  67. data/lib/simplecov_mcp/resolvers/resultset_path_resolver.rb +7 -9
  68. data/lib/simplecov_mcp/resultset_loader.rb +20 -25
  69. data/lib/simplecov_mcp/staleness_checker.rb +50 -46
  70. data/lib/simplecov_mcp/table_formatter.rb +64 -0
  71. data/lib/simplecov_mcp/tools/all_files_coverage_tool.rb +20 -50
  72. data/lib/simplecov_mcp/tools/coverage_detailed_tool.rb +13 -7
  73. data/lib/simplecov_mcp/tools/coverage_raw_tool.rb +12 -7
  74. data/lib/simplecov_mcp/tools/coverage_summary_tool.rb +13 -8
  75. data/lib/simplecov_mcp/tools/coverage_table_tool.rb +20 -60
  76. data/lib/simplecov_mcp/tools/coverage_totals_tool.rb +44 -0
  77. data/lib/simplecov_mcp/tools/help_tool.rb +38 -66
  78. data/lib/simplecov_mcp/tools/uncovered_lines_tool.rb +13 -8
  79. data/lib/simplecov_mcp/tools/validate_tool.rb +72 -0
  80. data/lib/simplecov_mcp/tools/version_tool.rb +7 -14
  81. data/lib/simplecov_mcp/util.rb +18 -12
  82. data/lib/simplecov_mcp/version.rb +1 -1
  83. data/lib/simplecov_mcp.rb +23 -29
  84. data/spec/all_files_coverage_tool_spec.rb +4 -3
  85. data/spec/{cli_config_spec.rb → app_config_spec.rb} +31 -26
  86. data/spec/base_tool_spec.rb +17 -14
  87. data/spec/cli/show_default_report_spec.rb +2 -2
  88. data/spec/cli_enumerated_options_spec.rb +31 -9
  89. data/spec/cli_error_spec.rb +46 -23
  90. data/spec/cli_format_spec.rb +123 -0
  91. data/spec/cli_json_options_spec.rb +50 -0
  92. data/spec/cli_source_spec.rb +11 -63
  93. data/spec/cli_spec.rb +82 -97
  94. data/spec/cli_usage_spec.rb +15 -15
  95. data/spec/commands/base_command_spec.rb +12 -92
  96. data/spec/commands/command_factory_spec.rb +7 -3
  97. data/spec/commands/detailed_command_spec.rb +10 -24
  98. data/spec/commands/list_command_spec.rb +28 -0
  99. data/spec/commands/raw_command_spec.rb +43 -20
  100. data/spec/commands/summary_command_spec.rb +10 -23
  101. data/spec/commands/totals_command_spec.rb +34 -0
  102. data/spec/commands/uncovered_command_spec.rb +29 -23
  103. data/spec/commands/validate_command_spec.rb +213 -0
  104. data/spec/commands/version_command_spec.rb +38 -0
  105. data/spec/constants_spec.rb +3 -3
  106. data/spec/coverage_reporter_spec.rb +102 -0
  107. data/spec/coverage_table_tool_spec.rb +21 -10
  108. data/spec/coverage_totals_tool_spec.rb +37 -0
  109. data/spec/error_handler_spec.rb +120 -4
  110. data/spec/error_mode_spec.rb +18 -22
  111. data/spec/errors_edge_cases_spec.rb +101 -28
  112. data/spec/errors_stale_spec.rb +34 -0
  113. data/spec/file_based_mcp_tools_spec.rb +6 -6
  114. data/spec/fixtures/project1/lib/bar.rb +2 -0
  115. data/spec/fixtures/project1/lib/foo.rb +2 -0
  116. data/spec/help_tool_spec.rb +2 -18
  117. data/spec/integration_spec.rb +103 -161
  118. data/spec/logging_fallback_spec.rb +3 -3
  119. data/spec/mcp_server_integration_spec.rb +1 -1
  120. data/spec/mcp_server_spec.rb +70 -53
  121. data/spec/mode_detector_spec.rb +46 -41
  122. data/spec/model_error_handling_spec.rb +139 -78
  123. data/spec/model_staleness_spec.rb +13 -13
  124. data/spec/option_normalizers_spec.rb +111 -112
  125. data/spec/option_parsers/env_options_parser_spec.rb +25 -37
  126. data/spec/option_parsers/error_helper_spec.rb +56 -56
  127. data/spec/path_relativizer_spec.rb +15 -0
  128. data/spec/presenters/coverage_detailed_presenter_spec.rb +1 -1
  129. data/spec/presenters/coverage_summary_presenter_spec.rb +1 -1
  130. data/spec/presenters/coverage_uncovered_presenter_spec.rb +1 -1
  131. data/spec/presenters/project_coverage_presenter_spec.rb +9 -8
  132. data/spec/presenters/project_totals_presenter_spec.rb +144 -0
  133. data/spec/resolvers/coverage_line_resolver_spec.rb +261 -36
  134. data/spec/resolvers/resultset_path_resolver_spec.rb +13 -8
  135. data/spec/shared_examples/file_based_mcp_tools.rb +23 -18
  136. data/spec/shared_examples/formatted_command_examples.rb +64 -0
  137. data/spec/simple_cov_mcp_module_spec.rb +24 -3
  138. data/spec/simplecov_mcp/formatters/source_formatter_spec.rb +267 -0
  139. data/spec/simplecov_mcp/formatters_spec.rb +76 -0
  140. data/spec/simplecov_mcp/presenters/base_coverage_presenter_spec.rb +79 -0
  141. data/spec/simplecov_mcp_model_spec.rb +97 -47
  142. data/spec/simplecov_mcp_opts_spec.rb +42 -39
  143. data/spec/spec_helper.rb +27 -92
  144. data/spec/staleness_checker_spec.rb +10 -9
  145. data/spec/staleness_more_spec.rb +4 -4
  146. data/spec/support/cli_helpers.rb +22 -0
  147. data/spec/support/control_flow_helpers.rb +20 -0
  148. data/spec/support/fake_mcp.rb +40 -0
  149. data/spec/support/io_helpers.rb +29 -0
  150. data/spec/support/mcp_helpers.rb +35 -0
  151. data/spec/support/mcp_runner.rb +10 -8
  152. data/spec/support/mocking_helpers.rb +30 -0
  153. data/spec/table_format_spec.rb +70 -0
  154. data/spec/tools/validate_tool_spec.rb +132 -0
  155. data/spec/tools_error_handling_spec.rb +34 -48
  156. data/spec/util_spec.rb +5 -4
  157. data/spec/version_spec.rb +7 -7
  158. data/spec/version_tool_spec.rb +20 -22
  159. metadata +90 -23
  160. data/docs/BRANCH_ONLY_COVERAGE.md +0 -81
  161. data/docs/CLI_USAGE.md +0 -637
  162. data/docs/EXAMPLES.md +0 -430
  163. data/docs/INSTALLATION.md +0 -352
  164. data/spec/cli_success_predicate_spec.rb +0 -141
@@ -5,25 +5,17 @@ require 'spec_helper'
5
5
  RSpec.describe SimpleCovMcp::OptionNormalizers do
6
6
  describe '.normalize_sort_order' do
7
7
  context 'with strict mode (default)' do
8
- it 'normalizes "a" to :ascending' do
9
- expect(described_class.normalize_sort_order('a')).to eq(:ascending)
10
- end
11
-
12
- it 'normalizes "ascending" to :ascending' do
13
- expect(described_class.normalize_sort_order('ascending')).to eq(:ascending)
14
- end
15
-
16
- it 'normalizes "d" to :descending' do
17
- expect(described_class.normalize_sort_order('d')).to eq(:descending)
18
- end
19
-
20
- it 'normalizes "descending" to :descending' do
21
- expect(described_class.normalize_sort_order('descending')).to eq(:descending)
22
- end
23
-
24
- it 'is case-insensitive' do
25
- expect(described_class.normalize_sort_order('ASCENDING')).to eq(:ascending)
26
- expect(described_class.normalize_sort_order('Descending')).to eq(:descending)
8
+ [
9
+ ['a', :ascending],
10
+ ['ascending', :ascending],
11
+ ['d', :descending],
12
+ ['descending', :descending],
13
+ ['ASCENDING', :ascending],
14
+ ['Descending', :descending]
15
+ ].each do |input, expected|
16
+ it "normalizes '#{input}' to #{expected}" do
17
+ expect(described_class.normalize_sort_order(input)).to eq(expected)
18
+ end
27
19
  end
28
20
 
29
21
  it 'raises OptionParser::InvalidArgument for invalid values' do
@@ -45,33 +37,24 @@ RSpec.describe SimpleCovMcp::OptionNormalizers do
45
37
 
46
38
  describe '.normalize_source_mode' do
47
39
  context 'with strict mode (default)' do
48
- it 'normalizes nil to :full' do
49
- expect(described_class.normalize_source_mode(nil)).to eq(:full)
50
- end
51
-
52
- it 'normalizes empty string to :full' do
53
- expect(described_class.normalize_source_mode('')).to eq(:full)
54
- end
55
-
56
- it 'normalizes "f" to :full' do
57
- expect(described_class.normalize_source_mode('f')).to eq(:full)
58
- end
59
-
60
- it 'normalizes "full" to :full' do
61
- expect(described_class.normalize_source_mode('full')).to eq(:full)
62
- end
63
-
64
- it 'normalizes "u" to :uncovered' do
65
- expect(described_class.normalize_source_mode('u')).to eq(:uncovered)
66
- end
67
-
68
- it 'normalizes "uncovered" to :uncovered' do
69
- expect(described_class.normalize_source_mode('uncovered')).to eq(:uncovered)
70
- end
71
-
72
- it 'is case-insensitive' do
73
- expect(described_class.normalize_source_mode('FULL')).to eq(:full)
74
- expect(described_class.normalize_source_mode('Uncovered')).to eq(:uncovered)
40
+ [nil, ''].each do |input|
41
+ it "raises OptionParser::InvalidArgument for #{input.inspect}" do
42
+ expect { described_class.normalize_source_mode(input) }
43
+ .to raise_error(OptionParser::InvalidArgument, /invalid argument/)
44
+ end
45
+ end
46
+
47
+ [
48
+ ['f', :full],
49
+ ['full', :full],
50
+ ['u', :uncovered],
51
+ ['uncovered', :uncovered],
52
+ ['FULL', :full],
53
+ ['Uncovered', :uncovered]
54
+ ].each do |input, expected|
55
+ it "normalizes '#{input}' to #{expected}" do
56
+ expect(described_class.normalize_source_mode(input)).to eq(expected)
57
+ end
75
58
  end
76
59
 
77
60
  it 'raises OptionParser::InvalidArgument for invalid values' do
@@ -91,84 +74,70 @@ RSpec.describe SimpleCovMcp::OptionNormalizers do
91
74
  end
92
75
  end
93
76
 
94
- describe '.normalize_stale_mode' do
77
+ describe '.normalize_staleness' do
95
78
  context 'with strict mode (default)' do
96
- it 'normalizes "o" to :off' do
97
- expect(described_class.normalize_stale_mode('o')).to eq(:off)
98
- end
99
-
100
- it 'normalizes "off" to :off' do
101
- expect(described_class.normalize_stale_mode('off')).to eq(:off)
102
- end
103
-
104
- it 'normalizes "e" to :error' do
105
- expect(described_class.normalize_stale_mode('e')).to eq(:error)
106
- end
107
-
108
- it 'normalizes "error" to :error' do
109
- expect(described_class.normalize_stale_mode('error')).to eq(:error)
110
- end
111
-
112
- it 'is case-insensitive' do
113
- expect(described_class.normalize_stale_mode('OFF')).to eq(:off)
114
- expect(described_class.normalize_stale_mode('Error')).to eq(:error)
79
+ [
80
+ ['o', :off],
81
+ ['off', :off],
82
+ ['e', :error],
83
+ ['error', :error],
84
+ ['OFF', :off],
85
+ ['Error', :error]
86
+ ].each do |input, expected|
87
+ it "normalizes '#{input}' to #{expected}" do
88
+ expect(described_class.normalize_staleness(input)).to eq(expected)
89
+ end
115
90
  end
116
91
 
117
92
  it 'raises OptionParser::InvalidArgument for invalid values' do
118
- expect { described_class.normalize_stale_mode('invalid') }
93
+ expect { described_class.normalize_staleness('invalid') }
119
94
  .to raise_error(OptionParser::InvalidArgument, /invalid argument: invalid/)
120
95
  end
121
96
  end
122
97
 
123
98
  context 'with strict: false' do
124
99
  it 'returns nil for invalid values' do
125
- expect(described_class.normalize_stale_mode('invalid', strict: false)).to be_nil
100
+ expect(described_class.normalize_staleness('invalid', strict: false)).to be_nil
126
101
  end
127
102
 
128
103
  it 'still normalizes valid values' do
129
- expect(described_class.normalize_stale_mode('e', strict: false)).to eq(:error)
104
+ expect(described_class.normalize_staleness('e', strict: false)).to eq(:error)
130
105
  end
131
106
  end
132
107
  end
133
108
 
134
109
  describe '.normalize_error_mode' do
135
110
  context 'with strict mode (default)' do
136
- it 'normalizes "off" to :off' do
137
- expect(described_class.normalize_error_mode('off')).to eq(:off)
138
- end
139
-
140
- it 'normalizes "on" to :on' do
141
- expect(described_class.normalize_error_mode('on')).to eq(:on)
142
- end
143
-
144
- it 'normalizes "trace" to :trace' do
145
- expect(described_class.normalize_error_mode('trace')).to eq(:trace)
146
- end
147
-
148
- it 'normalizes "t" to :trace' do
149
- expect(described_class.normalize_error_mode('t')).to eq(:trace)
150
- end
151
-
152
- it 'is case-insensitive' do
153
- expect(described_class.normalize_error_mode('OFF')).to eq(:off)
154
- expect(described_class.normalize_error_mode('On')).to eq(:on)
155
- expect(described_class.normalize_error_mode('TRACE')).to eq(:trace)
156
- end
157
-
158
- it 'raises OptionParser::InvalidArgument for invalid values' do
159
- expect { described_class.normalize_error_mode('invalid') }
160
- .to raise_error(OptionParser::InvalidArgument, /invalid argument: invalid/)
111
+ [
112
+ ['off', :off],
113
+ ['o', :off],
114
+ ['log', :log],
115
+ ['l', :log],
116
+ ['debug', :debug],
117
+ ['d', :debug],
118
+ ['OFF', :off],
119
+ ['Log', :log],
120
+ ['DEBUG', :debug]
121
+ ].each do |input, expected|
122
+ it "normalizes '#{input}' to #{expected}" do
123
+ expect(described_class.normalize_error_mode(input)).to eq(expected)
124
+ end
125
+ end
126
+
127
+ ['invalid', 'on', 'trace'].each do |input|
128
+ it "raises OptionParser::InvalidArgument for '#{input}'" do
129
+ expect { described_class.normalize_error_mode(input) }
130
+ .to raise_error(OptionParser::InvalidArgument, /invalid argument: #{input}/)
131
+ end
161
132
  end
162
133
  end
163
134
 
164
- context 'with strict: false and default: :on' do
165
- it 'returns default for invalid values' do
166
- expect(described_class.normalize_error_mode('invalid', strict: false,
167
- default: :on)).to eq(:on)
168
- end
169
-
170
- it 'returns default for nil' do
171
- expect(described_class.normalize_error_mode(nil, strict: false, default: :on)).to eq(:on)
135
+ context 'with strict: false and default: :log' do
136
+ [['invalid', :log], [nil, :log]].each do |input, expected|
137
+ it "returns default #{expected} for #{input.inspect}" do
138
+ expect(described_class.normalize_error_mode(input, strict: false,
139
+ default: :log)).to eq(expected)
140
+ end
172
141
  end
173
142
 
174
143
  it 'still normalizes valid values' do
@@ -184,21 +153,51 @@ RSpec.describe SimpleCovMcp::OptionNormalizers do
184
153
  end
185
154
  end
186
155
 
187
- describe 'constant maps' do
188
- it 'has frozen SORT_ORDER_MAP' do
189
- expect(described_class::SORT_ORDER_MAP).to be_frozen
190
- end
156
+ describe '.normalize_format' do
157
+ context 'with strict mode (default)' do
158
+ [
159
+ ['t', :table],
160
+ ['table', :table],
161
+ ['j', :json],
162
+ ['json', :json],
163
+ ['pretty_json', :pretty_json],
164
+ ['pretty-json', :pretty_json],
165
+ ['y', :yaml],
166
+ ['yaml', :yaml],
167
+ ['a', :awesome_print],
168
+ ['awesome_print', :awesome_print],
169
+ ['ap', :awesome_print],
170
+ ['TABLE', :table],
171
+ ['Json', :json]
172
+ ].each do |input, expected|
173
+ it "normalizes '#{input}' to #{expected}" do
174
+ expect(described_class.normalize_format(input)).to eq(expected)
175
+ end
176
+ end
191
177
 
192
- it 'has frozen SOURCE_MODE_MAP' do
193
- expect(described_class::SOURCE_MODE_MAP).to be_frozen
178
+ it 'raises OptionParser::InvalidArgument for invalid values' do
179
+ expect { described_class.normalize_format('invalid') }
180
+ .to raise_error(OptionParser::InvalidArgument, /invalid argument: invalid/)
181
+ end
194
182
  end
195
183
 
196
- it 'has frozen STALE_MODE_MAP' do
197
- expect(described_class::STALE_MODE_MAP).to be_frozen
184
+ context 'with strict: false' do
185
+ it 'returns nil for invalid values' do
186
+ expect(described_class.normalize_format('invalid', strict: false)).to be_nil
187
+ end
188
+
189
+ it 'still normalizes valid values' do
190
+ expect(described_class.normalize_format('json', strict: false)).to eq(:json)
191
+ end
198
192
  end
193
+ end
199
194
 
200
- it 'has frozen ERROR_MODE_MAP' do
201
- expect(described_class::ERROR_MODE_MAP).to be_frozen
195
+ describe 'constant maps' do
196
+ [:SORT_ORDER_MAP, :SOURCE_MODE_MAP, :STALENESS_MAP, :ERROR_MODE_MAP,
197
+ :FORMAT_MAP].each do |const|
198
+ it "has frozen #{const}" do
199
+ expect(described_class.const_get(const)).to be_frozen
200
+ end
202
201
  end
203
202
  end
204
203
  end
@@ -30,8 +30,8 @@ RSpec.describe SimpleCovMcp::OptionParsers::EnvOptionsParser do
30
30
  end
31
31
 
32
32
  it 'parses simple options correctly' do
33
- ENV['SIMPLECOV_MCP_OPTS'] = '--error-mode off --json'
34
- expect(parser.parse_env_opts).to eq(['--error-mode', 'off', '--json'])
33
+ ENV['SIMPLECOV_MCP_OPTS'] = '--error-mode off --format json'
34
+ expect(parser.parse_env_opts).to eq(['--error-mode', 'off', '--format', 'json'])
35
35
  end
36
36
 
37
37
  it 'handles quoted strings with spaces' do
@@ -106,9 +106,9 @@ RSpec.describe SimpleCovMcp::OptionParsers::EnvOptionsParser do
106
106
 
107
107
  context 'when error-mode is found' do
108
108
  it 'extracts error-mode with space separator' do
109
- argv = ['--error-mode', 'trace', '--other-option']
109
+ argv = ['--error-mode', 'debug', '--other-option']
110
110
  result = parser.pre_scan_error_mode(argv, error_mode_normalizer: error_mode_normalizer)
111
- expect(result).to eq(:trace)
111
+ expect(result).to eq(:debug)
112
112
  end
113
113
 
114
114
  it 'extracts error-mode with equals separator' do
@@ -125,9 +125,9 @@ RSpec.describe SimpleCovMcp::OptionParsers::EnvOptionsParser do
125
125
  end
126
126
 
127
127
  it 'returns first error-mode when multiple are present' do
128
- argv = ['--error-mode', 'on', '--error-mode', 'off']
128
+ argv = ['--error-mode', 'log', '--error-mode', 'off']
129
129
  result = parser.pre_scan_error_mode(argv, error_mode_normalizer: error_mode_normalizer)
130
- expect(result).to eq(:on)
130
+ expect(result).to eq(:log)
131
131
  end
132
132
  end
133
133
 
@@ -145,25 +145,25 @@ RSpec.describe SimpleCovMcp::OptionParsers::EnvOptionsParser do
145
145
  end
146
146
  end
147
147
 
148
- context 'error handling during pre-scan' do
148
+ context 'when handling errors during pre-scan' do
149
149
  it 'returns nil when normalizer raises an error' do
150
- faulty_normalizer = ->(value) { raise StandardError, 'Intentional error' }
151
- argv = ['--error-mode', 'on']
150
+ faulty_normalizer = ->(_) { raise StandardError, 'Intentional error' }
151
+ argv = ['--error-mode', 'log']
152
152
 
153
153
  result = parser.pre_scan_error_mode(argv, error_mode_normalizer: faulty_normalizer)
154
154
  expect(result).to be_nil
155
155
  end
156
156
 
157
157
  it 'returns nil when normalizer raises ArgumentError' do
158
- faulty_normalizer = ->(value) { raise ArgumentError, 'Bad argument' }
159
- argv = ['--error-mode', 'on']
158
+ faulty_normalizer = ->(_) { raise ArgumentError, 'Bad argument' }
159
+ argv = ['--error-mode', 'log']
160
160
 
161
161
  result = parser.pre_scan_error_mode(argv, error_mode_normalizer: faulty_normalizer)
162
162
  expect(result).to be_nil
163
163
  end
164
164
 
165
165
  it 'returns nil when normalizer raises RuntimeError' do
166
- faulty_normalizer = ->(value) { raise RuntimeError, 'Runtime problem' }
166
+ faulty_normalizer = ->(_) { raise 'Runtime problem' }
167
167
  argv = ['--error-mode=off']
168
168
 
169
169
  result = parser.pre_scan_error_mode(argv, error_mode_normalizer: faulty_normalizer)
@@ -172,14 +172,6 @@ RSpec.describe SimpleCovMcp::OptionParsers::EnvOptionsParser do
172
172
  end
173
173
  end
174
174
 
175
- describe 'integration with ErrorHandlerFactory' do
176
- it 'maps trace alias to an accepted error_mode' do
177
- mode = parser.pre_scan_error_mode(['--error-mode', 'trace'])
178
- expect { SimpleCovMcp::ErrorHandlerFactory.for_cli(error_mode: mode) }.not_to raise_error
179
- expect(mode).to eq(:trace)
180
- end
181
- end
182
-
183
175
  describe '#normalize_error_mode (private)' do
184
176
  it 'normalizes "off" to :off' do
185
177
  expect(parser.send(:normalize_error_mode, 'off')).to eq(:off)
@@ -187,29 +179,25 @@ RSpec.describe SimpleCovMcp::OptionParsers::EnvOptionsParser do
187
179
  expect(parser.send(:normalize_error_mode, 'Off')).to eq(:off)
188
180
  end
189
181
 
190
- it 'normalizes "on" to :on' do
191
- expect(parser.send(:normalize_error_mode, 'on')).to eq(:on)
192
- expect(parser.send(:normalize_error_mode, 'ON')).to eq(:on)
193
- end
194
-
195
- it 'normalizes "trace" to :trace' do
196
- expect(parser.send(:normalize_error_mode, 'trace')).to eq(:trace)
197
- expect(parser.send(:normalize_error_mode, 'TRACE')).to eq(:trace)
182
+ it 'normalizes "log" to :log' do
183
+ expect(parser.send(:normalize_error_mode, 'log')).to eq(:log)
184
+ expect(parser.send(:normalize_error_mode, 'LOG')).to eq(:log)
185
+ expect(parser.send(:normalize_error_mode, 'Log')).to eq(:log)
198
186
  end
199
187
 
200
- it 'normalizes "t" to :trace' do
201
- expect(parser.send(:normalize_error_mode, 't')).to eq(:trace)
202
- expect(parser.send(:normalize_error_mode, 'T')).to eq(:trace)
188
+ it 'normalizes "debug" to :debug' do
189
+ expect(parser.send(:normalize_error_mode, 'debug')).to eq(:debug)
190
+ expect(parser.send(:normalize_error_mode, 'DEBUG')).to eq(:debug)
203
191
  end
204
192
 
205
- it 'defaults unknown values to :on' do
206
- expect(parser.send(:normalize_error_mode, 'unknown')).to eq(:on)
207
- expect(parser.send(:normalize_error_mode, 'invalid')).to eq(:on)
208
- expect(parser.send(:normalize_error_mode, '')).to eq(:on)
193
+ it 'defaults unknown values to :log' do
194
+ expect(parser.send(:normalize_error_mode, 'unknown')).to eq(:log)
195
+ expect(parser.send(:normalize_error_mode, 'invalid')).to eq(:log)
196
+ expect(parser.send(:normalize_error_mode, '')).to eq(:log)
209
197
  end
210
198
 
211
- it 'handles nil by defaulting to :on' do
212
- expect(parser.send(:normalize_error_mode, nil)).to eq(:on)
199
+ it 'handles nil by defaulting to :log' do
200
+ expect(parser.send(:normalize_error_mode, nil)).to eq(:log)
213
201
  end
214
202
  end
215
203
 
@@ -2,6 +2,29 @@
2
2
 
3
3
  require 'spec_helper'
4
4
 
5
+ OPTION_TESTS = {
6
+ staleness: {
7
+ long: '--staleness',
8
+ short: '-S',
9
+ pattern: /Valid values for --staleness: o\[ff\]|e\[rror\]/
10
+ },
11
+ source: {
12
+ long: '--source',
13
+ short: '-s',
14
+ pattern: /Valid values for --source: f\[ull\]|u\[ncovered\]/
15
+ },
16
+ error_mode: {
17
+ long: '--error-mode',
18
+ short: nil,
19
+ pattern: /Valid values for --error-mode: o\[ff\]|l\[og\]|d\[ebug\]/
20
+ },
21
+ sort_order: {
22
+ long: '--sort-order',
23
+ short: '-o',
24
+ pattern: /Valid values for --sort-order: a\[scending\]|d\[escending\]/
25
+ }
26
+ }.freeze
27
+
5
28
  RSpec.describe SimpleCovMcp::OptionParsers::ErrorHelper do
6
29
  subject(:helper) { described_class.new }
7
30
 
@@ -23,42 +46,16 @@ RSpec.describe SimpleCovMcp::OptionParsers::ErrorHelper do
23
46
  # Helper method to test error output matches expected pattern
24
47
  def expect_error_output(error:, argv:, pattern:)
25
48
  expect do
26
- begin
27
- helper.handle_option_parser_error(error, argv: argv)
28
- rescue SystemExit
29
- # Ignore exit call
30
- end
49
+ helper.handle_option_parser_error(error, argv: argv)
50
+ rescue SystemExit
51
+ # Ignore exit call
31
52
  end.to output(pattern).to_stderr
32
53
  end
33
54
 
34
- # Test data for enumerated options
35
- OPTION_TESTS = {
36
- stale: {
37
- long: '--stale',
38
- short: '-S',
39
- pattern: /Valid values for --stale: o\[ff\]|e\[rror\]/
40
- },
41
- source: {
42
- long: '--source',
43
- short: '-s',
44
- pattern: /Valid values for --source: f\[ull\]|u\[ncovered\]/
45
- },
46
- error_mode: {
47
- long: '--error-mode',
48
- short: nil,
49
- pattern: /Valid values for --error-mode: off\|on\|t\[race\]/
50
- },
51
- sort_order: {
52
- long: '--sort-order',
53
- short: '-o',
54
- pattern: /Valid values for --sort-order: a\[scending\]|d\[escending\]/
55
- }
56
- }.freeze
57
-
58
55
  describe '#handle_option_parser_error' do
59
56
  context 'with invalid enumerated option values' do
60
- OPTION_TESTS.each do |name, config|
61
- context "for #{config[:long]} option" do
57
+ OPTION_TESTS.each_value do |config|
58
+ context "when parsing #{config[:long]} option" do
62
59
  let(:error) { OptionParser::InvalidArgument.new('invalid argument: xyz') }
63
60
 
64
61
  it 'suggests valid values for space-separated form with invalid value' do
@@ -89,13 +86,13 @@ RSpec.describe SimpleCovMcp::OptionParsers::ErrorHelper do
89
86
  end
90
87
  end
91
88
 
92
- context 'for --stale option edge cases' do
89
+ context 'when handling --staleness option edge cases' do
93
90
  it 'suggests valid values when value is missing' do
94
- error = OptionParser::InvalidArgument.new('missing argument: --stale')
91
+ error = OptionParser::InvalidArgument.new('missing argument: --staleness')
95
92
  expect_error_output(
96
93
  error: error,
97
- argv: ['--stale'],
98
- pattern: /Valid values for --stale: o\[ff\]|e\[rror\]/
94
+ argv: ['--staleness'],
95
+ pattern: /Valid values for --staleness: o\[ff\]|e\[rror\]/
99
96
  )
100
97
  end
101
98
 
@@ -103,8 +100,8 @@ RSpec.describe SimpleCovMcp::OptionParsers::ErrorHelper do
103
100
  error = OptionParser::InvalidArgument.new('invalid argument: --other')
104
101
  expect_error_output(
105
102
  error: error,
106
- argv: ['--stale', '--other-option'],
107
- pattern: /Valid values for --stale: o\[ff\]|e\[rror\]/
103
+ argv: ['--staleness', '--other-option'],
104
+ pattern: /Valid values for --staleness: o\[ff\]|e\[rror\]/
108
105
  )
109
106
  end
110
107
  end
@@ -115,8 +112,8 @@ RSpec.describe SimpleCovMcp::OptionParsers::ErrorHelper do
115
112
  error = OptionParser::InvalidArgument.new('invalid argument: bad')
116
113
  expect_error_output(
117
114
  error: error,
118
- argv: ['--resultset', 'coverage', '--stale', 'bad', '--json'],
119
- pattern: /Valid values for --stale: o\[ff\]|e\[rror\]/
115
+ argv: ['--resultset', 'coverage', '--staleness', 'bad', '--format', 'json'],
116
+ pattern: /Valid values for --staleness: o\[ff\]|e\[rror\]/
120
117
  )
121
118
  end
122
119
 
@@ -124,7 +121,7 @@ RSpec.describe SimpleCovMcp::OptionParsers::ErrorHelper do
124
121
  error = OptionParser::InvalidArgument.new('invalid argument: invalid')
125
122
  expect_error_output(
126
123
  error: error,
127
- argv: ['--json', '--sort-order=invalid', '--resultset', 'coverage'],
124
+ argv: ['--format', 'json', '--sort-order=invalid', '--resultset', 'coverage'],
128
125
  pattern: /Valid values for --sort-order: a\[scending\]|d\[escending\]/
129
126
  )
130
127
  end
@@ -152,42 +149,44 @@ RSpec.describe SimpleCovMcp::OptionParsers::ErrorHelper do
152
149
  helper.handle_option_parser_error(error, argv: ['--summary'])
153
150
  end
154
151
 
155
- # Note: The subcommand detection logic isn't fully working as expected
152
+ # NOTE: The subcommand detection logic isn't fully working as expected
156
153
  # because extract_invalid_option doesn't properly parse the error message
157
154
  expect(stderr_output).to match(/Error:.*--summary/)
158
155
  expect(stderr_output).to match(/Run 'simplecov-mcp --help'/)
159
156
  end
160
157
  end
161
158
 
162
- context 'exit behavior' do
159
+ context 'when exiting after invalid option' do
163
160
  it 'exits with status 1' do
164
161
  error = OptionParser::InvalidArgument.new('invalid argument: xyz')
165
162
 
166
- expect do
167
- helper.handle_option_parser_error(error, argv: ['--stale', 'xyz'])
168
- end.to raise_error(SystemExit) do |e|
169
- expect(e.status).to eq(1)
163
+ stderr_output = capture_stderr do
164
+ expect do
165
+ helper.handle_option_parser_error(error, argv: ['--staleness', 'xyz'])
166
+ end.to raise_error(SystemExit) do |e|
167
+ expect(e.status).to eq(1)
168
+ end
170
169
  end
170
+
171
+ expect(stderr_output).to include('invalid argument: xyz')
171
172
  end
172
173
  end
173
174
 
174
- context 'usage hint customization' do
175
+ context 'when customizing usage hint' do
175
176
  it 'uses custom usage hint when provided' do
176
177
  error = OptionParser::InvalidArgument.new('invalid argument: xyz')
177
178
 
178
179
  expect do
179
- begin
180
- helper.handle_option_parser_error(error, argv: ['--stale', 'xyz'],
181
- usage_hint: 'Custom hint message')
182
- rescue SystemExit
183
- # Ignore exit call
184
- end
180
+ helper.handle_option_parser_error(error, argv: ['--staleness', 'xyz'],
181
+ usage_hint: 'Custom hint message')
182
+ rescue SystemExit
183
+ # Ignore exit call
185
184
  end.to output(/Custom hint message/).to_stderr
186
185
  end
187
186
  end
188
187
  end
189
188
 
190
- describe 'edge cases' do
189
+ describe 'when handling edge cases' do
191
190
  it 'handles empty argv gracefully' do
192
191
  error = OptionParser::InvalidArgument.new('some error')
193
192
  expect_error_output(
@@ -201,7 +200,8 @@ RSpec.describe SimpleCovMcp::OptionParsers::ErrorHelper do
201
200
  error = OptionParser::InvalidArgument.new('some error')
202
201
 
203
202
  stderr_output = capture_stderr do
204
- helper.handle_option_parser_error(error, argv: ['--json', '--resultset', 'coverage'])
203
+ helper.handle_option_parser_error(error,
204
+ argv: ['--format', 'json', '--resultset', 'coverage'])
205
205
  end
206
206
 
207
207
  expect(stderr_output).to match(/Error: invalid argument: some error/)
@@ -212,7 +212,7 @@ RSpec.describe SimpleCovMcp::OptionParsers::ErrorHelper do
212
212
  error = OptionParser::MissingArgument.new('missing argument: --resultset')
213
213
 
214
214
  stderr_output = capture_stderr do
215
- helper.handle_option_parser_error(error, argv: ['--stale', 'off', '--resultset'])
215
+ helper.handle_option_parser_error(error, argv: ['--staleness', 'off', '--resultset'])
216
216
  end
217
217
 
218
218
  expect(stderr_output).to match(/Error:.*missing argument.*--resultset/)
@@ -79,5 +79,20 @@ RSpec.describe SimpleCovMcp::PathRelativizer do
79
79
  ensure
80
80
  FileUtils.rm_f(file_with_space)
81
81
  end
82
+
83
+ # On Windows, relative_path_from raises ArgumentError for paths on different
84
+ # drives (e.g., C: vs D:). The rescue block returns the original path.
85
+ it 'returns original path when relative_path_from raises ArgumentError' do
86
+ fake_pathname = instance_double(Pathname)
87
+ allow(fake_pathname).to receive(:relative_path_from)
88
+ .and_raise(ArgumentError, 'different prefix')
89
+ allow(Pathname).to receive(:new).and_call_original
90
+ allow(Pathname).to receive(:new).with(File.absolute_path('lib/foo.rb', root))
91
+ .and_return(fake_pathname)
92
+
93
+ result = relativizer.relativize_path(File.join(root, 'lib/foo.rb'))
94
+
95
+ expect(result).to eq(File.join(root, 'lib/foo.rb'))
96
+ end
82
97
  end
83
98
  end
@@ -12,7 +12,7 @@ RSpec.describe SimpleCovMcp::Presenters::CoverageDetailedPresenter do
12
12
  { 'line' => 1, 'hits' => 1, 'covered' => true },
13
13
  { 'line' => 2, 'hits' => 0, 'covered' => false }
14
14
  ],
15
- 'summary' => { 'covered' => 1, 'total' => 2, 'pct' => 50.0 }
15
+ 'summary' => { 'covered' => 1, 'total' => 2, 'percentage' => 50.0 }
16
16
  },
17
17
  stale: 'L',
18
18
  expected_keys: ['lines', 'summary']
@@ -8,7 +8,7 @@ RSpec.describe SimpleCovMcp::Presenters::CoverageSummaryPresenter do
8
8
  model_method: :summary_for,
9
9
  payload: {
10
10
  'file' => '/abs/path/lib/foo.rb',
11
- 'summary' => { 'covered' => 8, 'total' => 10, 'pct' => 80.0 }
11
+ 'summary' => { 'covered' => 8, 'total' => 10, 'percentage' => 80.0 }
12
12
  },
13
13
  stale: false,
14
14
  expected_keys: ['summary']
@@ -9,7 +9,7 @@ RSpec.describe SimpleCovMcp::Presenters::CoverageUncoveredPresenter do
9
9
  payload: {
10
10
  'file' => '/abs/path/lib/foo.rb',
11
11
  'uncovered' => [2, 4],
12
- 'summary' => { 'covered' => 2, 'total' => 4, 'pct' => 50.0 }
12
+ 'summary' => { 'covered' => 2, 'total' => 4, 'percentage' => 50.0 }
13
13
  },
14
14
  stale: 'M',
15
15
  expected_keys: ['uncovered', 'summary']