cov-loupe 3.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 (171) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +329 -0
  4. data/docs/dev/ARCHITECTURE.md +80 -0
  5. data/docs/dev/BRANCH_ONLY_COVERAGE.md +158 -0
  6. data/docs/dev/DEVELOPMENT.md +83 -0
  7. data/docs/dev/README.md +10 -0
  8. data/docs/dev/RELEASING.md +146 -0
  9. data/docs/dev/arch-decisions/001-x-arch-decision.md +95 -0
  10. data/docs/dev/arch-decisions/002-x-arch-decision.md +159 -0
  11. data/docs/dev/arch-decisions/003-x-arch-decision.md +165 -0
  12. data/docs/dev/arch-decisions/004-x-arch-decision.md +203 -0
  13. data/docs/dev/arch-decisions/005-x-arch-decision.md +189 -0
  14. data/docs/dev/arch-decisions/README.md +60 -0
  15. data/docs/dev/presentations/cov-loupe-presentation.md +255 -0
  16. data/docs/fixtures/demo_project/README.md +9 -0
  17. data/docs/user/ADVANCED_USAGE.md +777 -0
  18. data/docs/user/CLI_FALLBACK_FOR_LLMS.md +34 -0
  19. data/docs/user/CLI_USAGE.md +750 -0
  20. data/docs/user/ERROR_HANDLING.md +93 -0
  21. data/docs/user/EXAMPLES.md +588 -0
  22. data/docs/user/INSTALLATION.md +130 -0
  23. data/docs/user/LIBRARY_API.md +693 -0
  24. data/docs/user/MCP_INTEGRATION.md +490 -0
  25. data/docs/user/README.md +14 -0
  26. data/docs/user/TROUBLESHOOTING.md +197 -0
  27. data/docs/user/V2-BREAKING-CHANGES.md +472 -0
  28. data/exe/cov-loupe +23 -0
  29. data/lib/cov_loupe/app_config.rb +56 -0
  30. data/lib/cov_loupe/app_context.rb +26 -0
  31. data/lib/cov_loupe/base_tool.rb +102 -0
  32. data/lib/cov_loupe/cli.rb +178 -0
  33. data/lib/cov_loupe/commands/base_command.rb +67 -0
  34. data/lib/cov_loupe/commands/command_factory.rb +45 -0
  35. data/lib/cov_loupe/commands/detailed_command.rb +38 -0
  36. data/lib/cov_loupe/commands/list_command.rb +13 -0
  37. data/lib/cov_loupe/commands/raw_command.rb +38 -0
  38. data/lib/cov_loupe/commands/summary_command.rb +41 -0
  39. data/lib/cov_loupe/commands/totals_command.rb +53 -0
  40. data/lib/cov_loupe/commands/uncovered_command.rb +45 -0
  41. data/lib/cov_loupe/commands/validate_command.rb +60 -0
  42. data/lib/cov_loupe/commands/version_command.rb +33 -0
  43. data/lib/cov_loupe/config_parser.rb +32 -0
  44. data/lib/cov_loupe/constants.rb +22 -0
  45. data/lib/cov_loupe/coverage_reporter.rb +31 -0
  46. data/lib/cov_loupe/error_handler.rb +165 -0
  47. data/lib/cov_loupe/error_handler_factory.rb +31 -0
  48. data/lib/cov_loupe/errors.rb +191 -0
  49. data/lib/cov_loupe/formatters/source_formatter.rb +152 -0
  50. data/lib/cov_loupe/formatters.rb +51 -0
  51. data/lib/cov_loupe/mcp_server.rb +42 -0
  52. data/lib/cov_loupe/mode_detector.rb +56 -0
  53. data/lib/cov_loupe/model.rb +339 -0
  54. data/lib/cov_loupe/option_normalizers.rb +113 -0
  55. data/lib/cov_loupe/option_parser_builder.rb +147 -0
  56. data/lib/cov_loupe/option_parsers/env_options_parser.rb +48 -0
  57. data/lib/cov_loupe/option_parsers/error_helper.rb +110 -0
  58. data/lib/cov_loupe/path_relativizer.rb +64 -0
  59. data/lib/cov_loupe/predicate_evaluator.rb +72 -0
  60. data/lib/cov_loupe/presenters/base_coverage_presenter.rb +42 -0
  61. data/lib/cov_loupe/presenters/coverage_detailed_presenter.rb +14 -0
  62. data/lib/cov_loupe/presenters/coverage_raw_presenter.rb +14 -0
  63. data/lib/cov_loupe/presenters/coverage_summary_presenter.rb +14 -0
  64. data/lib/cov_loupe/presenters/coverage_uncovered_presenter.rb +14 -0
  65. data/lib/cov_loupe/presenters/project_coverage_presenter.rb +50 -0
  66. data/lib/cov_loupe/presenters/project_totals_presenter.rb +27 -0
  67. data/lib/cov_loupe/resolvers/coverage_line_resolver.rb +122 -0
  68. data/lib/cov_loupe/resolvers/resolver_factory.rb +28 -0
  69. data/lib/cov_loupe/resolvers/resultset_path_resolver.rb +76 -0
  70. data/lib/cov_loupe/resultset_loader.rb +131 -0
  71. data/lib/cov_loupe/staleness_checker.rb +247 -0
  72. data/lib/cov_loupe/table_formatter.rb +64 -0
  73. data/lib/cov_loupe/tools/all_files_coverage_tool.rb +51 -0
  74. data/lib/cov_loupe/tools/coverage_detailed_tool.rb +35 -0
  75. data/lib/cov_loupe/tools/coverage_raw_tool.rb +34 -0
  76. data/lib/cov_loupe/tools/coverage_summary_tool.rb +34 -0
  77. data/lib/cov_loupe/tools/coverage_table_tool.rb +50 -0
  78. data/lib/cov_loupe/tools/coverage_totals_tool.rb +44 -0
  79. data/lib/cov_loupe/tools/help_tool.rb +115 -0
  80. data/lib/cov_loupe/tools/uncovered_lines_tool.rb +34 -0
  81. data/lib/cov_loupe/tools/validate_tool.rb +72 -0
  82. data/lib/cov_loupe/tools/version_tool.rb +32 -0
  83. data/lib/cov_loupe/util.rb +88 -0
  84. data/lib/cov_loupe/version.rb +5 -0
  85. data/lib/cov_loupe.rb +140 -0
  86. data/spec/MCP_INTEGRATION_TESTS_README.md +111 -0
  87. data/spec/TIMESTAMPS.md +48 -0
  88. data/spec/all_files_coverage_tool_spec.rb +53 -0
  89. data/spec/app_config_spec.rb +142 -0
  90. data/spec/base_tool_spec.rb +62 -0
  91. data/spec/cli/show_default_report_spec.rb +33 -0
  92. data/spec/cli_enumerated_options_spec.rb +90 -0
  93. data/spec/cli_error_spec.rb +184 -0
  94. data/spec/cli_format_spec.rb +123 -0
  95. data/spec/cli_json_options_spec.rb +50 -0
  96. data/spec/cli_source_spec.rb +44 -0
  97. data/spec/cli_spec.rb +192 -0
  98. data/spec/cli_table_spec.rb +28 -0
  99. data/spec/cli_usage_spec.rb +42 -0
  100. data/spec/commands/base_command_spec.rb +107 -0
  101. data/spec/commands/command_factory_spec.rb +76 -0
  102. data/spec/commands/detailed_command_spec.rb +34 -0
  103. data/spec/commands/list_command_spec.rb +28 -0
  104. data/spec/commands/raw_command_spec.rb +69 -0
  105. data/spec/commands/summary_command_spec.rb +34 -0
  106. data/spec/commands/totals_command_spec.rb +34 -0
  107. data/spec/commands/uncovered_command_spec.rb +55 -0
  108. data/spec/commands/validate_command_spec.rb +213 -0
  109. data/spec/commands/version_command_spec.rb +38 -0
  110. data/spec/constants_spec.rb +61 -0
  111. data/spec/cov_loupe/formatters/source_formatter_spec.rb +267 -0
  112. data/spec/cov_loupe/formatters_spec.rb +76 -0
  113. data/spec/cov_loupe/presenters/base_coverage_presenter_spec.rb +79 -0
  114. data/spec/cov_loupe_model_spec.rb +454 -0
  115. data/spec/cov_loupe_module_spec.rb +37 -0
  116. data/spec/cov_loupe_opts_spec.rb +185 -0
  117. data/spec/coverage_reporter_spec.rb +102 -0
  118. data/spec/coverage_table_tool_spec.rb +59 -0
  119. data/spec/coverage_totals_tool_spec.rb +37 -0
  120. data/spec/error_handler_spec.rb +197 -0
  121. data/spec/error_mode_spec.rb +139 -0
  122. data/spec/errors_edge_cases_spec.rb +312 -0
  123. data/spec/errors_stale_spec.rb +83 -0
  124. data/spec/file_based_mcp_tools_spec.rb +99 -0
  125. data/spec/fixtures/project1/lib/bar.rb +5 -0
  126. data/spec/fixtures/project1/lib/foo.rb +6 -0
  127. data/spec/help_tool_spec.rb +26 -0
  128. data/spec/integration_spec.rb +789 -0
  129. data/spec/logging_fallback_spec.rb +128 -0
  130. data/spec/mcp_logging_spec.rb +44 -0
  131. data/spec/mcp_server_integration_spec.rb +23 -0
  132. data/spec/mcp_server_spec.rb +106 -0
  133. data/spec/mode_detector_spec.rb +153 -0
  134. data/spec/model_error_handling_spec.rb +269 -0
  135. data/spec/model_staleness_spec.rb +79 -0
  136. data/spec/option_normalizers_spec.rb +203 -0
  137. data/spec/option_parsers/env_options_parser_spec.rb +221 -0
  138. data/spec/option_parsers/error_helper_spec.rb +222 -0
  139. data/spec/path_relativizer_spec.rb +98 -0
  140. data/spec/presenters/coverage_detailed_presenter_spec.rb +19 -0
  141. data/spec/presenters/coverage_raw_presenter_spec.rb +15 -0
  142. data/spec/presenters/coverage_summary_presenter_spec.rb +15 -0
  143. data/spec/presenters/coverage_uncovered_presenter_spec.rb +16 -0
  144. data/spec/presenters/project_coverage_presenter_spec.rb +87 -0
  145. data/spec/presenters/project_totals_presenter_spec.rb +144 -0
  146. data/spec/resolvers/coverage_line_resolver_spec.rb +282 -0
  147. data/spec/resolvers/resolver_factory_spec.rb +61 -0
  148. data/spec/resolvers/resultset_path_resolver_spec.rb +60 -0
  149. data/spec/resultset_loader_spec.rb +167 -0
  150. data/spec/shared_examples/README.md +115 -0
  151. data/spec/shared_examples/coverage_presenter_examples.rb +66 -0
  152. data/spec/shared_examples/file_based_mcp_tools.rb +179 -0
  153. data/spec/shared_examples/formatted_command_examples.rb +64 -0
  154. data/spec/shared_examples/mcp_tool_text_json_response.rb +16 -0
  155. data/spec/spec_helper.rb +127 -0
  156. data/spec/staleness_checker_spec.rb +374 -0
  157. data/spec/staleness_more_spec.rb +42 -0
  158. data/spec/support/cli_helpers.rb +22 -0
  159. data/spec/support/control_flow_helpers.rb +20 -0
  160. data/spec/support/fake_mcp.rb +40 -0
  161. data/spec/support/io_helpers.rb +29 -0
  162. data/spec/support/mcp_helpers.rb +35 -0
  163. data/spec/support/mcp_runner.rb +66 -0
  164. data/spec/support/mocking_helpers.rb +30 -0
  165. data/spec/table_format_spec.rb +70 -0
  166. data/spec/tools/validate_tool_spec.rb +132 -0
  167. data/spec/tools_error_handling_spec.rb +130 -0
  168. data/spec/util_spec.rb +154 -0
  169. data/spec/version_spec.rb +123 -0
  170. data/spec/version_tool_spec.rb +141 -0
  171. metadata +290 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 59634d5c998df34ccad24447f4cd4d64d6684f7585e2e8749905344b6e532b81
4
+ data.tar.gz: 298f4cab2b8cbc0b837d5a47504851fcc6d5137202daff68f640e6e92ca04c0d
5
+ SHA512:
6
+ metadata.gz: c4cbe5d778fde90a412d27710eb9c022b3be1e77dd520cb3b263076e9ee36f5ce8e6bd65a2654048ca22e1073f77ec81dcf79e2615babeace4d3c41678ace3b2
7
+ data.tar.gz: 39de9d95a5665fb0caafb78090768cfa0f5b72b4572c312a340f8a23f1880dff24ffdf76374206610f5d31426628025a042db31e1c97541b7da136478984e1e3
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Keith Bennett
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,329 @@
1
+ <!-- Invisible SEO-friendly H1 -->
2
+ <h1 style="position:absolute; left:-9999px; top:auto; width:1px; height:1px; overflow:hidden;">
3
+ CovLoupe
4
+ </h1>
5
+
6
+ <p style="text-align:center; margin-bottom:-36px;">
7
+ <img src="dev/images/cov-loupe-logo.png" alt="CovLoupe logo" width="560">
8
+ </p>
9
+
10
+ <p style="text-align:center;">
11
+ An MCP server, command line utility, and library for Ruby SimpleCov test coverage analysis.
12
+
13
+
14
+ [![Gem Version](https://badge.fury.io/rb/cov-loupe.svg)](https://badge.fury.io/rb/cov-loupe)
15
+
16
+ ## What is cov-loupe?
17
+
18
+ **cov-loupe** makes SimpleCov coverage data queryable and actionable through three interfaces:
19
+
20
+ - **MCP server** - Lets AI assistants analyze your coverage
21
+ - **CLI** - Fast command-line coverage reports and queries
22
+ - **Ruby library** - Programmatic API for custom tooling
23
+
24
+ Works with any SimpleCov-generated `.resultset.json` file—no runtime dependency on your test suite.
25
+
26
+ ### Key Features
27
+
28
+ - ✅ **Multiple interfaces** - MCP server, CLI, and Ruby API
29
+ - **Annotated source code** - `--source full|uncovered` with `--context-lines N` for context lines
30
+ - ✅ **Staleness detection** - Identify outdated coverage (missing files, timestamp mismatches, line count changes)
31
+ - ✅ **Multi-suite support** - Automatic merging of multiple test suites (RSpec + Cucumber, etc.)
32
+ - ✅ **Flexible path resolution** - Works with absolute or relative paths
33
+ - ✅ **Comprehensive error handling** - Context-aware messages for each mode
34
+ - ⚠️ **Branch coverage limitation** - Branch-level metrics are collapsed to per-line totals. Use native SimpleCov reports for branch-by-branch analysis.
35
+
36
+ ### Practical Use Cases
37
+
38
+ - Query coverage data from AI assistants, e.g.:
39
+ - "Using cov-loupe, analyze test coverage data and write a report to a markdown file containing a free text analysis of each issue and then two tables, one sorted in descending order of urgency, the other in ascending order of level of effort."
40
+ - "Using cov-loupe, generate a table of directories and their average coverage rates, in ascending order of coverage."
41
+ - Find files with the lowest coverage
42
+ - Investigate specific files or directories
43
+ - Generate CI/CD coverage reports
44
+ - Create custom pass/fail predicates for scripts and CI - use the library API or CLI JSON output to implement arbitrarily complex coverage rules beyond simple thresholds (e.g., require higher coverage for critical paths, exempt test utilities, track coverage trends)
45
+
46
+ ## Quick Start
47
+
48
+ ### Installation
49
+
50
+ ```sh
51
+ gem install cov-loupe
52
+ ```
53
+
54
+ ### Generate Coverage Data
55
+
56
+ ```sh
57
+ # Run your tests with SimpleCov enabled
58
+ bundle exec rspec # or your test command
59
+
60
+ # Verify coverage was generated
61
+ ls -l coverage/.resultset.json
62
+ ```
63
+
64
+ ### Basic Usage
65
+
66
+ **CLI - View Coverage Table:**
67
+ ```sh
68
+ cov-loupe
69
+ ```
70
+
71
+ **CLI - Check Specific File:**
72
+ ```sh
73
+ cov-loupe summary lib/cov_loupe/model.rb
74
+ cov-loupe uncovered lib/cov_loupe/cli.rb
75
+ ```
76
+
77
+ **CLI - Find the Project Homepage Fast:**
78
+ Run `cov-loupe -h` and the banner's second line shows the repository URL. Some terminal applications (e.g. iTerm2) will enable direct clicking the link using modifier keys such as `Cmd` or `Alt`.
79
+ ```
80
+ Usage: cov-loupe [options] [subcommand] [args]
81
+ Repository: https://github.com/keithrbennett/cov-loupe # <--- Project URL ---
82
+ ```
83
+
84
+ **Ruby Library:**
85
+ ```ruby
86
+ require "cov_loupe"
87
+
88
+ model = CovLoupe::CoverageModel.new
89
+ files = model.all_files
90
+ # => [{ "file" => "lib/cov_loupe/model.rb", "covered" => 114, "total" => 118, "percentage" => 96.61, "stale" => false }, ...]
91
+
92
+ summary = model.summary_for("lib/cov_loupe/model.rb")
93
+ # => { "file" => "lib/cov_loupe/model.rb", "summary" => { "covered" => 114, "total" => 118, "percentage" => 96.61 }, "stale" => false }
94
+ ```
95
+
96
+ **MCP Server:**
97
+ See [MCP Integration Guide](docs/user/MCP_INTEGRATION.md) for AI assistant setup.
98
+
99
+ ## Multi-Suite Coverage Merging
100
+
101
+ ### How It Works
102
+
103
+ When a `.resultset.json` file contains multiple test suites (e.g., RSpec + Cucumber), `cov-loupe` automatically merges them using SimpleCov's combine logic. All covered files from every suite become available to the CLI, library, and MCP tools.
104
+
105
+ **Performance:** Single-suite projects avoid loading SimpleCov at runtime. Multi-suite resultsets trigger a lazy SimpleCov load only when needed, keeping the tool fast for the simpler coverage configurations.
106
+
107
+ ### Current Limitations
108
+
109
+ **Staleness checks:** When suites are merged, we keep a single "latest suite" timestamp. This matches prior behavior but may under-report stale files if only some suites were re-run after a change. A per-file timestamp refinement is planned. Until then, consider multi-suite staleness checks advisory rather than definitive.
110
+
111
+ **Multiple resultset files:** Only suites stored inside a *single* `.resultset.json` are merged automatically. If your project produces separate resultset files (e.g., different CI jobs writing `coverage/job1/.resultset.json`, `coverage/job2/.resultset.json`), you must merge them yourself before pointing `cov-loupe` at the combined file.
112
+
113
+ ## Documentation
114
+
115
+ **Getting Started:**
116
+ - [Installation](docs/user/INSTALLATION.md) - Setup for different environments
117
+ - [CLI Usage](docs/user/CLI_USAGE.md) - Command-line reference
118
+ - [Examples](docs/user/EXAMPLES.md) - Common use cases
119
+
120
+ **Advanced Usage:**
121
+ - [MCP Integration](docs/user/MCP_INTEGRATION.md) - AI assistant configuration
122
+ - [CLI Fallback for LLMs](docs/user/CLI_FALLBACK_FOR_LLMS.md) - Using CLI when MCP isn't available
123
+ - [Library API](docs/user/LIBRARY_API.md) - Ruby API documentation
124
+ - [Advanced Usage](docs/user/ADVANCED_USAGE.md) - Staleness detection, error modes, path resolution
125
+ - [Error Handling](docs/user/ERROR_HANDLING.md) - Error modes and exceptions
126
+
127
+ **Reference:**
128
+ - [Architecture](docs/dev/ARCHITECTURE.md) - Design and internals
129
+ - [Branch Coverage](docs/dev/BRANCH_ONLY_COVERAGE.md) - Branch coverage limitations
130
+ - [Troubleshooting](docs/user/TROUBLESHOOTING.md) - Common issues
131
+ - [Development](docs/dev/DEVELOPMENT.md) - Contributing guide
132
+
133
+ ## Requirements
134
+
135
+ - **Ruby >= 3.2** (required by `mcp` gem dependency)
136
+ - SimpleCov-generated `.resultset.json` file
137
+ - `simplecov` gem >= 0.21
138
+
139
+ ## Configuring the Resultset
140
+
141
+ `cov-loupe` automatically searches for `.resultset.json` in standard locations (`coverage/.resultset.json`, `.resultset.json`, `tmp/.resultset.json`). For non-standard locations:
142
+
143
+ ```sh
144
+ # Command-line option (highest priority)
145
+ cov-loupe --resultset /path/to/your/coverage
146
+
147
+ # Environment variable (project-wide default)
148
+ export COV_LOUPE_OPTS="--resultset /path/to/your/coverage"
149
+
150
+ # MCP server configuration
151
+ # Add to your MCP client config:
152
+ # "args": ["--resultset", "/path/to/your/coverage"]
153
+ ```
154
+
155
+ See [CLI Usage Guide](docs/user/CLI_USAGE.md#-r---resultset-path) for complete details.
156
+
157
+
158
+
159
+ ## Common Workflows
160
+
161
+ ### Find Coverage Gaps
162
+
163
+ ```sh
164
+ # Files with worst coverage
165
+ cov-loupe -o d list # -o = --sort-order, d = descending (worst at end)
166
+ cov-loupe list | less # display table in pager, worst files first
167
+ cov-loupe list | head -10 # truncate the table
168
+
169
+ # Specific directory
170
+ cov-loupe -g "lib/cov_loupe/tools/**/*.rb" list # -g = --tracked-globs
171
+
172
+ # Export for analysis
173
+ cov-loupe -fJ list > coverage-report.json
174
+ ```
175
+
176
+ ### Working with JSON Output
177
+
178
+ The `-fJ` flag enables programmatic processing of coverage data using command-line JSON tools.
179
+
180
+ **Using jq:**
181
+ ```sh
182
+ # Filter files below 80% coverage
183
+ cov-loupe -fJ list | jq '.files[] | select(.percentage < 80)'
184
+ ```
185
+
186
+ **Using Ruby one-liners:**
187
+ ```sh
188
+ # Count files below threshold
189
+ cov-loupe -fJ list | ruby -r json -e '
190
+ puts JSON.parse($stdin.read)["files"].count { |f| f["percentage"] < 80 }
191
+ '
192
+ ```
193
+
194
+ **Using rexe:**
195
+
196
+ [rexe](https://github.com/keithrbennett/rexe) is a Ruby gem that enables shorter Ruby command lines by providing command-line options for input and output formats, plus other conveniences. It eliminates the need for explicit JSON parsing and formatting code.
197
+
198
+ Install: `gem install rexe`
199
+
200
+ ```sh
201
+ # Filter files below 80% coverage with pretty-printed JSON output
202
+ cov-loupe -fJ list | rexe -ij -mb -oJ 'self["files"].select { |f| f["percentage"] < 80 }'
203
+
204
+ # Count files below threshold
205
+ cov-loupe -fJ list | rexe -ij -mb -op 'self["files"].count { |f| f["percentage"] < 80 }'
206
+
207
+ # Human-readable output with AwesomePrint
208
+ cov-loupe -fJ list | rexe -ij -mb -oa 'self["files"].first(3)'
209
+ ```
210
+
211
+ With rexe's `-ij -mb` options, `self` automatically becomes the parsed JSON object. The same holds true for JSON output -- using `-oJ` produces pretty-printed JSON without explicit formatting calls. Rexe also supports YAML input/output (`-iy`, `-oy`) and AwesomePrint output (`-oa`) for human consumption.
212
+
213
+ Run `rexe -h` to see all available options, or visit the [rexe project page](https://github.com/keithrbennett/rexe) for more examples.
214
+
215
+ For comprehensive JSON processing examples, see [docs/user/EXAMPLES.md](docs/user/EXAMPLES.md).
216
+
217
+ ### CI/CD Integration
218
+
219
+ ```sh
220
+ # Fail build if coverage is stale
221
+ cov-loupe --staleness error || exit 1
222
+
223
+ # Generate coverage report artifact
224
+ cov-loupe -fJ list > artifacts/coverage.json
225
+ ```
226
+
227
+ ### Investigate Specific Files
228
+
229
+ ```sh
230
+ # Quick summary
231
+ cov-loupe summary lib/cov_loupe/model.rb
232
+
233
+ # See uncovered lines
234
+ cov-loupe uncovered lib/cov_loupe/cli.rb
235
+
236
+ # View in context
237
+ cov-loupe -s u -c 3 uncovered lib/cov_loupe/cli.rb # -s = --source (u = uncovered), -c = --context-lines
238
+
239
+ # Detailed hit counts
240
+ cov-loupe detailed lib/cov_loupe/util.rb
241
+
242
+ # Project totals
243
+ cov-loupe totals
244
+ cov-loupe -fJ totals
245
+ ```
246
+
247
+ ## Commands and Tools
248
+
249
+ **CLI Subcommands:** `list`, `summary`, `uncovered`, `detailed`, `raw`, `totals`, `validate`, `version`
250
+
251
+ **MCP Tools:** `coverage_summary_tool`, `coverage_detailed_tool`, `coverage_raw_tool`, `uncovered_lines_tool`, `all_files_coverage_tool`, `coverage_totals_tool`, `coverage_table_tool`, `validate_tool`, `help_tool`, `version_tool`
252
+
253
+ 📖 **See also:**
254
+ - [CLI Usage Guide](docs/user/CLI_USAGE.md) - Complete command-line reference
255
+ - [MCP Integration Guide](docs/user/MCP_INTEGRATION.md#available-mcp-tools) - MCP tools documentation
256
+
257
+ ## Troubleshooting
258
+
259
+ - **"command not found"** - See [Installation Guide](docs/user/INSTALLATION.md#path-configuration)
260
+ - **"cannot load such file -- mcp"** - Requires Ruby >= 3.2. Verify: `ruby -v`
261
+ - **"Could not find .resultset.json"** - Ensure SimpleCov is configured in your test suite, then run tests to generate coverage. See the [Configuring the Resultset](#configuring-the-resultset) section for more details.
262
+ - **MCP server won't connect** - Check PATH and Ruby version in [MCP Troubleshooting](docs/user/MCP_INTEGRATION.md#troubleshooting)
263
+ - **RVM in sandboxed environments (macOS)** - RVM requires `/bin/ps` which may be blocked by sandbox restrictions. Use rbenv or chruby instead.
264
+
265
+ For more detailed help, see the full [Troubleshooting Guide](docs/user/TROUBLESHOOTING.md).
266
+
267
+ ## Development
268
+
269
+ ```sh
270
+ # Clone and setup
271
+ git clone https://github.com/keithrbennett/cov-loupe.git
272
+ cd cov-loupe
273
+ bundle install
274
+
275
+ # Run tests
276
+ bundle exec rspec
277
+
278
+ # Test locally
279
+ bundle exec exe/cov-loupe
280
+
281
+ # Build and install
282
+ gem build cov-loupe.gemspec
283
+ gem install cov-loupe-*.gem
284
+ ```
285
+
286
+ See [docs/dev/DEVELOPMENT.md](docs/dev/DEVELOPMENT.md) for more.
287
+
288
+ ## SimpleCov Dependency
289
+
290
+ `cov-loupe` declares a runtime dependency on `simplecov` (>= 0.21) to support multi-suite merging using SimpleCov's combine helpers. The dependency is lazy-loaded only when needed, ensuring fast startup for single-suite projects.
291
+
292
+ ## Contributing
293
+
294
+ Contributions are welcome! Please:
295
+
296
+ 1. Fork the repository
297
+ 2. Create a feature branch
298
+ 3. Add tests for new functionality
299
+ 4. Ensure all tests pass (`bundle exec rspec`)
300
+ 5. Submit a pull request
301
+
302
+ ## License
303
+
304
+ MIT License - see [LICENSE](LICENSE) file for details.
305
+
306
+ ## Links
307
+
308
+ - **GitHub:** https://github.com/keithrbennett/cov-loupe
309
+ - **RubyGems:** https://rubygems.org/gems/cov-loupe
310
+ - **Issues:** https://github.com/keithrbennett/cov-loupe/issues
311
+ - **Changelog:** [RELEASE_NOTES.md](RELEASE_NOTES.md)
312
+
313
+ ## Related Files
314
+
315
+ - [CLAUDE.md](CLAUDE.md) - Claude Code integration notes
316
+ - [AGENTS.md](AGENTS.md) - AI agent configuration
317
+ - [GEMINI.md](GEMINI.md) - Gemini-specific guidance
318
+
319
+ ---
320
+
321
+ ## Next Steps
322
+
323
+ 📦 **Install:** `gem install cov-loupe`
324
+
325
+ 📖 **Read:** [CLI Usage Guide](docs/user/CLI_USAGE.md) | [MCP Integration](docs/user/MCP_INTEGRATION.md)
326
+
327
+ 🐛 **Report issues:** [GitHub Issues](https://github.com/keithrbennett/cov-loupe/issues)
328
+
329
+ ⭐ **Star the repo** if you find it useful!
@@ -0,0 +1,80 @@
1
+ # Architecture
2
+
3
+ [Back to main README](../README.md)
4
+
5
+ cov-loupe is organized around a single coverage data model that feeds three delivery channels: a command-line interface, an MCP server for LLM agents, and a light-weight Ruby API. The codebase is intentionally modular—shared logic for loading, normalizing, and validating SimpleCov data lives in `lib/cov_loupe/`, while adapters wrap that core for each runtime mode.
6
+
7
+ ## Runtime Entry Points
8
+
9
+ - **Executable** – `exe/cov-loupe` bootstraps the gem, enforces Ruby >= 3.2, and delegates to `CovLoupe.run(ARGV)`.
10
+ - **Mode Negotiation** – `CovLoupe.run` inspects environment defaults from `COV_LOUPE_OPTS`, checks for CLI subcommands, and defaults to CLI mode when STDIN is a TTY. Otherwise it instantiates `CovLoupe::MCPServer` for MCP protocol communication over STDIO.
11
+ - **Embedded Usage** – Applications embed the gem by instantiating `CovLoupe::CoverageModel` directly, optionally wrapping work in `CovLoupe.with_context` to install a library-oriented error handler.
12
+
13
+ ## Coverage Data Pipeline
14
+
15
+ 1. **Resultset discovery** – The tool locates the `.resultset.json` file by checking a series of default paths or by using a path specified by the user. For a detailed explanation of the configuration options, see the [Configuring the Resultset](../README.md#configuring-the-resultset) section in the main README.
16
+ 2. **Parsing and normalization** – `CoverageModel` loads the chosen resultset once, extracts all test suites that expose `coverage` data (e.g., "RSpec", "Minitest"), merges them if multiple suites exist, and maps all file keys to absolute paths anchored at the configured project root. Timestamps are cached for staleness checks.
17
+ 3. **Path relativizing** – `PathRelativizer` produces relative paths for user-facing payloads without mutating the canonical data. Tool responses pass through `CoverageModel#relativize` before leaving the process.
18
+ 4. **Derived metrics** – `CovUtil.summary`, `CovUtil.uncovered`, and `CovUtil.detailed` compute coverage stats from the raw `lines` arrays. `CoverageModel` exposes `summary_for`, `uncovered_for`, `detailed_for`, and `raw_for` helpers that wrap these utilities.
19
+ 5. **Staleness detection** – `StalenessChecker` compares source mtimes/line counts to coverage metadata. CLI flags and MCP arguments can promote warnings to hard failures (`--staleness error`) or simply mark rows as stale for display.
20
+
21
+ ## Interfaces
22
+
23
+ ### CLI (`CovLoupe::CoverageCLI`)
24
+
25
+ - Builds on Ruby’s `OptionParser`, with global options such as `--resultset`, `--staleness`, `-fJ`, and `--source` modes.
26
+ - Subcommands (`list`, `summary`, `raw`, `uncovered`, `detailed`, `version`) translate to calls on `CoverageModel`.
27
+ - Uses `ErrorHandlerFactory.for_cli` to convert unexpected exceptions into friendly user messages while honoring `--error-mode`.
28
+ - Formatting logic (tables, JSON) lives in the model to keep presentation consistent with MCP responses.
29
+
30
+ ### MCP Server (`CovLoupe::MCPServer`)
31
+
32
+ - Assembles a list of tool classes and mounts them in `MCP::Server` using STDIO transport.
33
+ - Relies on the same core model; each tool instance recreates `CoverageModel` with the arguments provided by the MCP client, keeping the server stateless between requests.
34
+ - Error handling delegates to `BaseTool.handle_mcp_error`, which swaps in the MCP-specific handler and emits `MCP::Tool::Response` objects.
35
+
36
+ ### Library API
37
+
38
+ - Consuming code instantiates `CoverageModel` directly for fine-grained control over coverage queries.
39
+ - Use `CovLoupe::ErrorHandlerFactory.for_library` together with `CovLoupe.with_context` when an embedded caller wants to suppress CLI-friendly error logging.
40
+
41
+ ## MCP Tool Stack
42
+
43
+ - `CovLoupe::BaseTool` centralizes JSON schema definitions, error conversion, and response serialization for the MCP protocol.
44
+ - Individual tools reside in `lib/cov_loupe/tools/` and follow a consistent shape: define an input schema, call into `CoverageModel`, then serialize via `respond_json`. Examples include `AllFilesCoverageTool`, `CoverageSummaryTool`, and `UncoveredLinesTool`.
45
+ - Tools are registered in `CovLoupe::MCPServer#run`. Adding a new tool only requires creating a subclass and appending it to that list.
46
+
47
+ ## Error Handling & Logging
48
+
49
+ - Custom exceptions under `lib/cov_loupe/errors.rb` capture context for configuration issues, missing files, stale coverage, and general runtime errors. Each implements `#user_friendly_message` for consistent UX.
50
+ - `CovLoupe::ErrorHandler` encapsulates logging and severity decisions. Modes (`:off`, `:log`, `:debug`) control whether errors are recorded and whether stack traces are included.
51
+ - Runtime configuration (error handlers, log destinations) flows through `CovLoupe::AppContext`. Entry points push a context with `CovLoupe.with_context`, which stores the active configuration in a thread-local slot (`CovLoupe.context`). Nested calls automatically restore the previous context when the block exits, ensuring isolation even when multiple callers share a process or thread.
52
+ - Two helper accessors clarify intent:
53
+ - `CovLoupe.default_log_file` / `default_log_file=` adjust the baseline log sink that future contexts inherit.
54
+ - `CovLoupe.active_log_file` / `active_log_file=` mutate only the current context (or create one on demand) so the change applies immediately without touching the default.
55
+ - `ErrorHandlerFactory` wires the appropriate handler per runtime: CLI, MCP server, or embedded library, each of which installs its handler inside a fresh `AppContext` before executing user work.
56
+ - Diagnostics are written via `CovUtil.log` to `cov_loupe.log` in the current directory by default; override with CLI `--log-file`, set `CovLoupe.default_log_file` for future contexts, or temporarily tweak `CovLoupe.active_log_file` when a caller needs a different destination mid-run.
57
+
58
+ ## Configuration Surface
59
+
60
+ - **Environment defaults** – `COV_LOUPE_OPTS` applies baseline CLI flags before parsing the actual command line.
61
+ - **Resultset overrides** – The location of the `.resultset.json` file can be specified via CLI options or in the MCP configuration. See [Configuring the Resultset](../README.md#configuring-the-resultset) for details.
62
+ - **Tracked globs** – Glob patterns (e.g., `lib/**/*.rb`) that specify which files should have coverage. When provided, SimpleCov MCP alerts you if any matching files are missing from the coverage data, helping catch untested files that were added to the project but never executed during test runs.
63
+ - **Colorized source** – CLI-only flags (`--source`, `--source-context`, `--color`) enhance human-readable reports when working locally.
64
+
65
+ ## Repository Layout Highlights
66
+
67
+ - `lib/cov_loupe/` – Core runtime (model, utilities, error handling, CLI, MCP server, tools).
68
+ - `lib/cov_loupe.rb` – Primary public entry point required by gem consumers.
69
+ - `docs/` – Audience-specific guides (`docs/user` for usage, `docs/dev` for contributors).
70
+ - `spec/` – RSpec suite with fixtures under `spec/fixtures/` for deterministic coverage data.
71
+
72
+ ## Extending the System With a New Tool or Metric
73
+
74
+ 1. Add or update data processing inside `CoverageModel` or `CovUtil` when a new metric is needed.
75
+ 2. Surface that metric through all interfaces: add a CLI option/subcommand, create an MCP tool, and expose a library helper method.
76
+ 3. Register the new tool in `MCPServer` and update CLI option parsing in `CoverageCLI`.
77
+ 4. Provide tests under `spec/` mirroring the lib path (`spec/lib/cov_loupe/..._spec.rb`).
78
+ 5. Update documentation to reflect the new capability.
79
+
80
+ By funnelling every interface through the shared `CoverageModel`, cov-loupe guarantees that CLI users, MCP clients, and embedding libraries all observe identical coverage semantics and staleness rules, while still allowing each adapter to tailor presentation and error handling to its audience.
@@ -0,0 +1,158 @@
1
+ # Branch-Only Coverage Handling
2
+
3
+ [Back to main README](../README.md)
4
+
5
+ > **Note:** This document is for **developers working on SimpleCov MCP**, not for users of the gem.
6
+ > If you're a user, you don't need to read this - branch-only coverage "just works" automatically.
7
+ > This explains the internal implementation for contributors who need to maintain or modify the code.
8
+
9
+ ## The Problem This Solves for SimpleCov MCP
10
+
11
+ SimpleCov can be configured to track different types of coverage:
12
+ - **Line coverage**: Which lines of code were executed
13
+ - **Branch coverage**: Which paths through if/else, case/when, etc. were taken
14
+ - **Both**: Track both lines and branches together
15
+
16
+ When users configure SimpleCov to track **only branch coverage** (without line coverage),
17
+ the `.resultset.json` file has a different structure: the `lines` array is `null`,
18
+ and only the `branches` data exists.
19
+
20
+ **This breaks SimpleCov MCP** because our entire tool is designed around the `lines` array:
21
+ - Our MCP tools (`coverage_summary_tool`, `uncovered_lines_tool`, etc.) expect line data
22
+ - Our CLI commands expect line data
23
+ - Our `CoverageModel` API expects line data
24
+
25
+ Rather than failing with "no coverage data found" errors, SimpleCov MCP **automatically
26
+ converts branch coverage into line coverage** so all our tools continue to work seamlessly
27
+ for projects using branch-only configuration.
28
+
29
+ ## What Branch-Only Coverage Data Looks Like
30
+
31
+ When SimpleCov writes branch-only coverage, the `.resultset.json` file looks different
32
+ from the normal line coverage format. Here's an example:
33
+
34
+ ```json
35
+ {
36
+ "lib/example.rb": {
37
+ "lines": null,
38
+ "branches": {
39
+ "[:if, 0, 12, 4, 20, 29]": {
40
+ "[:then, 1, 13, 6, 13, 18]": 4,
41
+ "[:else, 2, 15, 6, 15, 16]": 0
42
+ }
43
+ }
44
+ }
45
+ }
46
+ ```
47
+
48
+ ### Understanding the Branch Data Structure
49
+
50
+ The `branches` hash uses a nested structure:
51
+
52
+ **Outer key** (e.g., `"[:if, 0, 12, 4, 20, 29]"`):
53
+ - This is either a Ruby array or its stringified version
54
+ - It describes where the branch decision happens (the `if` statement itself)
55
+ - Format: `[type, id, start_line, start_col, end_line, end_col]`
56
+ - Position 0: Branch type (`:if`, `:case`, `:unless`, etc.)
57
+ - Position 1: SimpleCov's internal ID for this branch
58
+ - **Position 2: The line number** ← This is what we need for line coverage!
59
+ - Position 3: Starting column
60
+ - Position 4: Ending line
61
+ - Position 5: Ending column
62
+
63
+ **Nested hash** (the value of the outer key):
64
+ - Contains each possible path through the branch
65
+ - Keys are branch targets like `"[:then, 1, 13, 6, 13, 18]"` or `"[:else, 2, 15, 6, 15, 16]"`
66
+ - Each target has the same array format as the outer key
67
+ - Values are integers showing how many times that path was executed
68
+ - `4` means the `then` branch ran 4 times
69
+ - `0` means the `else` branch never ran
70
+
71
+ **Why this matters for our conversion:**
72
+ - We extract the line number (position 2) from each branch target
73
+ - We sum up all the execution counts for branches on the same line
74
+ - This gives us enough information to build a line coverage array
75
+
76
+ ## How SimpleCov MCP Converts Branch Coverage to Line Coverage
77
+
78
+ Since all of SimpleCov MCP's tools expect a `lines` array, we need to build one from
79
+ the `branches` data. This happens automatically in the `CoverageLineResolver` whenever
80
+ it detects that `lines` is `null` but `branches` exists.
81
+
82
+ ### The Conversion Algorithm
83
+
84
+ Here's how we transform branch data into line data:
85
+
86
+ **Step 1: Parse the branch keys**
87
+ - Branch keys can be either raw Ruby arrays or stringified (e.g., `"[:if, 0, 12, 4, 20, 29]"`)
88
+ - We handle both formats by parsing the string format when needed
89
+
90
+ **Step 2: Extract line numbers**
91
+ - From each branch target tuple, we pull out position 2 (the line number)
92
+ - Invalid or malformed tuples are silently skipped
93
+
94
+ **Step 3: Sum hits per line**
95
+ - Multiple branches can exist on the same line (e.g., nested ifs, ternaries)
96
+ - We add up all the execution counts for branches on the same line
97
+ - Example: if line 15 has a `then` branch hit 4 times and an `else` branch hit 2 times,
98
+ that line gets a total of 6 hits
99
+
100
+ **Step 4: Build the line array**
101
+ - We create an array with length equal to the highest line number found
102
+ - Each array position represents a line: `array[0]` = line 1, `array[1]` = line 2, etc.
103
+ - Positions get the summed hit count for that line, or `nil` if no branches appeared there
104
+
105
+ **Result:** We now have a `lines` array that looks exactly like SimpleCov's normal
106
+ format, so all our tools (`summary`, `uncovered`, staleness checks, etc.) work without
107
+ knowing branch coverage was involved.
108
+
109
+ ## Where to Find the Code
110
+
111
+ If you need to modify or debug the branch coverage conversion, here's where everything lives:
112
+
113
+ ### Implementation
114
+ **`lib/cov_loupe/resolvers/coverage_line_resolver.rb`**
115
+ - Line 59-66: `lines_from_entry` - Detects when to synthesize line data
116
+ - Line 68-103: `synthesize_lines_from_branches` - The main conversion logic
117
+ - Line 105-123: `extract_line_number` - Parses line numbers from branch tuples
118
+
119
+ This file is referenced in the code comments at line 79.
120
+
121
+ ### Tests
122
+ **`spec/resolvers/coverage_line_resolver_spec.rb`**
123
+ - Contains tests specifically for the resolver and branch synthesis
124
+
125
+ **`spec/cov_loupe_model_spec.rb`**
126
+ - Integration tests showing how the model uses synthesized line data
127
+
128
+ ### Test Data
129
+ **`spec/fixtures/branch_only_project/coverage/.resultset.json`**
130
+ - Example of real branch-only coverage data used in tests
131
+ - Useful reference when debugging issues or adding new test cases
132
+
133
+ ## Important Implementation Notes
134
+
135
+ ### Why the resolver returns `nil` for missing files
136
+
137
+ When `CoverageLineResolver.lookup_lines` can't find a file, it returns `nil` rather
138
+ than immediately raising an error. This is deliberate:
139
+
140
+ - The lookup tries multiple strategies: exact path match, relative path, path without
141
+ working directory prefix
142
+ - Each strategy calls `lines_from_entry` which may return `nil`
143
+ - Only after **all strategies fail** does the resolver raise a `FileError`
144
+ - This ensures we try every possible way to find the file before giving up
145
+
146
+ If you change `lines_from_entry` to raise an error immediately, the multi-strategy
147
+ lookup will break!
148
+
149
+ ### Future-proofing for SimpleCov changes
150
+
151
+ SimpleCov's branch tuple format could change in future versions. If that happens:
152
+
153
+ 1. Update `extract_line_number` to recognize the new tuple format
154
+ 2. Add test fixtures with the new format
155
+ 3. Update this documentation with the new structure
156
+ 4. Keep backward compatibility with the old format if possible
157
+
158
+ This way, SimpleCov MCP continues working even when SimpleCov evolves.
@@ -0,0 +1,83 @@
1
+ # Development Guide
2
+
3
+ [Back to main README](../README.md)
4
+
5
+ > **Note:** Commands like `cov-loupe` assume the gem is installed globally. If not, substitute `bundle exec exe/cov-loupe`.
6
+
7
+ ## Setup
8
+
9
+ ```sh
10
+ git clone https://github.com/keithrbennett/cov-loupe.git
11
+ cd cov-loupe
12
+ bundle install
13
+ gem build cov-loupe.gemspec && gem install cov-loupe-*.gem # optional
14
+ cov-loupe version # verify it works
15
+ ```
16
+
17
+ ## Running Tests
18
+
19
+ ```sh
20
+ bundle exec rspec
21
+ ```
22
+
23
+ ## Project-Specific Patterns
24
+
25
+ **All Ruby files start with:**
26
+ ```ruby
27
+ # frozen_string_literal: true
28
+ ```
29
+
30
+ **Error handling uses custom exceptions from `errors.rb`:**
31
+ ```ruby
32
+ rescue Errno::ENOENT => e
33
+ raise FileError.new("Coverage data not found: #{e.message}")
34
+ rescue JSON::ParserError => e
35
+ raise CoverageDataError.new("Invalid coverage format: #{e.message}")
36
+ ```
37
+
38
+ **MCP tools extend `BaseTool` and follow this pattern:**
39
+ ```ruby
40
+ module CovLoupe::Tools
41
+ class MyTool < BaseTool
42
+ def self.name = 'my_tool'
43
+ def self.description = 'What this tool does'
44
+
45
+ def self.call(path:, root: '.', resultset: nil, stale: 'off', error_mode: 'on', **)
46
+ model = CoverageModel.new(root: root, resultset: resultset, staleness: stale)
47
+ data = model.my_method_for(path)
48
+ respond_json(model.relativize(data), name: 'my_tool_output.json')
49
+ rescue => e
50
+ handle_mcp_error(e, name, error_mode: error_mode.to_sym)
51
+ end
52
+ end
53
+ end
54
+ ```
55
+
56
+ **Use test fixtures for consistency:**
57
+ ```ruby
58
+ let(:project_root) { (FIXTURES_DIR / 'project1').to_s }
59
+ let(:coverage_dir) { File.join(project_root, 'coverage') }
60
+ ```
61
+
62
+ **MCP tool tests need setup:**
63
+ ```ruby
64
+ let(:server_context) { instance_double('ServerContext').as_null_object }
65
+ before { setup_mcp_response_stub }
66
+ ```
67
+
68
+ ## Adding Features
69
+
70
+ **CLI commands:** Add to `SUBCOMMANDS` in `cli.rb`, implement handler, add tests
71
+
72
+ **MCP tools:** Create `*_tool.rb` in `lib/cov_loupe/tools/`, register in `mcp_server.rb`
73
+
74
+ **Coverage features:** Add to `CoverageModel` in `model.rb` or `CovUtil` in `util.rb`
75
+
76
+ ## Troubleshooting
77
+
78
+ **RVM + Codex macOS:** Currently not possible for Codex to run rspec when running on macOS with rvm-managed rubies - see [TROUBLESHOOTING.md](TROUBLESHOOTING.md)
79
+
80
+ **MCP server testing:**
81
+ ```sh
82
+ echo '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"version_tool","arguments":{}}}' | cov-loupe
83
+ ```