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,967 @@
1
+ # Advanced Usage Guide
2
+
3
+ This guide covers advanced usage patterns, integration strategies, and optimization techniques for simplecov-mcp.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Advanced MCP Integration](#advanced-mcp-integration)
8
+ - [Staleness Detection & Validation](#staleness-detection--validation)
9
+ - [Advanced Path Resolution](#advanced-path-resolution)
10
+ - [Error Handling Strategies](#error-handling-strategies)
11
+ - [Custom Ruby Integration](#custom-ruby-integration)
12
+ - [CI/CD Integration Patterns](#cicd-integration-patterns)
13
+ - [Advanced Filtering & Glob Patterns](#advanced-filtering--glob-patterns)
14
+ - [Performance Optimization](#performance-optimization)
15
+ - [Custom Output Processing](#custom-output-processing)
16
+
17
+ ---
18
+
19
+ ## Advanced MCP Integration
20
+
21
+ ### MCP Error Handling
22
+
23
+ The MCP server uses structured error responses. Understanding the error types helps with debugging:
24
+
25
+ ```json
26
+ {
27
+ "jsonrpc": "2.0",
28
+ "error": {
29
+ "code": -32603,
30
+ "message": "Coverage data not found at coverage/.resultset.json",
31
+ "data": {
32
+ "type": "FileError",
33
+ "context": "MCP tool execution"
34
+ }
35
+ },
36
+ "id": 1
37
+ }
38
+ ```
39
+
40
+ **Error Types:**
41
+ - `FileError` - File not found in coverage or filesystem
42
+ - `FileNotFoundError` - Source file missing from filesystem
43
+ - `CoverageDataError` - Invalid or corrupt `.resultset.json`
44
+ - `CoverageDataStaleError` - Coverage older than source (single file)
45
+ - `CoverageDataProjectStaleError` - Project-wide staleness issues
46
+
47
+ ### MCP Server Logging
48
+
49
+ The MCP server logs to `simplecov_mcp.log` in the current directory by default. For custom log locations, configure via your MCP client:
50
+
51
+ **Claude Code (`claude_desktop_config.json`):**
52
+ ```json
53
+ {
54
+ "mcpServers": {
55
+ "simplecov-mcp": {
56
+ "command": "simplecov-mcp",
57
+ "args": ["--log-file", "/var/log/coverage/mcp.log"]
58
+ }
59
+ }
60
+ }
61
+ ```
62
+
63
+ **Or use environment variables:**
64
+ ```json
65
+ {
66
+ "mcpServers": {
67
+ "simplecov-mcp": {
68
+ "command": "simplecov-mcp",
69
+ "env": {
70
+ "SIMPLECOV_MCP_OPTS": "--log-file /var/log/coverage/mcp.log"
71
+ }
72
+ }
73
+ }
74
+ }
75
+ ```
76
+
77
+ **To log to standard error:**
78
+ ```json
79
+ {
80
+ "mcpServers": {
81
+ "simplecov-mcp": {
82
+ "command": "simplecov-mcp",
83
+ "args": ["--log-file", "stderr"]
84
+ }
85
+ }
86
+ }
87
+ ```
88
+
89
+ **Note:** Logging to `stdout` is not permitted in MCP mode.
90
+
91
+ ### Testing MCP Server Manually
92
+
93
+ Use JSON-RPC over stdin to test the MCP server:
94
+
95
+ ```sh
96
+ # Get version
97
+ echo '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"version_tool","arguments":{}}}' | simplecov-mcp
98
+
99
+ # Get file summary
100
+ echo '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"coverage_summary_tool","arguments":{"path":"lib/simplecov_mcp/model.rb"}}}' | simplecov-mcp
101
+
102
+ # List all files with sorting
103
+ echo '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"all_files_coverage_tool","arguments":{"sort_order":"ascending"}}}' | simplecov-mcp
104
+
105
+ # Get uncovered lines
106
+ echo '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"uncovered_lines_tool","arguments":{"path":"lib/simplecov_mcp/cli.rb"}}}' | simplecov-mcp
107
+ ```
108
+
109
+ ---
110
+
111
+ ## Staleness Detection & Validation
112
+
113
+ ### Understanding Staleness Modes
114
+
115
+ Staleness checking prevents using outdated coverage data. Two modes are available:
116
+
117
+ **Mode: `off` (default)**
118
+ - No validation, fastest operation
119
+ - Coverage data used as-is
120
+ - Stale indicators still computed but don't block operations
121
+
122
+ **Mode: `error`**
123
+ - Strict validation enabled
124
+ - Raises errors if coverage is outdated
125
+ - Perfect for CI/CD pipelines
126
+
127
+ ### File-Level Staleness
128
+
129
+ A file is considered stale when any of the following are true:
130
+ 1. Source file modified after coverage generation
131
+ 2. Line count differs from coverage array length
132
+ 3. File exists in coverage but deleted from filesystem
133
+
134
+ **CLI Usage:**
135
+ ```sh
136
+ # Fail if any file is stale
137
+ simplecov-mcp --stale error summary lib/model.rb
138
+
139
+ # Check specific file staleness
140
+ simplecov-mcp summary lib/model.rb --stale error
141
+ ```
142
+
143
+ **Ruby API:**
144
+ ```ruby
145
+ model = SimpleCovMcp::CoverageModel.new(
146
+ staleness: 'error'
147
+ )
148
+
149
+ begin
150
+ summary = model.summary_for('lib/model.rb')
151
+ rescue SimpleCovMcp::CoverageDataStaleError => e
152
+ puts "File modified after coverage: #{e.file_path}"
153
+ puts "Coverage timestamp: #{e.cov_timestamp}"
154
+ puts "File mtime: #{e.file_mtime}"
155
+ puts "Source lines: #{e.src_len}, Coverage lines: #{e.cov_len}"
156
+ end
157
+ ```
158
+
159
+ ### Project-Level Staleness
160
+
161
+ Detects system-wide staleness issues:
162
+
163
+ **Conditions Checked:**
164
+ 1. **Newer files** - Any tracked file modified after coverage
165
+ 2. **Missing files** - Tracked files with no coverage data
166
+ 3. **Deleted files** - Coverage exists for non-existent files
167
+
168
+ **CLI Usage:**
169
+ ```sh
170
+ # Track specific patterns
171
+ simplecov-mcp --stale error \
172
+ --tracked-globs "lib/**/*.rb" \
173
+ --tracked-globs "app/**/*.rb"
174
+
175
+ # Combine with JSON output for parsing
176
+ simplecov-mcp list --stale error --json > stale-check.json
177
+ ```
178
+
179
+ **Ruby API:**
180
+ ```ruby
181
+ model = SimpleCovMcp::CoverageModel.new(
182
+ staleness: 'error',
183
+ tracked_globs: ['lib/**/*.rb', 'app/**/*.rb']
184
+ )
185
+
186
+ begin
187
+ files = model.all_files(check_stale: true)
188
+ rescue SimpleCovMcp::CoverageDataProjectStaleError => e
189
+ puts "Newer files: #{e.newer_files.join(', ')}"
190
+ puts "Missing from coverage: #{e.missing_files.join(', ')}"
191
+ puts "Deleted but in coverage: #{e.deleted_files.join(', ')}"
192
+ end
193
+ ```
194
+
195
+ ### Staleness in CI/CD
196
+
197
+ **Example: GitHub Actions**
198
+ ```yaml
199
+ - name: Validate Coverage Freshness
200
+ run: |
201
+ bundle exec rspec
202
+ simplecov-mcp --stale error --tracked-globs "lib/**/*.rb" || {
203
+ echo "Coverage is stale! Re-run tests."
204
+ exit 1
205
+ }
206
+ ```
207
+
208
+ **Example: GitLab CI**
209
+ ```yaml
210
+ coverage:validate:
211
+ script:
212
+ - bundle exec rspec
213
+ - simplecov-mcp list --stale error --json > coverage.json
214
+ artifacts:
215
+ reports:
216
+ coverage_report:
217
+ coverage_format: cobertura
218
+ path: coverage.json
219
+ ```
220
+
221
+ ---
222
+
223
+ ## Advanced Path Resolution
224
+
225
+ ### Multi-Strategy Path Matching
226
+
227
+ SimpleCov-mcp uses a sophisticated path resolution strategy:
228
+
229
+ 1. **Exact absolute path match**
230
+ 2. **Relative path resolution from root**
231
+ 3. **Basename (filename) fallback**
232
+
233
+ This allows flexible path specifications:
234
+
235
+ ```ruby
236
+ model = SimpleCovMcp::CoverageModel.new(root: '/project')
237
+
238
+ # All these work:
239
+ model.summary_for('/project/lib/model.rb') # Absolute
240
+ model.summary_for('lib/model.rb') # Relative
241
+ model.summary_for('model.rb') # Basename only
242
+ ```
243
+
244
+ ### Working with Multiple Projects
245
+
246
+ ```ruby
247
+ # Project A
248
+ model_a = SimpleCovMcp::CoverageModel.new(
249
+ root: '/projects/service-a',
250
+ resultset: '/projects/service-a/coverage/.resultset.json'
251
+ )
252
+
253
+ # Project B
254
+ model_b = SimpleCovMcp::CoverageModel.new(
255
+ root: '/projects/service-b',
256
+ resultset: '/projects/service-b/tmp/coverage/.resultset.json'
257
+ )
258
+
259
+ # Compare coverage
260
+ coverage_a = model_a.all_files
261
+ coverage_b = model_b.all_files
262
+ ```
263
+
264
+
265
+
266
+
267
+ ---
268
+
269
+ ## Error Handling Strategies
270
+
271
+ ### Context-Aware Error Handling
272
+
273
+ SimpleCov-mcp uses different error handling strategies based on context:
274
+
275
+ **CLI Mode:**
276
+ - User-friendly messages
277
+ - Exit codes (0 = success, 1 = error)
278
+ - Optional debug mode
279
+
280
+ **Library Mode:**
281
+ - Raises typed exceptions
282
+ - Programmatic error handling
283
+ - Full exception details
284
+
285
+ **MCP Server Mode:**
286
+ - JSON-RPC error responses
287
+ - Logging to file
288
+ - Structured error data
289
+
290
+ ### Error Modes
291
+
292
+ **CLI Error Modes:**
293
+ ```sh
294
+ # Silent mode - minimal output
295
+ simplecov-mcp --error-mode off summary lib/model.rb
296
+
297
+ # Standard mode - user-friendly errors (default)
298
+ simplecov-mcp --error-mode on summary lib/model.rb
299
+
300
+ # Verbose mode - full stack traces
301
+ simplecov-mcp --error-mode trace summary lib/model.rb
302
+ ```
303
+
304
+ **Ruby API Error Handling:**
305
+ ```ruby
306
+ require 'simplecov_mcp'
307
+
308
+ begin
309
+ model = SimpleCovMcp::CoverageModel.new(
310
+ root: '/project',
311
+ resultset: '/nonexistent/.resultset.json'
312
+ )
313
+ rescue SimpleCovMcp::FileError => e
314
+ # Handle missing resultset
315
+ puts "Coverage file not found: #{e.message}"
316
+ rescue SimpleCovMcp::CoverageDataError => e
317
+ # Handle corrupt/invalid coverage data
318
+ puts "Invalid coverage data: #{e.message}"
319
+ end
320
+ ```
321
+
322
+ ### Custom Error Handlers
323
+
324
+ For library integration, provide custom error handlers:
325
+
326
+ ```ruby
327
+ class CustomErrorHandler
328
+ def handle_error(error, context: nil)
329
+ # Log to custom service
330
+ ErrorTracker.notify(error, context: context)
331
+
332
+ # Re-raise or handle gracefully
333
+ raise error
334
+ end
335
+ end
336
+
337
+ cli = SimpleCovMcp::CoverageCLI.new(
338
+ error_handler: CustomErrorHandler.new
339
+ )
340
+ ```
341
+
342
+ ---
343
+
344
+ ## Custom Ruby Integration
345
+
346
+ ### Building Custom Coverage Policies
347
+
348
+ Use `--success-predicate` to enforce custom coverage policies in CI/CD. Example predicates are in [`examples/success_predicates/`](../../examples/success_predicates/).
349
+
350
+ > **⚠️ SECURITY WARNING**
351
+ >
352
+ > Success predicates execute as **arbitrary Ruby code with full system privileges**. They have unrestricted access to:
353
+ > - File system operations (read, write, delete)
354
+ > - Network operations (HTTP requests, sockets)
355
+ > - System commands (via backticks, `system()`, `exec()`, etc.)
356
+ > - Environment variables and sensitive data
357
+ >
358
+ > **Only use predicate files from trusted sources.** Treat them like any other executable code in your project.
359
+ > - Never use predicates from untrusted or unknown sources
360
+ > - Review predicates before use, especially in CI/CD environments
361
+ > - Store predicates in version control with code review
362
+ > - Be cautious when copying examples from the internet
363
+
364
+ **Quick Usage:**
365
+ ```sh
366
+ # All files must be >= 80%
367
+ simplecov-mcp --success-predicate examples/success_predicates/all_files_above_threshold.rb
368
+
369
+ # Total project coverage >= 85%
370
+ simplecov-mcp --success-predicate examples/success_predicates/project_coverage_minimum.rb
371
+
372
+ # Custom predicate
373
+ simplecov-mcp --success-predicate coverage_policy.rb
374
+ ```
375
+
376
+ **Creating a predicate:**
377
+ ```ruby
378
+ # coverage_policy.rb
379
+ ->(model) do
380
+ # All files must have >= 80% coverage
381
+ model.all_files.all? { |f| f['percentage'] >= 80 }
382
+ end
383
+ ```
384
+
385
+ **Advanced predicate with reporting:**
386
+ ```ruby
387
+ # coverage_policy.rb
388
+ class CoveragePolicy
389
+ def call(model)
390
+ threshold = 80
391
+ low_files = model.all_files.select { |f| f['percentage'] < threshold }
392
+
393
+ if low_files.empty?
394
+ puts "✓ All files have >= #{threshold}% coverage"
395
+ true
396
+ else
397
+ warn "✗ Files below #{threshold}%:"
398
+ low_files.each { |f| warn " #{f['file']}: #{f['percentage']}%" }
399
+ false
400
+ end
401
+ end
402
+ end
403
+
404
+ CoveragePolicy.new
405
+ ```
406
+
407
+ **Exit codes:**
408
+ - `0` - Predicate returned truthy (pass)
409
+ - `1` - Predicate returned falsy (fail)
410
+ - `2` - Predicate raised an error
411
+
412
+ See [examples/success_predicates/README.md](../../examples/success_predicates/README.md) for more examples.
413
+
414
+ ### Path Relativization
415
+
416
+ Convert absolute paths to relative for cleaner output:
417
+
418
+ ```ruby
419
+ model = SimpleCovMcp::CoverageModel.new(root: '/project')
420
+
421
+ # Get data with absolute paths
422
+ data = model.summary_for('lib/model.rb')
423
+ # => { 'file' => '/project/lib/model.rb', ... }
424
+
425
+ # Relativize paths
426
+ relative_data = model.relativize(data)
427
+ # => { 'file' => 'lib/model.rb', ... }
428
+
429
+ # Works with arrays too
430
+ files = model.all_files
431
+ relative_files = model.relativize(files)
432
+ ```
433
+
434
+ ### Batch Operations
435
+
436
+ ```ruby
437
+ # Process multiple files efficiently
438
+ files_to_check = ['lib/model.rb', 'lib/controller.rb', 'lib/view.rb']
439
+
440
+ model = SimpleCovMcp::CoverageModel.new
441
+
442
+ results = files_to_check.map do |file|
443
+ begin
444
+ {
445
+ file: file,
446
+ summary: model.summary_for(file),
447
+ uncovered: model.uncovered_for(file)
448
+ }
449
+ rescue SimpleCovMcp::FileError => e
450
+ { file: file, error: e.message }
451
+ end
452
+ end
453
+ ```
454
+
455
+ ---
456
+
457
+ ## CI/CD Integration Patterns
458
+
459
+ ### GitHub Actions
460
+
461
+ **Complete Workflow:**
462
+ ```yaml
463
+ name: Coverage Analysis
464
+
465
+ on: [push, pull_request]
466
+
467
+ jobs:
468
+ coverage:
469
+ runs-on: ubuntu-latest
470
+ steps:
471
+ - uses: actions/checkout@v3
472
+
473
+ - name: Set up Ruby
474
+ uses: ruby/setup-ruby@v1
475
+ with:
476
+ ruby-version: 3.2
477
+ bundler-cache: true
478
+
479
+ - name: Run tests with coverage
480
+ run: bundle exec rspec
481
+
482
+ - name: Install simplecov-mcp
483
+ run: gem install simplecov-mcp
484
+
485
+ - name: Validate coverage freshness
486
+ run: |
487
+ simplecov-mcp --stale error \
488
+ --tracked-globs "lib/**/*.rb" \
489
+ --tracked-globs "app/**/*.rb"
490
+
491
+ - name: Check minimum coverage
492
+ run: |
493
+ # Export coverage data
494
+ simplecov-mcp list --json > coverage.json
495
+
496
+ # Use jq to check minimum threshold
497
+ MIN_COVERAGE=$(jq '[.files[].percentage] | add / length' coverage.json)
498
+ if (( $(echo "$MIN_COVERAGE < 80" | bc -l) )); then
499
+ echo "Coverage below 80%: $MIN_COVERAGE%"
500
+ exit 1
501
+ fi
502
+
503
+ - name: Upload coverage report
504
+ uses: actions/upload-artifact@v3
505
+ with:
506
+ name: coverage-report
507
+ path: coverage.json
508
+
509
+ - name: Comment PR with coverage
510
+ if: github.event_name == 'pull_request'
511
+ run: |
512
+ COVERAGE=$(simplecov-mcp list)
513
+ gh pr comment ${{ github.event.pull_request.number }} \
514
+ --body "## Coverage Report\n\`\`\`\n$COVERAGE\n\`\`\`"
515
+ env:
516
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
517
+ ```
518
+
519
+ ### GitLab CI
520
+
521
+ ```yaml
522
+ coverage:
523
+ stage: test
524
+ script:
525
+ - bundle exec rspec
526
+ - gem install simplecov-mcp
527
+ - simplecov-mcp --stale error
528
+ artifacts:
529
+ reports:
530
+ coverage_report:
531
+ coverage_format: cobertura
532
+ path: coverage/coverage.xml
533
+ paths:
534
+ - coverage/
535
+ coverage: '/TOTAL.*\s(\d+\.\d+)%/'
536
+ ```
537
+
538
+ ### Jenkins Pipeline
539
+
540
+ ```groovy
541
+ pipeline {
542
+ agent any
543
+
544
+ stages {
545
+ stage('Test') {
546
+ steps {
547
+ sh 'bundle exec rspec'
548
+ }
549
+ }
550
+
551
+ stage('Coverage Analysis') {
552
+ steps {
553
+ sh 'gem install simplecov-mcp'
554
+ sh '''
555
+ simplecov-mcp list --json > coverage.json
556
+ simplecov-mcp --stale error || exit 1
557
+ '''
558
+ }
559
+ }
560
+
561
+ stage('Coverage Report') {
562
+ steps {
563
+ publishHTML([
564
+ reportDir: 'coverage',
565
+ reportFiles: 'index.html',
566
+ reportName: 'Coverage Report'
567
+ ])
568
+ }
569
+ }
570
+ }
571
+ }
572
+ ```
573
+
574
+ ### Pre-commit Hooks
575
+
576
+ ```sh
577
+ #!/bin/bash
578
+ # .git/hooks/pre-commit
579
+
580
+ # Run tests
581
+ bundle exec rspec || exit 1
582
+
583
+ # Validate coverage
584
+ simplecov-mcp --stale error --tracked-globs "lib/**/*.rb" || {
585
+ echo "Coverage validation failed!"
586
+ echo "Re-run tests or review coverage changes."
587
+ exit 1
588
+ }
589
+
590
+ # Check for files with low coverage
591
+ LOW_COVERAGE=$(simplecov-mcp list --json | \
592
+ jq -r '.files[] | select(.percentage < 80) | .file' | \
593
+ head -5)
594
+
595
+ if [ -n "$LOW_COVERAGE" ]; then
596
+ echo "Warning: Files with coverage below 80%:"
597
+ echo "$LOW_COVERAGE"
598
+ fi
599
+ ```
600
+
601
+ ### Using Success Predicates in CI/CD
602
+
603
+ Use `--success-predicate` to enforce coverage policies:
604
+
605
+ **GitHub Actions:**
606
+ ```yaml
607
+ - name: Enforce Coverage Policy
608
+ run: |
609
+ bundle exec rspec
610
+ bundle exec simplecov-mcp --success-predicate coverage_policy.rb
611
+ ```
612
+
613
+ **GitLab CI:**
614
+ ```yaml
615
+ coverage:enforce:
616
+ script:
617
+ - bundle exec rspec
618
+ - bundle exec simplecov-mcp --success-predicate coverage_policy.rb
619
+ ```
620
+
621
+ **Jenkins:**
622
+ ```groovy
623
+ stage('Coverage Policy') {
624
+ steps {
625
+ sh 'bundle exec rspec'
626
+ sh 'bundle exec simplecov-mcp --success-predicate coverage_policy.rb'
627
+ }
628
+ }
629
+ ```
630
+
631
+ The build will fail (exit code 1) if the predicate returns falsy.
632
+
633
+ ---
634
+
635
+ ## Advanced Filtering & Glob Patterns
636
+
637
+ ### Tracked Globs Overview
638
+
639
+ Tracked globs serve two purposes:
640
+ 1. **Filter output** - Only show matching files
641
+ 2. **Validate coverage** - Ensure new files have coverage
642
+
643
+ ### Pattern Syntax
644
+
645
+ Uses Ruby's `File.fnmatch` with extended glob support:
646
+
647
+ ```sh
648
+ # Single directory
649
+ --tracked-globs "lib/**/*.rb"
650
+
651
+ # Multiple patterns
652
+ --tracked-globs "lib/**/*.rb" --tracked-globs "app/**/*.rb"
653
+
654
+ # Exclude patterns (use CLI filtering)
655
+ simplecov-mcp list --json | jq '.files[] | select(.file | test("spec") | not)'
656
+
657
+ # Complex patterns
658
+ --tracked-globs "lib/{models,controllers}/**/*.rb"
659
+ --tracked-globs "app/**/concerns/*.rb"
660
+ ```
661
+
662
+ ### Use Cases
663
+
664
+ **1. Monitor Subsystem Coverage:**
665
+ ```sh
666
+ # API layer only
667
+ simplecov-mcp list --tracked-globs "lib/api/**/*.rb"
668
+
669
+ # Core business logic
670
+ simplecov-mcp list --tracked-globs "lib/domain/**/*.rb"
671
+ ```
672
+
673
+ **2. Ensure New Files Have Coverage:**
674
+ ```sh
675
+ # Fail if any tracked file lacks coverage
676
+ simplecov-mcp --stale error \
677
+ --tracked-globs "lib/features/**/*.rb"
678
+ ```
679
+
680
+ **3. Multi-tier Reporting:**
681
+ ```sh
682
+ # Generate separate reports per layer
683
+ for layer in models views controllers; do
684
+ simplecov-mcp list \
685
+ --tracked-globs "app/${layer}/**/*.rb" \
686
+ --json > "coverage-${layer}.json"
687
+ done
688
+ ```
689
+
690
+ ### Ruby API with Globs
691
+
692
+ ```ruby
693
+ model = SimpleCovMcp::CoverageModel.new
694
+
695
+ # Filter files in output
696
+ api_files = model.all_files(
697
+ tracked_globs: ['lib/api/**/*.rb']
698
+ )
699
+
700
+ # Multi-pattern filtering
701
+ core_files = model.all_files(
702
+ tracked_globs: [
703
+ 'lib/core/**/*.rb',
704
+ 'lib/domain/**/*.rb'
705
+ ]
706
+ )
707
+
708
+ # Validate specific subsystems
709
+ begin
710
+ model.all_files(
711
+ check_stale: true,
712
+ tracked_globs: ['lib/critical/**/*.rb']
713
+ )
714
+ rescue SimpleCovMcp::CoverageDataProjectStaleError => e
715
+ # Handle missing coverage for critical files
716
+ puts "Critical files missing coverage:"
717
+ e.missing_files.each { |f| puts " - #{f}" }
718
+ end
719
+ ```
720
+
721
+ ---
722
+
723
+ ## Performance Optimization
724
+
725
+ ### Minimizing Coverage Reads
726
+
727
+ The `CoverageModel` reads `.resultset.json` once at initialization:
728
+
729
+ ```ruby
730
+ # Good: Single model for multiple queries
731
+ model = SimpleCovMcp::CoverageModel.new
732
+ files = model.all_files
733
+ file1 = model.summary_for('lib/a.rb')
734
+ file2 = model.summary_for('lib/b.rb')
735
+
736
+ # Bad: Re-reads coverage for each operation
737
+ model1 = SimpleCovMcp::CoverageModel.new
738
+ files = model1.all_files
739
+
740
+ model2 = SimpleCovMcp::CoverageModel.new
741
+ file1 = model2.summary_for('lib/a.rb')
742
+ ```
743
+
744
+ ### Batch Processing
745
+
746
+ ```ruby
747
+ # Process multiple files in one pass
748
+ files_to_analyze = ['lib/a.rb', 'lib/b.rb', 'lib/c.rb']
749
+ model = SimpleCovMcp::CoverageModel.new
750
+
751
+ results = files_to_analyze.each_with_object({}) do |file, hash|
752
+ hash[file] = {
753
+ summary: model.summary_for(file),
754
+ uncovered: model.uncovered_for(file)
755
+ }
756
+ rescue SimpleCovMcp::FileError
757
+ hash[file] = { error: 'No coverage' }
758
+ end
759
+ ```
760
+
761
+ ### Filtering Early
762
+
763
+ Use `tracked_globs` to reduce data processing:
764
+
765
+ ```ruby
766
+ # Bad: Filter after loading all data
767
+ all_files = model.all_files
768
+ api_files = all_files.select { |f| f['file'].include?('api') }
769
+
770
+ # Good: Filter during query
771
+ api_files = model.all_files(
772
+ tracked_globs: ['lib/api/**/*.rb']
773
+ )
774
+ ```
775
+
776
+ ### Caching Coverage Models
777
+
778
+ For long-running processes:
779
+
780
+ ```ruby
781
+ class CoverageCache
782
+ def initialize(ttl: 300) # 5 minute cache
783
+ @cache = {}
784
+ @ttl = ttl
785
+ end
786
+
787
+ def model_for(root)
788
+ key = root.to_s
789
+ now = Time.now
790
+
791
+ if @cache[key] && (now - @cache[key][:time] < @ttl)
792
+ @cache[key][:model]
793
+ else
794
+ @cache[key] = {
795
+ model: SimpleCovMcp::CoverageModel.new(root: root),
796
+ time: now
797
+ }
798
+ @cache[key][:model]
799
+ end
800
+ end
801
+ end
802
+
803
+ cache = CoverageCache.new
804
+ model = cache.model_for('/project')
805
+ ```
806
+
807
+ ---
808
+
809
+ ## Custom Output Processing
810
+
811
+ ### Format Conversion
812
+
813
+ **CSV Export:**
814
+ ```ruby
815
+ require 'csv'
816
+
817
+ model = SimpleCovMcp::CoverageModel.new
818
+ files = model.all_files
819
+
820
+ CSV.open('coverage.csv', 'w') do |csv|
821
+ csv << ['File', 'Coverage %', 'Lines Covered', 'Total Lines', 'Stale']
822
+ files.each do |f|
823
+ csv << [
824
+ model.relativize(f)['file'],
825
+ f['percentage'],
826
+ f['covered'],
827
+ f['total'],
828
+ f['stale']
829
+ ]
830
+ end
831
+ end
832
+ ```
833
+
834
+ **HTML Report:**
835
+ ```ruby
836
+ require 'erb'
837
+
838
+ template = ERB.new(<<~HTML)
839
+ <html>
840
+ <head><title>Coverage Report</title></head>
841
+ <body>
842
+ <h1>Coverage Report</h1>
843
+ <table>
844
+ <tr>
845
+ <th>File</th><th>Coverage</th><th>Covered</th><th>Total</th>
846
+ </tr>
847
+ <% files.each do |f| %>
848
+ <tr class="<%= f['percentage'] < 80 ? 'low' : 'ok' %>">
849
+ <td><%= f['file'] %></td>
850
+ <td><%= f['percentage'].round(2) %>%</td>
851
+ <td><%= f['covered'] %></td>
852
+ <td><%= f['total'] %></td>
853
+ </tr>
854
+ <% end %>
855
+ </table>
856
+ </body>
857
+ </html>
858
+ HTML
859
+
860
+ model = SimpleCovMcp::CoverageModel.new
861
+ files = model.relativize(model.all_files)
862
+ File.write('coverage.html', template.result(binding))
863
+ ```
864
+
865
+ ### Annotated Source Output
866
+
867
+ The CLI supports annotated source viewing:
868
+
869
+ ```sh
870
+ # Show uncovered lines with context
871
+ simplecov-mcp uncovered lib/model.rb \
872
+ --source uncovered \
873
+ --source-context 3
874
+
875
+ # Show full file with coverage annotations
876
+ simplecov-mcp uncovered lib/model.rb \
877
+ --source full \
878
+ --source-context 0
879
+ ```
880
+
881
+ **Programmatic Source Annotation:**
882
+ ```ruby
883
+ def annotate_source(file_path)
884
+ model = SimpleCovMcp::CoverageModel.new
885
+ details = model.detailed_for(file_path)
886
+ source_lines = File.readlines(file_path)
887
+
888
+ output = []
889
+ details['lines'].each do |line_data|
890
+ line_num = line_data['line']
891
+ hits = line_data['hits']
892
+ source = source_lines[line_num - 1]
893
+
894
+ marker = case hits
895
+ when nil then ' '
896
+ when 0 then ' ✗ '
897
+ else " #{hits} "
898
+ end
899
+
900
+ output << "#{marker}#{line_num.to_s.rjust(4)}: #{source}"
901
+ end
902
+
903
+ output.join
904
+ end
905
+
906
+ puts annotate_source('lib/model.rb')
907
+ ```
908
+
909
+ ### Integration with Coverage Trackers
910
+
911
+ **Send to Codecov:**
912
+ ```sh
913
+ #!/bin/bash
914
+ bundle exec rspec
915
+ simplecov-mcp list --json > coverage.json
916
+
917
+ # Transform to Codecov format (example)
918
+ jq '{
919
+ coverage: [
920
+ .files[] | {
921
+ name: .file,
922
+ coverage: .percentage
923
+ }
924
+ ]
925
+ }' coverage.json | curl -X POST \
926
+ -H "Authorization: token $CODECOV_TOKEN" \
927
+ -d @- https://codecov.io/upload
928
+ ```
929
+
930
+ **Send to Coveralls:**
931
+ ```ruby
932
+ require 'simplecov_mcp'
933
+ require 'net/http'
934
+ require 'json'
935
+
936
+ model = SimpleCovMcp::CoverageModel.new
937
+ files = model.all_files
938
+
939
+ coveralls_data = {
940
+ repo_token: ENV['COVERALLS_REPO_TOKEN'],
941
+ source_files: files.map { |f|
942
+ {
943
+ name: f['file'],
944
+ coverage: model.raw_for(f['file'])['lines']
945
+ }
946
+ }
947
+ }
948
+
949
+ uri = URI('https://coveralls.io/api/v1/jobs')
950
+ Net::HTTP.post(uri, coveralls_data.to_json, {
951
+ 'Content-Type' => 'application/json'
952
+ })
953
+ ```
954
+
955
+ ---
956
+
957
+ ## Additional Resources
958
+
959
+ - [CLI Usage Guide](CLI_USAGE.md)
960
+ - [Library API Reference](LIBRARY_API.md)
961
+ - [MCP Integration Guide](MCP_INTEGRATION.md)
962
+ - [Error Handling Details](ERROR_HANDLING.md)
963
+ - [Troubleshooting](TROUBLESHOOTING.md)
964
+
965
+ ---
966
+
967
+ **Made with ❤️ for sophisticated Ruby test coverage analysis**