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.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.md +329 -0
- data/docs/dev/ARCHITECTURE.md +80 -0
- data/docs/dev/BRANCH_ONLY_COVERAGE.md +158 -0
- data/docs/dev/DEVELOPMENT.md +83 -0
- data/docs/dev/README.md +10 -0
- data/docs/dev/RELEASING.md +146 -0
- data/docs/dev/arch-decisions/001-x-arch-decision.md +95 -0
- data/docs/dev/arch-decisions/002-x-arch-decision.md +159 -0
- data/docs/dev/arch-decisions/003-x-arch-decision.md +165 -0
- data/docs/dev/arch-decisions/004-x-arch-decision.md +203 -0
- data/docs/dev/arch-decisions/005-x-arch-decision.md +189 -0
- data/docs/dev/arch-decisions/README.md +60 -0
- data/docs/dev/presentations/cov-loupe-presentation.md +255 -0
- data/docs/fixtures/demo_project/README.md +9 -0
- data/docs/user/ADVANCED_USAGE.md +777 -0
- data/docs/user/CLI_FALLBACK_FOR_LLMS.md +34 -0
- data/docs/user/CLI_USAGE.md +750 -0
- data/docs/user/ERROR_HANDLING.md +93 -0
- data/docs/user/EXAMPLES.md +588 -0
- data/docs/user/INSTALLATION.md +130 -0
- data/docs/user/LIBRARY_API.md +693 -0
- data/docs/user/MCP_INTEGRATION.md +490 -0
- data/docs/user/README.md +14 -0
- data/docs/user/TROUBLESHOOTING.md +197 -0
- data/docs/user/V2-BREAKING-CHANGES.md +472 -0
- data/exe/cov-loupe +23 -0
- data/lib/cov_loupe/app_config.rb +56 -0
- data/lib/cov_loupe/app_context.rb +26 -0
- data/lib/cov_loupe/base_tool.rb +102 -0
- data/lib/cov_loupe/cli.rb +178 -0
- data/lib/cov_loupe/commands/base_command.rb +67 -0
- data/lib/cov_loupe/commands/command_factory.rb +45 -0
- data/lib/cov_loupe/commands/detailed_command.rb +38 -0
- data/lib/cov_loupe/commands/list_command.rb +13 -0
- data/lib/cov_loupe/commands/raw_command.rb +38 -0
- data/lib/cov_loupe/commands/summary_command.rb +41 -0
- data/lib/cov_loupe/commands/totals_command.rb +53 -0
- data/lib/cov_loupe/commands/uncovered_command.rb +45 -0
- data/lib/cov_loupe/commands/validate_command.rb +60 -0
- data/lib/cov_loupe/commands/version_command.rb +33 -0
- data/lib/cov_loupe/config_parser.rb +32 -0
- data/lib/cov_loupe/constants.rb +22 -0
- data/lib/cov_loupe/coverage_reporter.rb +31 -0
- data/lib/cov_loupe/error_handler.rb +165 -0
- data/lib/cov_loupe/error_handler_factory.rb +31 -0
- data/lib/cov_loupe/errors.rb +191 -0
- data/lib/cov_loupe/formatters/source_formatter.rb +152 -0
- data/lib/cov_loupe/formatters.rb +51 -0
- data/lib/cov_loupe/mcp_server.rb +42 -0
- data/lib/cov_loupe/mode_detector.rb +56 -0
- data/lib/cov_loupe/model.rb +339 -0
- data/lib/cov_loupe/option_normalizers.rb +113 -0
- data/lib/cov_loupe/option_parser_builder.rb +147 -0
- data/lib/cov_loupe/option_parsers/env_options_parser.rb +48 -0
- data/lib/cov_loupe/option_parsers/error_helper.rb +110 -0
- data/lib/cov_loupe/path_relativizer.rb +64 -0
- data/lib/cov_loupe/predicate_evaluator.rb +72 -0
- data/lib/cov_loupe/presenters/base_coverage_presenter.rb +42 -0
- data/lib/cov_loupe/presenters/coverage_detailed_presenter.rb +14 -0
- data/lib/cov_loupe/presenters/coverage_raw_presenter.rb +14 -0
- data/lib/cov_loupe/presenters/coverage_summary_presenter.rb +14 -0
- data/lib/cov_loupe/presenters/coverage_uncovered_presenter.rb +14 -0
- data/lib/cov_loupe/presenters/project_coverage_presenter.rb +50 -0
- data/lib/cov_loupe/presenters/project_totals_presenter.rb +27 -0
- data/lib/cov_loupe/resolvers/coverage_line_resolver.rb +122 -0
- data/lib/cov_loupe/resolvers/resolver_factory.rb +28 -0
- data/lib/cov_loupe/resolvers/resultset_path_resolver.rb +76 -0
- data/lib/cov_loupe/resultset_loader.rb +131 -0
- data/lib/cov_loupe/staleness_checker.rb +247 -0
- data/lib/cov_loupe/table_formatter.rb +64 -0
- data/lib/cov_loupe/tools/all_files_coverage_tool.rb +51 -0
- data/lib/cov_loupe/tools/coverage_detailed_tool.rb +35 -0
- data/lib/cov_loupe/tools/coverage_raw_tool.rb +34 -0
- data/lib/cov_loupe/tools/coverage_summary_tool.rb +34 -0
- data/lib/cov_loupe/tools/coverage_table_tool.rb +50 -0
- data/lib/cov_loupe/tools/coverage_totals_tool.rb +44 -0
- data/lib/cov_loupe/tools/help_tool.rb +115 -0
- data/lib/cov_loupe/tools/uncovered_lines_tool.rb +34 -0
- data/lib/cov_loupe/tools/validate_tool.rb +72 -0
- data/lib/cov_loupe/tools/version_tool.rb +32 -0
- data/lib/cov_loupe/util.rb +88 -0
- data/lib/cov_loupe/version.rb +5 -0
- data/lib/cov_loupe.rb +140 -0
- data/spec/MCP_INTEGRATION_TESTS_README.md +111 -0
- data/spec/TIMESTAMPS.md +48 -0
- data/spec/all_files_coverage_tool_spec.rb +53 -0
- data/spec/app_config_spec.rb +142 -0
- data/spec/base_tool_spec.rb +62 -0
- data/spec/cli/show_default_report_spec.rb +33 -0
- data/spec/cli_enumerated_options_spec.rb +90 -0
- data/spec/cli_error_spec.rb +184 -0
- data/spec/cli_format_spec.rb +123 -0
- data/spec/cli_json_options_spec.rb +50 -0
- data/spec/cli_source_spec.rb +44 -0
- data/spec/cli_spec.rb +192 -0
- data/spec/cli_table_spec.rb +28 -0
- data/spec/cli_usage_spec.rb +42 -0
- data/spec/commands/base_command_spec.rb +107 -0
- data/spec/commands/command_factory_spec.rb +76 -0
- data/spec/commands/detailed_command_spec.rb +34 -0
- data/spec/commands/list_command_spec.rb +28 -0
- data/spec/commands/raw_command_spec.rb +69 -0
- data/spec/commands/summary_command_spec.rb +34 -0
- data/spec/commands/totals_command_spec.rb +34 -0
- data/spec/commands/uncovered_command_spec.rb +55 -0
- data/spec/commands/validate_command_spec.rb +213 -0
- data/spec/commands/version_command_spec.rb +38 -0
- data/spec/constants_spec.rb +61 -0
- data/spec/cov_loupe/formatters/source_formatter_spec.rb +267 -0
- data/spec/cov_loupe/formatters_spec.rb +76 -0
- data/spec/cov_loupe/presenters/base_coverage_presenter_spec.rb +79 -0
- data/spec/cov_loupe_model_spec.rb +454 -0
- data/spec/cov_loupe_module_spec.rb +37 -0
- data/spec/cov_loupe_opts_spec.rb +185 -0
- data/spec/coverage_reporter_spec.rb +102 -0
- data/spec/coverage_table_tool_spec.rb +59 -0
- data/spec/coverage_totals_tool_spec.rb +37 -0
- data/spec/error_handler_spec.rb +197 -0
- data/spec/error_mode_spec.rb +139 -0
- data/spec/errors_edge_cases_spec.rb +312 -0
- data/spec/errors_stale_spec.rb +83 -0
- data/spec/file_based_mcp_tools_spec.rb +99 -0
- data/spec/fixtures/project1/lib/bar.rb +5 -0
- data/spec/fixtures/project1/lib/foo.rb +6 -0
- data/spec/help_tool_spec.rb +26 -0
- data/spec/integration_spec.rb +789 -0
- data/spec/logging_fallback_spec.rb +128 -0
- data/spec/mcp_logging_spec.rb +44 -0
- data/spec/mcp_server_integration_spec.rb +23 -0
- data/spec/mcp_server_spec.rb +106 -0
- data/spec/mode_detector_spec.rb +153 -0
- data/spec/model_error_handling_spec.rb +269 -0
- data/spec/model_staleness_spec.rb +79 -0
- data/spec/option_normalizers_spec.rb +203 -0
- data/spec/option_parsers/env_options_parser_spec.rb +221 -0
- data/spec/option_parsers/error_helper_spec.rb +222 -0
- data/spec/path_relativizer_spec.rb +98 -0
- data/spec/presenters/coverage_detailed_presenter_spec.rb +19 -0
- data/spec/presenters/coverage_raw_presenter_spec.rb +15 -0
- data/spec/presenters/coverage_summary_presenter_spec.rb +15 -0
- data/spec/presenters/coverage_uncovered_presenter_spec.rb +16 -0
- data/spec/presenters/project_coverage_presenter_spec.rb +87 -0
- data/spec/presenters/project_totals_presenter_spec.rb +144 -0
- data/spec/resolvers/coverage_line_resolver_spec.rb +282 -0
- data/spec/resolvers/resolver_factory_spec.rb +61 -0
- data/spec/resolvers/resultset_path_resolver_spec.rb +60 -0
- data/spec/resultset_loader_spec.rb +167 -0
- data/spec/shared_examples/README.md +115 -0
- data/spec/shared_examples/coverage_presenter_examples.rb +66 -0
- data/spec/shared_examples/file_based_mcp_tools.rb +179 -0
- data/spec/shared_examples/formatted_command_examples.rb +64 -0
- data/spec/shared_examples/mcp_tool_text_json_response.rb +16 -0
- data/spec/spec_helper.rb +127 -0
- data/spec/staleness_checker_spec.rb +374 -0
- data/spec/staleness_more_spec.rb +42 -0
- data/spec/support/cli_helpers.rb +22 -0
- data/spec/support/control_flow_helpers.rb +20 -0
- data/spec/support/fake_mcp.rb +40 -0
- data/spec/support/io_helpers.rb +29 -0
- data/spec/support/mcp_helpers.rb +35 -0
- data/spec/support/mcp_runner.rb +66 -0
- data/spec/support/mocking_helpers.rb +30 -0
- data/spec/table_format_spec.rb +70 -0
- data/spec/tools/validate_tool_spec.rb +132 -0
- data/spec/tools_error_handling_spec.rb +130 -0
- data/spec/util_spec.rb +154 -0
- data/spec/version_spec.rb +123 -0
- data/spec/version_tool_spec.rb +141 -0
- 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
|
+
[](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
|
+
```
|