simplecov-mcp 0.1.0 → 0.2.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 (58) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +379 -32
  3. data/exe/simplecov-mcp +19 -2
  4. data/lib/simple_cov/mcp.rb +9 -0
  5. data/lib/simple_cov_mcp/base_tool.rb +54 -0
  6. data/lib/simple_cov_mcp/cli.rb +390 -0
  7. data/lib/simple_cov_mcp/error_handler.rb +131 -0
  8. data/lib/simple_cov_mcp/error_handler_factory.rb +38 -0
  9. data/lib/simple_cov_mcp/errors.rb +176 -0
  10. data/lib/simple_cov_mcp/mcp_server.rb +30 -0
  11. data/lib/simple_cov_mcp/model.rb +104 -0
  12. data/lib/simple_cov_mcp/staleness_checker.rb +125 -0
  13. data/lib/simple_cov_mcp/tools/all_files_coverage_tool.rb +63 -0
  14. data/lib/simple_cov_mcp/tools/coverage_detailed_tool.rb +29 -0
  15. data/lib/simple_cov_mcp/tools/coverage_raw_tool.rb +29 -0
  16. data/lib/simple_cov_mcp/tools/coverage_summary_tool.rb +29 -0
  17. data/lib/simple_cov_mcp/tools/coverage_table_tool.rb +61 -0
  18. data/lib/simple_cov_mcp/tools/help_tool.rb +133 -0
  19. data/lib/simple_cov_mcp/tools/uncovered_lines_tool.rb +29 -0
  20. data/lib/simple_cov_mcp/tools/version_tool.rb +31 -0
  21. data/lib/simple_cov_mcp/util.rb +122 -0
  22. data/lib/simple_cov_mcp/version.rb +5 -0
  23. data/lib/simple_cov_mcp.rb +102 -0
  24. data/lib/simplecov_mcp.rb +2 -3
  25. data/spec/all_files_coverage_tool_spec.rb +46 -0
  26. data/spec/base_tool_spec.rb +58 -0
  27. data/spec/cli_error_spec.rb +103 -0
  28. data/spec/cli_json_source_spec.rb +92 -0
  29. data/spec/cli_source_spec.rb +37 -0
  30. data/spec/cli_spec.rb +72 -0
  31. data/spec/cli_table_spec.rb +28 -0
  32. data/spec/cli_usage_spec.rb +58 -0
  33. data/spec/coverage_table_tool_spec.rb +64 -0
  34. data/spec/error_handler_spec.rb +72 -0
  35. data/spec/errors_stale_spec.rb +49 -0
  36. data/spec/fixtures/project1/lib/bar.rb +4 -0
  37. data/spec/fixtures/project1/lib/foo.rb +5 -0
  38. data/spec/help_tool_spec.rb +47 -0
  39. data/spec/legacy_shim_spec.rb +13 -0
  40. data/spec/mcp_server_spec.rb +78 -0
  41. data/spec/model_staleness_spec.rb +49 -0
  42. data/spec/simplecov_mcp_model_spec.rb +51 -32
  43. data/spec/spec_helper.rb +37 -7
  44. data/spec/staleness_more_spec.rb +39 -0
  45. data/spec/util_spec.rb +78 -0
  46. data/spec/version_spec.rb +10 -0
  47. metadata +56 -13
  48. data/lib/simplecov/mcp/base_tool.rb +0 -18
  49. data/lib/simplecov/mcp/cli.rb +0 -98
  50. data/lib/simplecov/mcp/model.rb +0 -59
  51. data/lib/simplecov/mcp/tools/all_files_coverage.rb +0 -28
  52. data/lib/simplecov/mcp/tools/coverage_detailed.rb +0 -22
  53. data/lib/simplecov/mcp/tools/coverage_raw.rb +0 -22
  54. data/lib/simplecov/mcp/tools/coverage_summary.rb +0 -22
  55. data/lib/simplecov/mcp/tools/uncovered_lines.rb +0 -22
  56. data/lib/simplecov/mcp/util.rb +0 -94
  57. data/lib/simplecov/mcp/version.rb +0 -8
  58. data/lib/simplecov/mcp.rb +0 -28
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f2a8ecf4bfeeedc4b7377baaa3fcabf0b196ad9c17f2ccd29e5532b7a6104751
4
- data.tar.gz: 8b52e3920dd3734add19862838f03b967bb73d17dd769539a66160ef9950e38f
3
+ metadata.gz: 06e96d273c6549d1f0539baa88ec863009f263e6d9f214c66157ac5e708b51a5
4
+ data.tar.gz: fc1503a39a31d19076f4e7827aad1d1996fd4760b381aafdf7e4b13f8891fb0c
5
5
  SHA512:
6
- metadata.gz: bbcec76c297750544657a18cec0366148f1cff30ebff2c9b5bbc593f2d9492fe32554d2b4859f1e89f7efd19f35d35e7345f8ecdfa05d1c32ffea86f42c68ed6
7
- data.tar.gz: 2266b137c11b92fcaedc7337010c7a5b8c6814d09b4a652bc642abad2082e3f8cd20e8d33930808257176e020602249408be71c206d518adba3cc577dab7aa41
6
+ metadata.gz: 41125a954fca4422327a821f18ad55b8de0d473ea3c3d67bc0bb9eb6105d81aa09efcab262a09f0cedeb809adb73a0e974b36d0dcf00ceff55af8e639a573afe
7
+ data.tar.gz: 7ab8e6375e1645557a8dd30c72be8e37e9d4fd97de59e330db24c0f6ec92339db5488b43c5cc9dbc975edbf5184fdb3179d5425cb48d5791d2fb0650659ab439
data/README.md CHANGED
@@ -5,56 +5,331 @@ MCP server + CLI for inspecting SimpleCov coverage data.
5
5
  This gem provides:
6
6
 
7
7
  - An MCP (Model Context Protocol) server exposing tools to query coverage for files.
8
- - A human-friendly CLI that prints a sorted table of file coverage.
8
+ - A flexible CLI with subcommands to list all files, show a file summary, print raw coverage arrays, list uncovered lines, and display detailed per-line hits. Supports JSON output, displaying annotated source code (full file or uncovered lines with context), and custom resultset locations.
9
+
10
+ ## Features
11
+
12
+ - MCP server tools for coverage queries: raw, summary, uncovered, detailed, all-files list, and version.
13
+ - Per-file staleness flag in list outputs to highlight files newer than coverage or with line-count mismatches (shown as a compact '!' column in the CLI).
14
+ - CLI subcommands: `list`, `summary`, `raw`, `uncovered`, `detailed`, `version` (default `list`).
15
+ - JSON output with `--json` for machine use; human-readable tables/rows by default.
16
+ - Annotated source snippets with `--source[=full|uncovered]` and `--source-context N`; optional colors with `--color/--no-color`.
17
+ - Flexible resultset location: `--resultset PATH` or `SIMPLECOV_RESULTSET`; accepts file or directory; sensible default search order.
18
+ - Works installed as a gem, via Bundler (`bundle exec`), or directly from this repo’s `exe/` (symlink-safe).
19
+
20
+ ## SimpleCov Independence
21
+
22
+ This codebase does not require, and is not connected to, the `simplecov` library at runtime. Its only interaction is reading the JSON resultset file that SimpleCov generates (`.resultset.json`). As long as that file exists in your project (in a default or specified location), the CLI and MCP server can operate without `require "simplecov"` in your app or test process.
9
23
 
10
24
  ## Installation
11
25
 
12
26
  Add to your Gemfile or install directly:
13
27
 
14
- ```
28
+ ```sh
15
29
  gem install simplecov-mcp
16
30
  ```
17
31
 
18
- Require path is `simplecov/mcp` (also `simplecov_mcp`). Executable is `simplecov-mcp`.
32
+ Require path is `simple_cov_mcp` (also `simplecov_mcp`). Legacy `simple_cov/mcp` is supported via shim. Executable is `simplecov-mcp`.
19
33
 
20
34
  ## Usage
21
35
 
22
- Environment variable:
36
+ ### Library Usage (Ruby)
37
+
38
+ Use this gem programmatically to inspect coverage without running the CLI or MCP server. The primary entry point is `SimpleCovMcp::CoverageModel`.
39
+
40
+ Basics:
41
+
42
+ ```ruby
43
+ require "simple_cov_mcp"
44
+
45
+ # Defaults (omit args; shown here with comments):
46
+ # - root: "."
47
+ # - resultset: resolved from SIMPLECOV_RESULTSET or common paths under root
48
+ # - staleness: "off" (no stale checks)
49
+ # - tracked_globs: nil (no project-level file-set checks)
50
+ model = SimpleCovMcp::CoverageModel.new
51
+
52
+ # Custom configuration (non-default values):
53
+ model = SimpleCovMcp::CoverageModel.new(
54
+ root: "/path/to/project", # non-default project root
55
+ resultset: "build/coverage", # file or directory containing .resultset.json
56
+ staleness: "error", # enable stale checks (raise on stale)
57
+ tracked_globs: ["lib/**/*.rb"] # for 'all_files' staleness: flag new/missing files
58
+ )
59
+
60
+ # List all files with coverage summary, sorted ascending by % (default)
61
+ files = model.all_files
62
+ # => [ { 'file' => '/abs/path/lib/foo.rb', 'covered' => 12, 'total' => 14, 'percentage' => 85.71, 'stale' => false }, ... ]
63
+
64
+ # Per-file summaries
65
+ summary = model.summary_for("lib/foo.rb")
66
+ # => { 'file' => '/abs/.../lib/foo.rb', 'summary' => {'covered'=>12, 'total'=>14, 'pct'=>85.71} }
67
+
68
+ raw = model.raw_for("lib/foo.rb")
69
+ # => { 'file' => '/abs/.../lib/foo.rb', 'lines' => [nil, 1, 0, 3, ...] }
70
+
71
+ uncovered = model.uncovered_for("lib/foo.rb")
72
+ # => { 'file' => '/abs/.../lib/foo.rb', 'uncovered' => [5, 9, 12], 'summary' => { ... } }
73
+
74
+ detailed = model.detailed_for("lib/foo.rb")
75
+ # => { 'file' => '/abs/.../lib/foo.rb', 'lines' => [{'line' => 1, 'hits' => 1, 'covered' => true}, ...], 'summary' => { ... } }
76
+ ```
77
+
78
+ ### MCP Quick Start
79
+
80
+ - Prereqs: Ruby 3.2+; run tests once so `coverage/.resultset.json` exists.
81
+ - One-off MCP requests can be made by piping JSON-RPC to the server:
82
+
83
+ **Important**: JSON-RPC messages must be on a single line (no line breaks). Multi-line JSON will cause parse errors.
84
+
85
+ ```sh
86
+ # Per-file summary (staleness off)
87
+ echo '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"coverage_summary_tool","arguments":{"path":"lib/foo.rb","resultset":"coverage","stale":"off"}}}' | simplecov-mcp
88
+
89
+ # All files with project-level staleness checks
90
+ echo '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"all_files_coverage_tool","arguments":{"resultset":"coverage","stale":"error","tracked_globs":["lib/**/*.rb"]}}}' | simplecov-mcp
91
+ ```
92
+
93
+ Tip: In an MCP-capable editor/agent, configure `simplecov-mcp` as a stdio server and call the same tool names with the `arguments` shown above.
94
+
95
+ Resultset resolution:
96
+
97
+ - If `resultset:` is provided, it may be:
98
+ - a file path to `.resultset.json`, or
99
+ - a directory containing `.resultset.json` (e.g., `coverage/`).
100
+ - If `resultset:` is omitted, resolution follows:
101
+ 1) `ENV["SIMPLECOV_RESULTSET"]` (file or directory), then
102
+ 2) `.resultset.json`, 3) `coverage/.resultset.json`, 4) `tmp/.resultset.json` under `root`.
103
+
104
+ Path semantics:
105
+
106
+ - Methods accept paths relative to `root` or absolute paths. Internally, the model resolves to an absolute path and looks up coverage by:
107
+ 1) exact absolute path,
108
+ 2) the path without the current working directory prefix,
109
+ 3) filename (basename) match as a last resort.
110
+
111
+ Sorting:
112
+
113
+ ```ruby
114
+ model.all_files(sort_order: :descending) # or :ascending (default)
115
+ ```
116
+
117
+ ## Example Prompts
118
+
119
+ Using simplecov-mcp, show me a table of all files and their coverages.
120
+
121
+ ----
122
+
123
+ Using simplecov-mcp, find the uncovered code lines and report to me:
124
+
125
+ * the most important coverage omissions to address
126
+ * the simplest coverage omissions to address
127
+ * analyze the risk of the current state of coverage
128
+ * propose a plan of action (if any) to improve coverage state
129
+
130
+ ----
131
+
132
+
133
+ ## Error Handling
134
+
135
+ This tool provides different error handling behavior depending on how it's used:
136
+
137
+ ### CLI Mode
138
+ When used as a command-line tool, errors are displayed as user-friendly messages without stack traces:
139
+
140
+ ```bash
141
+ $ simplecov-mcp summary nonexistent.rb
142
+ File error: No coverage data found for the specified file
143
+ ```
144
+
145
+ For debugging, set the `SIMPLECOV_MCP_DEBUG=1` environment variable to see full stack traces.
146
+
147
+ ### Library Mode
148
+ When used as a Ruby library, errors are raised as custom exception classes that can be caught and handled:
149
+
150
+ ```ruby
151
+ begin
152
+ SimpleCovMcp.run_as_library(['summary', 'missing.rb'])
153
+ rescue SimpleCovMcp::FileError => e
154
+ puts "Handled gracefully: #{e.user_friendly_message}"
155
+ end
156
+ ```
157
+
158
+ Available exception classes:
159
+ - `SimpleCovMcp::Error` - Base error class
160
+ - `SimpleCovMcp::FileError` - File not found or access issues
161
+ - `SimpleCovMcp::CoverageDataError` - Invalid or missing coverage data
162
+ - `SimpleCovMcp::ConfigurationError` - Configuration problems
163
+ - `SimpleCovMcp::UsageError` - Command usage errors
164
+
165
+ ### MCP Server Mode
166
+ When running as an MCP server, errors are handled internally and returned as structured responses to the MCP client. The MCP server uses:
167
+
168
+ - **Logging enabled** - Errors are logged to `~/simplecov_mcp.log` for server debugging
169
+ - **Clean error messages** - User-friendly messages are returned to the client (no stack traces unless `SIMPLECOV_MCP_DEBUG=1`)
170
+ - **Structured responses** - Errors are returned as proper MCP tool responses, not exceptions
171
+
172
+ The MCP server automatically configures error handling appropriately for server usage.
173
+
174
+ ### Custom Error Handlers
175
+ Library usage defaults to no logging to avoid side effects, but you can customize this:
176
+
177
+ ```ruby
178
+ # Default library behavior - no logging
179
+ SimpleCovMcp.run_as_library(['summary', 'file.rb'])
180
+
181
+ # Custom error handler with logging enabled
182
+ handler = SimpleCovMcp::ErrorHandler.new(
183
+ log_errors: true, # Enable logging for library usage
184
+ show_stack_traces: false # Clean error messages
185
+ )
186
+ SimpleCovMcp.run_as_library(argv, error_handler: handler)
187
+
188
+ # Or configure globally for MCP tools
189
+ SimpleCovMcp.configure_error_handling do |handler|
190
+ handler.log_errors = true
191
+ handler.show_stack_traces = true # For debugging
192
+ end
193
+ ```
23
194
 
24
- - `SIMPLECOV_RESULTSET` optional explicit path to `.resultset.json`.
195
+ ### Legacy Error Handling Tips
25
196
 
26
- Search order for resultset:
197
+ - Missing resultset: `SimpleCov::Mcp::CovUtil.find_resultset` raises with guidance to run tests or set `SIMPLECOV_RESULTSET`.
198
+ - Missing file coverage: lookups raise `"No coverage entry found for <path>"`.
27
199
 
28
- 1. `.resultset.json`
29
- 2. `coverage/.resultset.json`
30
- 3. `tmp/.resultset.json`
200
+ Example: fail CI if a file has uncovered lines
201
+
202
+ ```ruby
203
+ require "simple_cov_mcp"
204
+
205
+ model = SimpleCovMcp::CoverageModel.new(root: Dir.pwd)
206
+ res = model.uncovered_for("lib/foo.rb")
207
+ if res['uncovered'].any?
208
+ warn "Uncovered lines in lib/foo.rb: #{res['uncovered'].join(", ")}"
209
+ exit 1
210
+ end
211
+ ```
212
+
213
+ Example: enforce a project-wide minimum percentage
214
+
215
+ ```ruby
216
+ require "simple_cov_mcp"
217
+
218
+ threshold = 90.0
219
+ min = model.all_files.map { |r| r['percentage'] }.min || 100.0
220
+ if min < threshold
221
+ warn "Min coverage %.2f%% is below threshold %.2f%%" % [min, threshold]
222
+ exit 1
223
+ end
224
+ ```
225
+
226
+ Public API stability:
227
+
228
+ - Consider the following public and stable under SemVer:
229
+ - `SimpleCovMcp::CoverageModel.new(root:, resultset:, staleness: 'off', tracked_globs: nil)`
230
+ - `#raw_for(path)`, `#summary_for(path)`, `#uncovered_for(path)`, `#detailed_for(path)`, `#all_files(sort_order:)`
231
+ - Return shapes shown above (keys and value types). For `all_files`, each row also includes `'stale' => true|false`.
232
+ - CLI (`SimpleCovMcp.run(argv)`) and MCP tools remain stable but are separate surfaces.
233
+ - Internal helpers under `SimpleCovMcp::CovUtil` may change; prefer `CoverageModel` unless you need low-level access.
234
+
235
+ ### Resultset Location
236
+
237
+ - Defaults (search order):
238
+ 1. `.resultset.json`
239
+ 2. `coverage/.resultset.json`
240
+ 3. `tmp/.resultset.json`
241
+ - Override via CLI: `--resultset PATH` (PATH may be the file itself or a directory containing `.resultset.json`).
242
+ - Override via environment: `SIMPLECOV_RESULTSET=PATH` (file or directory). This takes precedence over defaults. The CLI flag, when present, takes precedence over the environment variable.
31
243
 
32
244
  ### CLI Mode
33
245
 
246
+ ### Stale Coverage Errors
247
+
248
+ When strict staleness checking is enabled, the model (and CLI) raise a
249
+ `CoverageDataStaleError` if a source file appears newer than the coverage data
250
+ or the line counts differ.
251
+
252
+ - Enable per instance: `SimpleCovMcp::CoverageModel.new(staleness: 'error')`
253
+
254
+ The error message is detailed and includes:
255
+
256
+ - File and Coverage times (UTC and local) and line counts
257
+ - A delta indicating how much newer the file is than coverage
258
+ - The absolute path to the `.resultset.json` used
259
+
260
+ Example excerpt:
261
+
262
+ ```
263
+ Coverage data stale: Coverage data appears stale for lib/foo.rb
264
+ File - time: 2025-09-16T14:03:22Z (local 2025-09-16T07:03:22-07:00), lines: 226
265
+ Coverage - time: 2025-09-15T21:11:09Z (local 2025-09-15T14:11:09-07:00), lines: 220
266
+ Delta - file is +123s newer than coverage
267
+ Resultset - /path/to/your/project/coverage/.resultset.json
268
+ ```
269
+
34
270
  Run in a project directory with a SimpleCov resultset:
35
271
 
272
+ ```sh
273
+ simplecov-mcp # same as 'list'
36
274
  ```
37
- simplecov-mcp
275
+
276
+ Subcommands:
277
+
278
+ - `list` — show the table of all files (sorted ascending by default)
279
+ - `summary <path>` — show covered/total/% for a file
280
+ - `raw <path>` — show the original SimpleCov lines array
281
+ - `uncovered <path>` — show uncovered lines and summary
282
+ - `detailed <path>` — show per-line rows with hits and covered
283
+ - `version` — show version information
284
+
285
+ Global flags (OptionParser):
286
+
287
+ - `--cli` (alias `--report`) — force CLI output
288
+ - `--resultset PATH` — path or directory for `.resultset.json`
289
+ - `--root PATH` — project root (default `.`)
290
+ - `--json` — print JSON output for machine use
291
+ - `--sort-order ascending|descending` — for `list`
292
+ - `--source[=MODE]` — include source text for `summary`, `uncovered`, `detailed` (MODE: `full` or `uncovered`; default `full`)
293
+ - `--source-context N` — for `--source=uncovered`, lines of context (default 2)
294
+ - `--color` / `--no-color` — enable/disable ANSI colors in source output
295
+ - `--stale off|error` — staleness checking mode (default `off`)
296
+ - `--tracked-globs x,y,z` — globs for files that should be covered (applies to `list` staleness only)
297
+ - `--help` — show usage
298
+
299
+ Select a nonstandard resultset path:
300
+
301
+ ```sh
302
+ simplecov-mcp --cli --resultset build/coverage/.resultset.json
303
+ # or
304
+ SIMPLECOV_RESULTSET=build/coverage/.resultset.json simplecov-mcp --cli
38
305
  ```
39
306
 
40
- Forces CLI mode:
307
+ You can also pass a directory that contains `.resultset.json` (common when the file lives in a `coverage/` folder):
41
308
 
309
+ ```sh
310
+ simplecov-mcp --cli --resultset coverage
311
+ # or via env
312
+ SIMPLECOV_RESULTSET=coverage simplecov-mcp --cli
42
313
  ```
314
+
315
+ Forces CLI mode:
316
+
317
+ ```sh
43
318
  simplecov-mcp --cli
44
319
  # or
45
- COVERAGE_MCP_CLI=1 simplecov-mcp
46
- ``;
320
+ SIMPLECOV_MCP_CLI=1 simplecov-mcp
321
+ ```
47
322
 
48
323
  Example output:
49
324
 
50
- ```
51
- ┌───────────────────────────┬──────────┬──────────┬────────┐
52
- │ File │ % │ Covered │ Total │
53
- ├───────────────────────────┼──────────┼──────────┼────────┤
54
- lib/models/user.rb 92.31 │ 12 │ 13
55
- │ lib/services/auth.rb 100.00 8 8
56
- spec/user_spec.rb 85.71 12 14
57
- └───────────────────────────┴──────────┴──────────┴────────┘
325
+ ```text
326
+ ┌───────────────────────────┬──────────┬──────────┬────────┬───┐
327
+ │ File │ % │ Covered │ Total │ ! │
328
+ ├───────────────────────────┼──────────┼──────────┼────────┼───┤
329
+ spec/user_spec.rb 85.71 │ 12 │ 14
330
+ │ lib/models/user.rb 92.31 12 13 ! │
331
+ lib/services/auth.rb 100.00 8 8
332
+ └───────────────────────────┴──────────┴──────────┴────────┴───┘
58
333
  ```
59
334
 
60
335
  Files are sorted by percentage (ascending), then by path.
@@ -65,33 +340,105 @@ When stdin has data (e.g., from an MCP client), the program runs as an MCP serve
65
340
 
66
341
  Available tools:
67
342
 
68
- - `coverage_raw(path, root=".")`
69
- - `coverage_summary(path, root=".")`
70
- - `uncovered_lines(path, root=".")`
71
- - `coverage_detailed(path, root=".")`
72
- - `all_files_coverage(root=".")`
343
+ - `coverage_raw_tool(path, root=".", resultset=nil, stale='off')`
344
+ - `coverage_summary_tool(path, root=".", resultset=nil, stale='off')`
345
+ - `uncovered_lines_tool(path, root=".", resultset=nil, stale='off')`
346
+ - `coverage_detailed_tool(path, root=".", resultset=nil, stale='off')`
347
+ - `all_files_coverage_tool(root=".", resultset=nil, stale='off', tracked_globs=nil)`
348
+ - Returns `{ files: [{"file","covered","total","percentage","stale"}, ...] }` where `stale` is a boolean.
349
+ - `version_tool()` — returns version information
73
350
 
74
- Example (manual):
351
+ Notes:
75
352
 
353
+ - `resultset` lets clients pass a nonstandard path to `.resultset.json` directly (absolute or relative to `root`). It may be:
354
+ - a file path to `.resultset.json`, or
355
+ - a directory path containing `.resultset.json` (e.g., `coverage/`).
356
+ - If `resultset` is omitted, the server checks `SIMPLECOV_RESULTSET`, then searches `.resultset.json`, `coverage/.resultset.json`, `tmp/.resultset.json` in that order.
357
+ - `stale` controls staleness checking per call (`off` or `error`).
358
+ - For `all_files_coverage`, `tracked_globs` detects new project files missing from coverage, and the tool also flags covered files that are newer than the coverage timestamp or present in coverage but deleted in the project.
359
+
360
+ Example (manual - note single-line JSON-RPC format):
361
+
362
+ ```sh
363
+ echo '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"coverage_summary_tool","arguments":{"path":"lib/foo.rb"}}}' | simplecov-mcp
76
364
  ```
77
- echo '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"coverage_summary","arguments":{"path":"lib/foo.rb"}}}' | simplecov-mcp
365
+
366
+ With an explicit resultset path:
367
+
368
+ ```sh
369
+ echo '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"coverage_summary_tool","arguments":{"path":"lib/foo.rb","resultset":"build/coverage/.resultset.json"}}}' | simplecov-mcp
370
+ ```
371
+
372
+ With a resultset directory:
373
+
374
+ ```sh
375
+ echo '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"coverage_summary_tool","arguments":{"path":"lib/foo.rb","resultset":"coverage"}}}' | simplecov-mcp
78
376
  ```
79
377
 
378
+ CLI vs MCP summary:
379
+
380
+ - CLI: use subcommands. Pass `--resultset PATH` or set `SIMPLECOV_RESULTSET`.
381
+ - MCP: pass `resultset` in tool arguments, or set `SIMPLECOV_RESULTSET`.
382
+
383
+ ## Troubleshooting
384
+
385
+ - MCP client fails to start or times out
386
+ - Likely cause: launching `simplecov-mcp` with an older Ruby that cannot load the `mcp` gem. This gem requires Ruby >= 3.2.
387
+ - Check the Ruby your MCP client uses: run `ruby -v` in the same environment your client inherits; ensure it reports 3.2+.
388
+ - Fix PATH or select a newer Ruby via rbenv/rvm/asdf, then retry. You can configure your MCP client to point to the shim/binary for that Ruby version. For example:
389
+ - rbenv shim: `~/.rbenv/shims/simplecov-mcp`
390
+ - asdf shim: `~/.asdf/shims/simplecov-mcp`
391
+ - RVM wrapper: `/Users/you/.rvm/wrappers/ruby-3.3.0/simplecov-mcp` (adjust version)
392
+ - Codex CLI example (`~/.codex/config.toml`):
393
+ ```toml
394
+ # Use the Ruby 3.2+ shim for the MCP server
395
+ [tools.simplecov_mcp]
396
+ command = "/Users/you/.rbenv/shims/simplecov-mcp"
397
+ cwd = "/path/to/your/project"
398
+ ```
399
+ - Validate manually: `simplecov-mcp --cli` (or from this repo: `ruby -Ilib exe/simplecov-mcp --cli`). If you see the coverage table, the binary starts correctly.
400
+ - On failures, check `~/simplecov_mcp.log` for details.
401
+
80
402
  ### Notes
81
403
 
82
- - Library entrypoint: `require "simplecov/mcp"` or `require "simplecov_mcp"`
83
- - Programmatic run: `Simplecov::Mcp.run(ARGV)`
84
- - Logs basic diagnostics to `~/coverage_mcp.log`.
404
+ - Library entrypoint: `require "simple_cov_mcp"` (also `simplecov_mcp`). Legacy `simple_cov/mcp` is supported.
405
+ - Programmatic run: `SimpleCovMcp.run(ARGV)`
406
+ - Staleness checks: pass `staleness: 'error'` to `CoverageModel` (or use CLI `--stale error`) to
407
+ raise if source mtimes are newer than coverage or line counts mismatch. Use
408
+ `--tracked-globs` (CLI) or `tracked_globs` (API/MCP) to flag new files.
409
+ - Logs basic diagnostics to `~/simplecov_mcp.log`.
410
+
411
+ ## Executables and PATH
412
+
413
+ To run `simplecov-mcp` globally, your PATH must include where Ruby installs executables.
414
+
415
+ - Version managers
416
+ - RVM, rbenv, asdf, chruby typically add the right bin/shim directories to PATH.
417
+ - Ensure your shell is initialized (e.g., rbenv init, asdf reshim ruby after installs).
418
+ - Without a manager
419
+ - Add the gem bin dir to PATH: see it with `gem env` (look for "EXECUTABLE DIRECTORY") or `ruby -e 'puts Gem.bindir'`.
420
+ - Example: `export PATH="$HOME/.gem/ruby/3.2.0/bin:$PATH"` (adjust version).
421
+ - Alternatives
422
+ - Use Bundler: `bundle exec simplecov-mcp` with cwd set to your project.
423
+ - Symlink the repo executable into a bin on your PATH; the script resolves its lib/ via realpath.
424
+ - Or configure Codex to run the executable by filename (`simplecov-mcp`) and inherit the current workspace as cwd.
85
425
 
86
426
  ## Development
87
427
 
88
428
  Standard Ruby gem structure. After cloning:
89
429
 
90
- ```
430
+ ```sh
91
431
  bundle install
92
432
  ruby -Ilib exe/simplecov-mcp --cli
93
433
  ```
94
434
 
435
+ Run tests with coverage (SimpleCov writes to `coverage/`):
436
+
437
+ ```sh
438
+ bundle exec rspec
439
+ # open coverage/index.html for HTML report, or run exe/simplecov-mcp for table summary
440
+ ```
441
+
95
442
  ## License
96
443
 
97
444
  MIT
data/exe/simplecov-mcp CHANGED
@@ -1,6 +1,23 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- require "simplecov_mcp"
4
+ # Make it safe to run via symlink by resolving the real path
5
+ root = File.expand_path("..", File.realpath(__FILE__))
6
+ lib = File.join(root, "lib")
7
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
8
 
6
- Simplecov::Mcp.run(ARGV)
9
+ # Fail fast with a helpful message if Ruby is too old for this gem
10
+ begin
11
+ require 'rubygems'
12
+ if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('3.2')
13
+ $stderr.puts "simplecov-mcp requires Ruby >= 3.2 (current: #{RUBY_VERSION})."
14
+ $stderr.puts "Please run with a supported Ruby version (e.g., via rbenv, rvm, asdf)."
15
+ exit 1
16
+ end
17
+ rescue StandardError
18
+ # If anything goes wrong, let the app load and potentially fail with a more specific error.
19
+ end
20
+
21
+ require 'simple_cov_mcp'
22
+
23
+ SimpleCovMcp.run(ARGV)
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Back-compat shim: load new path
4
+ require 'simple_cov_mcp'
5
+
6
+ # Only when requiring this legacy path, expose SimpleCov::Mcp as an alias
7
+ module SimpleCov
8
+ Mcp = SimpleCovMcp
9
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'mcp'
4
+ require_relative 'errors'
5
+ require_relative 'error_handler'
6
+
7
+ module SimpleCovMcp
8
+ class BaseTool < ::MCP::Tool
9
+ INPUT_SCHEMA = {
10
+ type: 'object',
11
+ additionalProperties: false,
12
+ properties: {
13
+ path: {
14
+ type: 'string',
15
+ description: 'Repo-relative or absolute path to the file whose coverage data you need.',
16
+ examples: ['lib/simple_cov_mcp/model.rb']
17
+ },
18
+ root: {
19
+ type: 'string',
20
+ description: 'Project root used to resolve relative paths (defaults to current workspace).',
21
+ default: '.'
22
+ },
23
+ resultset: {
24
+ type: 'string',
25
+ description: 'Path to the SimpleCov .resultset.json file (absolute or relative to root).'
26
+ },
27
+ stale: {
28
+ type: 'string',
29
+ description: "How to handle missing/outdated coverage data. 'off' skips checks; 'error' raises.",
30
+ enum: %w[off error],
31
+ default: 'off'
32
+ }
33
+ },
34
+ required: ['path']
35
+ }
36
+ def self.input_schema_def = INPUT_SCHEMA
37
+
38
+ # Handle errors consistently across all MCP tools
39
+ # Returns an MCP::Tool::Response with appropriate error message
40
+ def self.handle_mcp_error(error, tool_name)
41
+ # Normalize to a SimpleCovMcp::Error so we can handle/log uniformly
42
+ normalized = error.is_a?(SimpleCovMcp::Error) ? error : SimpleCovMcp.error_handler.convert_standard_error(error)
43
+ log_mcp_error(normalized, tool_name)
44
+ ::MCP::Tool::Response.new([{ type: 'text', text: "Error: #{normalized.user_friendly_message}" }])
45
+ end
46
+
47
+ private
48
+
49
+ def self.log_mcp_error(error, tool_name)
50
+ # Access the error handler's log_error method via send to bypass visibility
51
+ SimpleCovMcp.error_handler.send(:log_error, error, tool_name)
52
+ end
53
+ end
54
+ end