cov-loupe 3.0.0 → 4.0.0.pre

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 (281) hide show
  1. checksums.yaml +4 -4
  2. data/AGENTS.md +230 -0
  3. data/CLAUDE.md +5 -0
  4. data/CODE_OF_CONDUCT.md +62 -0
  5. data/CONTRIBUTING.md +102 -0
  6. data/GEMINI.md +5 -0
  7. data/README.md +154 -51
  8. data/RELEASE_NOTES.md +452 -0
  9. data/dev/images/cov-loupe-icon-lores.png +0 -0
  10. data/dev/images/cov-loupe-icon-square.png +0 -0
  11. data/dev/images/cov-loupe-icon.png +0 -0
  12. data/dev/images/cov-loupe-logo.png +0 -0
  13. data/dev/prompts/README.md +74 -0
  14. data/dev/prompts/archive/architectural-review-and-actions-prompt.md +53 -0
  15. data/dev/prompts/archive/investigate-and-report-issues-prompt.md +33 -0
  16. data/dev/prompts/archive/produce-action-items-prompt.md +25 -0
  17. data/dev/prompts/guidelines/ai-code-evaluator-guidelines.md +337 -0
  18. data/dev/prompts/improve/refactor-test-suite.md +18 -0
  19. data/dev/prompts/improve/simplify-code-logic.md +133 -0
  20. data/dev/prompts/improve/update-documentation.md +21 -0
  21. data/dev/prompts/review/comprehensive-codebase-review.md +176 -0
  22. data/dev/prompts/review/identify-action-items.md +143 -0
  23. data/dev/prompts/review/verify-code-changes.md +54 -0
  24. data/dev/prompts/validate/create-screencast-outline.md +234 -0
  25. data/dev/prompts/validate/test-documentation-examples.md +180 -0
  26. data/docs/QUICKSTART.md +63 -0
  27. data/docs/assets/images/cov-loupe-logo-lores.png +0 -0
  28. data/docs/assets/images/cov-loupe-logo.png +0 -0
  29. data/docs/assets/images/favicon.png +0 -0
  30. data/docs/assets/stylesheets/branding.css +16 -0
  31. data/docs/assets/stylesheets/extra.css +15 -0
  32. data/docs/code_of_conduct.md +1 -0
  33. data/docs/contributing.md +1 -0
  34. data/docs/dev/ARCHITECTURE.md +56 -11
  35. data/docs/dev/DEVELOPMENT.md +116 -12
  36. data/docs/dev/FUTURE_ENHANCEMENTS.md +14 -0
  37. data/docs/dev/README.md +3 -2
  38. data/docs/dev/RELEASING.md +2 -0
  39. data/docs/dev/arch-decisions/README.md +10 -7
  40. data/docs/dev/arch-decisions/application-architecture.md +259 -0
  41. data/docs/dev/arch-decisions/coverage-data-quality.md +193 -0
  42. data/docs/dev/arch-decisions/output-character-mode.md +217 -0
  43. data/docs/dev/arch-decisions/path-resolution.md +90 -0
  44. data/docs/dev/arch-decisions/{004-x-arch-decision.md → policy-validation.md} +32 -28
  45. data/docs/dev/arch-decisions/{005-x-arch-decision.md → simplecov-integration.md} +47 -44
  46. data/docs/dev/presentations/cov-loupe-presentation.md +15 -13
  47. data/docs/examples/mcp-inputs.md +3 -0
  48. data/docs/examples/prompts.md +3 -0
  49. data/docs/examples/success_predicates.md +3 -0
  50. data/docs/fixtures/demo_project/.resultset.json +170 -0
  51. data/docs/fixtures/demo_project/README.md +6 -0
  52. data/docs/fixtures/demo_project/app/controllers/admin/audit_logs_controller.rb +19 -0
  53. data/docs/fixtures/demo_project/app/controllers/orders_controller.rb +26 -0
  54. data/docs/fixtures/demo_project/app/models/order.rb +20 -0
  55. data/docs/fixtures/demo_project/app/models/user.rb +19 -0
  56. data/docs/fixtures/demo_project/lib/api/client.rb +22 -0
  57. data/docs/fixtures/demo_project/lib/ops/jobs/cleanup_job.rb +16 -0
  58. data/docs/fixtures/demo_project/lib/ops/jobs/report_job.rb +17 -0
  59. data/docs/fixtures/demo_project/lib/payments/processor.rb +15 -0
  60. data/docs/fixtures/demo_project/lib/payments/refund_service.rb +15 -0
  61. data/docs/fixtures/demo_project/lib/payments/reporting/exporter.rb +16 -0
  62. data/docs/index.md +1 -0
  63. data/docs/license.md +3 -0
  64. data/docs/release_notes.md +3 -0
  65. data/docs/user/ADVANCED_USAGE.md +208 -115
  66. data/docs/user/CLI_FALLBACK_FOR_LLMS.md +2 -0
  67. data/docs/user/CLI_USAGE.md +276 -101
  68. data/docs/user/ERROR_HANDLING.md +4 -4
  69. data/docs/user/EXAMPLES.md +121 -128
  70. data/docs/user/INSTALLATION.md +9 -28
  71. data/docs/user/LIBRARY_API.md +227 -122
  72. data/docs/user/MCP_INTEGRATION.md +114 -203
  73. data/docs/user/README.md +5 -1
  74. data/docs/user/TROUBLESHOOTING.md +49 -27
  75. data/docs/user/installing-a-prelease-version-of-covloupe.md +43 -0
  76. data/docs/user/{V2-BREAKING-CHANGES.md → migrations/MIGRATING_TO_V2.md} +62 -72
  77. data/docs/user/migrations/MIGRATING_TO_V3.md +72 -0
  78. data/docs/user/migrations/MIGRATING_TO_V4.md +591 -0
  79. data/docs/user/migrations/README.md +22 -0
  80. data/docs/user/prompts/README.md +9 -0
  81. data/docs/user/prompts/non-web-coverage-analysis-prompt.md +103 -0
  82. data/docs/user/prompts/rails-coverage-analysis-prompt.md +94 -0
  83. data/docs/user/prompts/use-cli-not-mcp-prompt.md +53 -0
  84. data/examples/cli_demo.sh +77 -0
  85. data/examples/filter_and_table_demo-output.md +114 -0
  86. data/examples/filter_and_table_demo.rb +174 -0
  87. data/examples/fixtures/demo_project/coverage/.resultset.json +10 -0
  88. data/examples/mcp-inputs/README.md +66 -0
  89. data/examples/mcp-inputs/coverage_detailed.json +1 -0
  90. data/examples/mcp-inputs/coverage_raw.json +1 -0
  91. data/examples/mcp-inputs/coverage_summary.json +1 -0
  92. data/examples/mcp-inputs/list.json +1 -0
  93. data/examples/mcp-inputs/uncovered_lines.json +1 -0
  94. data/examples/prompts/README.md +27 -0
  95. data/examples/prompts/custom_resultset.txt +2 -0
  96. data/examples/prompts/detailed_with_source.txt +2 -0
  97. data/examples/prompts/list_lowest.txt +2 -0
  98. data/examples/prompts/summary.txt +2 -0
  99. data/examples/prompts/uncovered.txt +2 -0
  100. data/examples/success_predicates/README.md +198 -0
  101. data/examples/success_predicates/all_files_above_threshold_predicate.rb +21 -0
  102. data/examples/success_predicates/directory_specific_thresholds_predicate.rb +30 -0
  103. data/examples/success_predicates/project_coverage_minimum_predicate.rb +6 -0
  104. data/lib/cov_loupe/base_tool.rb +229 -20
  105. data/lib/cov_loupe/cli.rb +132 -23
  106. data/lib/cov_loupe/commands/base_command.rb +25 -6
  107. data/lib/cov_loupe/commands/command_factory.rb +0 -1
  108. data/lib/cov_loupe/commands/detailed_command.rb +10 -5
  109. data/lib/cov_loupe/commands/list_command.rb +2 -1
  110. data/lib/cov_loupe/commands/raw_command.rb +7 -5
  111. data/lib/cov_loupe/commands/summary_command.rb +12 -7
  112. data/lib/cov_loupe/commands/totals_command.rb +74 -10
  113. data/lib/cov_loupe/commands/uncovered_command.rb +7 -5
  114. data/lib/cov_loupe/commands/validate_command.rb +11 -3
  115. data/lib/cov_loupe/commands/version_command.rb +6 -4
  116. data/lib/cov_loupe/{app_config.rb → config/app_config.rb} +13 -5
  117. data/lib/cov_loupe/config/app_context.rb +43 -0
  118. data/lib/cov_loupe/config/boolean_type.rb +91 -0
  119. data/lib/cov_loupe/config/logger.rb +92 -0
  120. data/lib/cov_loupe/{option_normalizers.rb → config/option_normalizers.rb} +55 -24
  121. data/lib/cov_loupe/{option_parser_builder.rb → config/option_parser_builder.rb} +46 -24
  122. data/lib/cov_loupe/coverage/coverage_calculator.rb +53 -0
  123. data/lib/cov_loupe/coverage/coverage_reporter.rb +63 -0
  124. data/lib/cov_loupe/coverage/coverage_table_formatter.rb +133 -0
  125. data/lib/cov_loupe/{error_handler.rb → errors/error_handler.rb} +21 -33
  126. data/lib/cov_loupe/{errors.rb → errors/errors.rb} +48 -71
  127. data/lib/cov_loupe/formatters/formatters.rb +75 -0
  128. data/lib/cov_loupe/formatters/source_formatter.rb +18 -7
  129. data/lib/cov_loupe/formatters/table_formatter.rb +80 -0
  130. data/lib/cov_loupe/loaders/all.rb +15 -0
  131. data/lib/cov_loupe/loaders/all_cli.rb +10 -0
  132. data/lib/cov_loupe/loaders/all_mcp.rb +23 -0
  133. data/lib/cov_loupe/loaders/resultset_loader.rb +147 -0
  134. data/lib/cov_loupe/mcp_server.rb +3 -2
  135. data/lib/cov_loupe/model/model.rb +520 -0
  136. data/lib/cov_loupe/model/model_data.rb +13 -0
  137. data/lib/cov_loupe/model/model_data_cache.rb +116 -0
  138. data/lib/cov_loupe/option_parsers/env_options_parser.rb +17 -6
  139. data/lib/cov_loupe/option_parsers/error_helper.rb +16 -10
  140. data/lib/cov_loupe/output_chars.rb +192 -0
  141. data/lib/cov_loupe/paths/glob_utils.rb +100 -0
  142. data/lib/cov_loupe/{path_relativizer.rb → paths/path_relativizer.rb} +5 -13
  143. data/lib/cov_loupe/paths/path_utils.rb +265 -0
  144. data/lib/cov_loupe/paths/volume_case_sensitivity.rb +173 -0
  145. data/lib/cov_loupe/presenters/base_coverage_presenter.rb +9 -13
  146. data/lib/cov_loupe/presenters/coverage_payload_presenter.rb +21 -0
  147. data/lib/cov_loupe/presenters/payload_caching.rb +23 -0
  148. data/lib/cov_loupe/presenters/project_coverage_presenter.rb +73 -21
  149. data/lib/cov_loupe/presenters/project_totals_presenter.rb +16 -10
  150. data/lib/cov_loupe/repositories/coverage_repository.rb +149 -0
  151. data/lib/cov_loupe/resolvers/coverage_line_resolver.rb +90 -76
  152. data/lib/cov_loupe/resolvers/{resolver_factory.rb → resolver_helpers.rb} +6 -5
  153. data/lib/cov_loupe/resolvers/resultset_path_resolver.rb +40 -12
  154. data/lib/cov_loupe/scripts/command_execution.rb +113 -0
  155. data/lib/cov_loupe/scripts/latest_ci_status.rb +97 -0
  156. data/lib/cov_loupe/scripts/pre_release_check.rb +164 -0
  157. data/lib/cov_loupe/scripts/setup_doc_server.rb +23 -0
  158. data/lib/cov_loupe/scripts/start_doc_server.rb +24 -0
  159. data/lib/cov_loupe/staleness/stale_status.rb +23 -0
  160. data/lib/cov_loupe/staleness/staleness_checker.rb +328 -0
  161. data/lib/cov_loupe/staleness/staleness_message_formatter.rb +91 -0
  162. data/lib/cov_loupe/tools/coverage_detailed_tool.rb +14 -15
  163. data/lib/cov_loupe/tools/coverage_raw_tool.rb +14 -14
  164. data/lib/cov_loupe/tools/coverage_summary_tool.rb +16 -16
  165. data/lib/cov_loupe/tools/coverage_table_tool.rb +139 -21
  166. data/lib/cov_loupe/tools/coverage_totals_tool.rb +31 -13
  167. data/lib/cov_loupe/tools/help_tool.rb +16 -20
  168. data/lib/cov_loupe/tools/list_tool.rb +65 -0
  169. data/lib/cov_loupe/tools/uncovered_lines_tool.rb +14 -14
  170. data/lib/cov_loupe/tools/validate_tool.rb +18 -24
  171. data/lib/cov_loupe/tools/version_tool.rb +8 -3
  172. data/lib/cov_loupe/version.rb +1 -1
  173. data/lib/cov_loupe.rb +83 -55
  174. metadata +184 -154
  175. data/docs/dev/BRANCH_ONLY_COVERAGE.md +0 -158
  176. data/docs/dev/arch-decisions/001-x-arch-decision.md +0 -95
  177. data/docs/dev/arch-decisions/002-x-arch-decision.md +0 -159
  178. data/docs/dev/arch-decisions/003-x-arch-decision.md +0 -165
  179. data/lib/cov_loupe/app_context.rb +0 -26
  180. data/lib/cov_loupe/constants.rb +0 -22
  181. data/lib/cov_loupe/coverage_reporter.rb +0 -31
  182. data/lib/cov_loupe/formatters.rb +0 -51
  183. data/lib/cov_loupe/mode_detector.rb +0 -56
  184. data/lib/cov_loupe/model.rb +0 -339
  185. data/lib/cov_loupe/presenters/coverage_detailed_presenter.rb +0 -14
  186. data/lib/cov_loupe/presenters/coverage_raw_presenter.rb +0 -14
  187. data/lib/cov_loupe/presenters/coverage_summary_presenter.rb +0 -14
  188. data/lib/cov_loupe/presenters/coverage_uncovered_presenter.rb +0 -14
  189. data/lib/cov_loupe/resultset_loader.rb +0 -131
  190. data/lib/cov_loupe/staleness_checker.rb +0 -247
  191. data/lib/cov_loupe/table_formatter.rb +0 -64
  192. data/lib/cov_loupe/tools/all_files_coverage_tool.rb +0 -51
  193. data/lib/cov_loupe/util.rb +0 -88
  194. data/spec/MCP_INTEGRATION_TESTS_README.md +0 -111
  195. data/spec/TIMESTAMPS.md +0 -48
  196. data/spec/all_files_coverage_tool_spec.rb +0 -53
  197. data/spec/app_config_spec.rb +0 -142
  198. data/spec/base_tool_spec.rb +0 -62
  199. data/spec/cli/show_default_report_spec.rb +0 -33
  200. data/spec/cli_enumerated_options_spec.rb +0 -90
  201. data/spec/cli_error_spec.rb +0 -184
  202. data/spec/cli_format_spec.rb +0 -123
  203. data/spec/cli_json_options_spec.rb +0 -50
  204. data/spec/cli_source_spec.rb +0 -44
  205. data/spec/cli_spec.rb +0 -192
  206. data/spec/cli_table_spec.rb +0 -28
  207. data/spec/cli_usage_spec.rb +0 -42
  208. data/spec/commands/base_command_spec.rb +0 -107
  209. data/spec/commands/command_factory_spec.rb +0 -76
  210. data/spec/commands/detailed_command_spec.rb +0 -34
  211. data/spec/commands/list_command_spec.rb +0 -28
  212. data/spec/commands/raw_command_spec.rb +0 -69
  213. data/spec/commands/summary_command_spec.rb +0 -34
  214. data/spec/commands/totals_command_spec.rb +0 -34
  215. data/spec/commands/uncovered_command_spec.rb +0 -55
  216. data/spec/commands/validate_command_spec.rb +0 -213
  217. data/spec/commands/version_command_spec.rb +0 -38
  218. data/spec/constants_spec.rb +0 -61
  219. data/spec/cov_loupe/formatters/source_formatter_spec.rb +0 -267
  220. data/spec/cov_loupe/formatters_spec.rb +0 -76
  221. data/spec/cov_loupe/presenters/base_coverage_presenter_spec.rb +0 -79
  222. data/spec/cov_loupe_model_spec.rb +0 -454
  223. data/spec/cov_loupe_module_spec.rb +0 -37
  224. data/spec/cov_loupe_opts_spec.rb +0 -185
  225. data/spec/coverage_reporter_spec.rb +0 -102
  226. data/spec/coverage_table_tool_spec.rb +0 -59
  227. data/spec/coverage_totals_tool_spec.rb +0 -37
  228. data/spec/error_handler_spec.rb +0 -197
  229. data/spec/error_mode_spec.rb +0 -139
  230. data/spec/errors_edge_cases_spec.rb +0 -312
  231. data/spec/errors_stale_spec.rb +0 -83
  232. data/spec/file_based_mcp_tools_spec.rb +0 -99
  233. data/spec/help_tool_spec.rb +0 -26
  234. data/spec/integration_spec.rb +0 -789
  235. data/spec/logging_fallback_spec.rb +0 -128
  236. data/spec/mcp_logging_spec.rb +0 -44
  237. data/spec/mcp_server_integration_spec.rb +0 -23
  238. data/spec/mcp_server_spec.rb +0 -106
  239. data/spec/mode_detector_spec.rb +0 -153
  240. data/spec/model_error_handling_spec.rb +0 -269
  241. data/spec/model_staleness_spec.rb +0 -79
  242. data/spec/option_normalizers_spec.rb +0 -203
  243. data/spec/option_parsers/env_options_parser_spec.rb +0 -221
  244. data/spec/option_parsers/error_helper_spec.rb +0 -222
  245. data/spec/path_relativizer_spec.rb +0 -98
  246. data/spec/presenters/coverage_detailed_presenter_spec.rb +0 -19
  247. data/spec/presenters/coverage_raw_presenter_spec.rb +0 -15
  248. data/spec/presenters/coverage_summary_presenter_spec.rb +0 -15
  249. data/spec/presenters/coverage_uncovered_presenter_spec.rb +0 -16
  250. data/spec/presenters/project_coverage_presenter_spec.rb +0 -87
  251. data/spec/presenters/project_totals_presenter_spec.rb +0 -144
  252. data/spec/resolvers/coverage_line_resolver_spec.rb +0 -282
  253. data/spec/resolvers/resolver_factory_spec.rb +0 -61
  254. data/spec/resolvers/resultset_path_resolver_spec.rb +0 -60
  255. data/spec/resultset_loader_spec.rb +0 -167
  256. data/spec/shared_examples/README.md +0 -115
  257. data/spec/shared_examples/coverage_presenter_examples.rb +0 -66
  258. data/spec/shared_examples/file_based_mcp_tools.rb +0 -179
  259. data/spec/shared_examples/formatted_command_examples.rb +0 -64
  260. data/spec/shared_examples/mcp_tool_text_json_response.rb +0 -16
  261. data/spec/spec_helper.rb +0 -127
  262. data/spec/staleness_checker_spec.rb +0 -374
  263. data/spec/staleness_more_spec.rb +0 -42
  264. data/spec/support/cli_helpers.rb +0 -22
  265. data/spec/support/control_flow_helpers.rb +0 -20
  266. data/spec/support/fake_mcp.rb +0 -40
  267. data/spec/support/io_helpers.rb +0 -29
  268. data/spec/support/mcp_helpers.rb +0 -35
  269. data/spec/support/mcp_runner.rb +0 -66
  270. data/spec/support/mocking_helpers.rb +0 -30
  271. data/spec/table_format_spec.rb +0 -70
  272. data/spec/tools/validate_tool_spec.rb +0 -132
  273. data/spec/tools_error_handling_spec.rb +0 -130
  274. data/spec/util_spec.rb +0 -154
  275. data/spec/version_spec.rb +0 -123
  276. data/spec/version_tool_spec.rb +0 -141
  277. /data/{spec/fixtures/project1 → examples/fixtures/demo_project}/lib/bar.rb +0 -0
  278. /data/{spec/fixtures/project1 → examples/fixtures/demo_project}/lib/foo.rb +0 -0
  279. /data/lib/cov_loupe/{config_parser.rb → config/config_parser.rb} +0 -0
  280. /data/lib/cov_loupe/{predicate_evaluator.rb → config/predicate_evaluator.rb} +0 -0
  281. /data/lib/cov_loupe/{error_handler_factory.rb → errors/error_handler_factory.rb} +0 -0
@@ -1,22 +1,25 @@
1
1
  # Advanced Usage Guide
2
2
 
3
- [Back to main README](../README.md)
3
+ [Back to main README](../index.md)
4
4
 
5
5
  > Examples use `clp`, an alias pointed at the demo fixture with partial coverage:
6
- > `alias clp='cov-loupe --root docs/fixtures/demo_project'`
7
- > Swap `clp` with `cov-loupe` if you want to target your own project/resultset.
6
+ >
7
+ > `alias clp='cov-loupe -R docs/fixtures/demo_project' # (-R = --root_dir)`
8
+ >
9
+ > Replace `clp` with `cov-loupe` if you want to target your own project/resultset.
8
10
 
9
11
  ## Table of Contents
10
12
 
11
13
  - [Advanced MCP Integration](#advanced-mcp-integration)
12
- - [Staleness Detection & Validation](#staleness-detection--validation)
14
+ - [Staleness Detection & Validation](#staleness-detection-validation)
13
15
  - [Advanced Path Resolution](#advanced-path-resolution)
14
16
  - [Error Handling Strategies](#error-handling-strategies)
15
17
  - [Custom Ruby Integration](#custom-ruby-integration)
16
- - [CI/CD Integration Patterns](#cicd-integration-patterns)
17
- - [Advanced Filtering & Glob Patterns](#advanced-filtering--glob-patterns)
18
+ - [CI/CD Integration](#cicd-integration)
19
+ - [Advanced Filtering & Glob Patterns](#advanced-filtering-glob-patterns)
18
20
  - [Performance Optimization](#performance-optimization)
19
21
  - [Custom Output Processing](#custom-output-processing)
22
+ - [Multi-Suite Coverage Merging](#multi-suite-coverage-merging)
20
23
 
21
24
  ---
22
25
 
@@ -45,63 +48,74 @@ The MCP server uses structured error responses:
45
48
 
46
49
  The MCP server logs to `cov_loupe.log` in the current directory by default.
47
50
 
48
- To override the default log file location, specify the `--log-file` argument wherever and however you configure your MCP server. For example, to log to a different file path, include `--log-file /path/to/logfile.log` in your server configuration. To log to standard error, use `--log-file stderr`.
51
+ To override the default log file location, specify the `--log-file` (or `-l`) argument wherever and however you configure your MCP server. For example, to log to a different file path, include `-l /path/to/logfile.log` in your server configuration. To log to standard error, use `-l stderr`.
49
52
 
50
- **Note:** Logging to `stdout` is not permitted in MCP mode.
53
+ **Warning:** Log files may grow unbounded in long-running or CI usage. Consider using a log rotation tool or periodically cleaning up the log file if this is a concern.
54
+
55
+ **Note:** Logging to `stdout` is not permitted in MCP mode since it would interfere with the request processing.
51
56
 
52
57
  ### Testing MCP Server Manually
53
58
 
54
- Use JSON-RPC over stdin to test the MCP server:
59
+ Use JSON-RPC over stdin to test the MCP server. **Note:** CLI flags set defaults for MCP tool calls, but per-request JSON parameters still win. Use `-R`/`-r` when you want server-wide defaults, or pass `root`/`resultset` per request.
55
60
 
56
61
  ```sh
57
- # Get version
58
- echo '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"version_tool","arguments":{}}}' | clp
62
+ # Get version (no parameters needed)
63
+ echo '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"version_tool","arguments":{}}}' | cov-loupe -m mcp
59
64
 
60
- # Get file summary
61
- echo '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"coverage_summary_tool","arguments":{"path":"app/models/order.rb"}}}' | clp
65
+ # Get file summary (include root parameter in JSON)
66
+ echo '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"coverage_summary_tool","arguments":{"path":"app/models/order.rb","root":"docs/fixtures/demo_project"}}}' | cov-loupe -m mcp
62
67
 
63
- # List all files with sorting
64
- echo '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"all_files_coverage_tool","arguments":{"sort_order":"ascending"}}}' | clp
68
+ # List all files with sorting (include root parameter)
69
+ echo '{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"list_tool","arguments":{"sort_order":"ascending","root":"docs/fixtures/demo_project"}}}' | cov-loupe -m mcp
65
70
 
66
- # Get uncovered lines
67
- echo '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"uncovered_lines_tool","arguments":{"path":"app/controllers/orders_controller.rb"}}}' | clp
71
+ # Get uncovered lines (include root parameter)
72
+ echo '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"uncovered_lines_tool","arguments":{"path":"app/controllers/orders_controller.rb","root":"docs/fixtures/demo_project"}}}' | cov-loupe -m mcp
68
73
  ```
69
74
 
75
+ **Why not use `clp` alias here?** `clp` is useful for CLI subcommands, but MCP calls run a long-lived server process. You can pass `-R` at startup to set defaults, or set `root` explicitly in each JSON request when you want to be explicit or override the defaults.
76
+
77
+ ---
78
+
79
+ ## Sorting Coverage Lists with n/a Entries
80
+
81
+ When a file has no executable lines, its coverage percentage is reported as `n/a`. In list outputs, `n/a` entries are grouped separately from numeric percentages. The default descending sort order places `n/a` entries above `100%` coverage so they appear earlier in the list, while still keeping low numeric percentages toward the bottom for attention.
82
+
83
+ If you need to treat `n/a` differently, post-process the JSON output from `list` or `list_tool` and apply your own sort or filtering rules.
84
+
70
85
  ---
71
86
 
72
87
  ## Staleness Detection & Validation
73
88
 
74
- ### Understanding Staleness Modes
89
+ ### Understanding Staleness Checks
75
90
 
76
- Staleness checking prevents using outdated coverage data. Two modes are available:
91
+ Staleness checking prevents using outdated coverage data. The behavior is controlled by the boolean `raise_on_stale` setting:
77
92
 
78
- **Mode: `off` (default)**
79
- - No validation, fastest operation
80
- - Coverage data used as-is
81
- - Stale indicators still computed but don't block operations
93
+ **`raise_on_stale: false` (default)**
94
+ - Coverage data is returned even if stale
95
+ - Stale indicators are still computed
96
+ - Best for exploratory reporting
82
97
 
83
- **Mode: `error`**
84
- - Strict validation enabled
85
- - Raises errors if coverage is outdated
86
- - Perfect for CI/CD pipelines
98
+ **`raise_on_stale: true`**
99
+ - Raises errors when stale coverage is detected
100
+ - Recommended for CI/CD enforcement
87
101
 
88
102
  ### File-Level Staleness
89
103
 
90
104
  A file is considered stale when any of the following are true:
91
- 1. Source file modified after coverage generation
105
+ 1. Source file modified after coverage generation (requires timestamps - see [Timestamp Warnings](#timestamp-warnings))
92
106
  2. Line count differs from coverage array length
93
107
  3. File exists in coverage but deleted from filesystem
94
108
 
95
109
  **CLI Usage:**
96
110
  ```sh
97
- # Fail if any file is stale (option before subcommand)
98
- clp --staleness error summary app/models/order.rb
111
+ # Fail if the file is stale
112
+ clp -S true summary app/models/order.rb # -S = --raise-on-stale
99
113
  ```
100
114
 
101
115
  **Ruby API:**
102
116
  ```ruby
103
117
  model = CovLoupe::CoverageModel.new(
104
- staleness: 'error'
118
+ raise_on_stale: true
105
119
  )
106
120
 
107
121
  begin
@@ -119,30 +133,31 @@ end
119
133
  Detects system-wide staleness issues:
120
134
 
121
135
  **Conditions Checked:**
122
- 1. **Newer files** - Any tracked file modified after coverage
136
+ 1. **Newer files** - Any tracked file modified after coverage (requires timestamps - see [Timestamp Warnings](#timestamp-warnings))
123
137
  2. **Missing files** - Tracked files with no coverage data
124
138
  3. **Deleted files** - Coverage exists for non-existent files
125
139
 
126
140
  **CLI Usage:**
127
- ```sh
128
- # Track specific patterns
129
- clp --staleness error \
130
- -g "lib/payments/**/*.rb" \
131
- -g "lib/ops/jobs/**/*.rb" # -g = --tracked-globs
132
141
 
133
- # Combine with JSON output for parsing
134
- clp --staleness error -fJ list > stale-check.json
142
+ You can see if _any_ files in the project are stale by running the (implicit here) `list` command
143
+ with `--raise-on-stale` and checking the exit code:
144
+
145
+ ```sh
146
+ $ cov-loupe -S true list
147
+ Coverage data stale (project): CovLoupe::CoverageDataProjectStaleError
148
+ Coverage - time: 2025-12-10T18:23:00Z (local 2025-12-11T02:23:00+08:00)
149
+ Newer files (1): - lib/cov_loupe/version.rb
150
+ Resultset - /path/to/project/coverage/.resultset.json
151
+ $ echo $?
152
+ 1
135
153
  ```
136
154
 
137
155
  **Ruby API:**
138
156
  ```ruby
139
- model = CovLoupe::CoverageModel.new(
140
- staleness: 'error',
141
- tracked_globs: ['lib/payments/**/*.rb', 'lib/ops/jobs/**/*.rb']
142
- )
157
+ model = CovLoupe::CoverageModel.new(raise_on_stale: true)
143
158
 
144
159
  begin
145
- files = model.all_files(check_stale: true)
160
+ model.list(raise_on_stale: true)
146
161
  rescue CovLoupe::CoverageDataProjectStaleError => e
147
162
  puts "Newer files: #{e.newer_files.join(', ')}"
148
163
  puts "Missing from coverage: #{e.missing_files.join(', ')}"
@@ -150,39 +165,62 @@ rescue CovLoupe::CoverageDataProjectStaleError => e
150
165
  end
151
166
  ```
152
167
 
153
- ### Staleness in CI/CD
168
+ ### Timestamp Warnings
154
169
 
155
- Staleness checking is particularly useful in CI/CD pipelines to ensure coverage data is fresh:
170
+ When coverage data lacks timestamps (e.g., manually created resultsets or older SimpleCov versions), cov-loupe displays a warning in both CLI and MCP modes:
156
171
 
157
- ```sh
158
- # Run tests to generate coverage
159
- bundle exec rspec
172
+ ```
173
+ WARNING: Coverage timestamps are missing. Time-based staleness checks were skipped.
174
+ Files may appear "ok" even if source code is newer than the coverage data.
175
+ Check your coverage tool configuration to ensure timestamps are recorded.
176
+ ```
160
177
 
161
- # Validate coverage freshness (fails with exit code 1 if stale)
162
- clp --staleness error -g "lib/**/*.rb"
178
+ **What this means:**
179
+ - Time-based staleness checks (the `"newer"` indicator) cannot run without timestamps
180
+ - Files modified after coverage collection won't be flagged as stale
181
+ - Only line count mismatches and missing files will be detected
182
+ - The `timestamp_status` field in JSON output will show `"missing"` instead of `"ok"`
163
183
 
164
- # Export validated data for CI artifacts
165
- clp -fJ list > coverage.json
166
- ```
184
+ **Where it appears:**
185
+ - **CLI table format:** After the coverage table in `list` output
186
+ - **CLI JSON format:** After JSON output, and in the `timestamp_status` field
187
+ - **MCP mode:** In `coverage_table_tool` output after the exclusions summary
188
+ - **MCP JSON:** In the `timestamp_status` field of `list_tool` responses
189
+
190
+ **How to fix:**
191
+
192
+ Modern SimpleCov versions automatically include timestamps in `.resultset.json`. If you see this warning:
167
193
 
168
- The `--staleness error` flag causes the command to exit with a non-zero status when coverage is outdated, making it suitable for pipeline failure conditions.
194
+ 1. Ensure SimpleCov is up to date (`gem update simplecov`)
195
+ 2. Regenerate coverage data (`bundle exec rspec`)
196
+ 3. If using custom resultset generation, ensure timestamps are included
197
+
198
+ **Example timestamp in `.resultset.json`:**
199
+ ```json
200
+ {
201
+ "RSpec": {
202
+ "coverage": { ... },
203
+ "timestamp": 1704067200
204
+ }
205
+ }
206
+ ```
169
207
 
170
208
  ---
171
209
 
172
210
  ## Advanced Path Resolution
173
211
 
174
- ### Multi-Strategy Path Matching
212
+ ### Path Matching Strategy
175
213
 
176
- Path resolution order:
214
+ Path resolution uses two strategies in order:
177
215
 
178
- 1. **Exact absolute path match**
179
- 2. **Relative path resolution from root**
216
+ 1. **Exact absolute path match** - Direct lookup using the full path
217
+ 2. **Relative path resolution** - Strips project root and retries with relative path
180
218
 
181
219
  ```ruby
182
- model = CovLoupe::CoverageModel.new(root: '/project')
220
+ model = CovLoupe::CoverageModel.new(root: '/path/to/project')
183
221
 
184
- model.summary_for('/project/app/models/order.rb') # Absolute
185
- model.summary_for('app/models/order.rb') # Relative
222
+ model.summary_for('/path/to/project/app/models/order.rb') # Absolute
223
+ model.summary_for('app/models/order.rb') # Relative
186
224
  ```
187
225
 
188
226
  ### Working with Multiple Projects
@@ -190,19 +228,19 @@ model.summary_for('app/models/order.rb') # Relative
190
228
  ```ruby
191
229
  # Project A
192
230
  model_a = CovLoupe::CoverageModel.new(
193
- root: '/projects/service-a',
194
- resultset: '/projects/service-a/coverage/.resultset.json'
231
+ root: '/path/to/projects/service-a',
232
+ resultset: '/path/to/projects/service-a/coverage/.resultset.json'
195
233
  )
196
234
 
197
235
  # Project B
198
236
  model_b = CovLoupe::CoverageModel.new(
199
- root: '/projects/service-b',
200
- resultset: '/projects/service-b/tmp/coverage/.resultset.json'
237
+ root: '/path/to/projects/service-b',
238
+ resultset: '/path/to/projects/service-b/tmp/coverage/.resultset.json'
201
239
  )
202
240
 
203
241
  # Compare coverage
204
- coverage_a = model_a.all_files
205
- coverage_b = model_b.all_files
242
+ coverage_a = model_a.list
243
+ coverage_b = model_b.list
206
244
  ```
207
245
 
208
246
 
@@ -240,7 +278,7 @@ require 'cov_loupe'
240
278
 
241
279
  begin
242
280
  model = CovLoupe::CoverageModel.new(
243
- root: '/project',
281
+ root: '/path/to/project',
244
282
  resultset: '/nonexistent/.resultset.json'
245
283
  )
246
284
  rescue CovLoupe::FileError => e
@@ -276,7 +314,13 @@ cli = CovLoupe::CoverageCLI.new(error_handler: CustomErrorHandler.new)
276
314
 
277
315
  ### Building Custom Coverage Policies
278
316
 
279
- Use the `validate` subcommand to enforce custom coverage policies in CI/CD. Example predicates are in [`examples/success_predicates/`](../../examples/success_predicates/).
317
+ Use the `validate` subcommand to enforce custom coverage policies in CI/CD.
318
+ Example predicates are in [`examples/success_predicates/`](../examples/success_predicates.md).
319
+
320
+ The predicate can be any Ruby object that responds to `call` and accepts a `CoverageModel`
321
+ as its argument. This is usually a lambda (proc), but it can also be a nonlambda proc, a class,
322
+ or an instance with a `call` method. The predicate should return a truthy value for success
323
+ or `false`/`nil` for failure.
280
324
 
281
325
  > **⚠️ SECURITY WARNING**
282
326
  >
@@ -295,7 +339,7 @@ Use the `validate` subcommand to enforce custom coverage policies in CI/CD. Exam
295
339
  **Quick Usage:**
296
340
  ```sh
297
341
  # All files must be >= 80%
298
- clp validate examples/success_predicates/all_files_above_threshold_predicate.rb
342
+ clp validate examples/success_predicates/list_above_threshold_predicate.rb
299
343
 
300
344
  # Total project coverage >= 85%
301
345
  clp validate examples/success_predicates/project_coverage_minimum_predicate.rb
@@ -304,7 +348,7 @@ clp validate examples/success_predicates/project_coverage_minimum_predicate.rb
304
348
  clp validate coverage_policy.rb
305
349
 
306
350
  # Inline string mode
307
- clp validate -i '->(m) { m.all_files.all? { |f| f["percentage"] >= 80 } }'
351
+ clp validate -i '->(m) { m.list["files"].all? { |f| f["percentage"] >= 80 } }'
308
352
  ```
309
353
 
310
354
  **Creating a predicate:**
@@ -312,7 +356,7 @@ clp validate -i '->(m) { m.all_files.all? { |f| f["percentage"] >= 80 } }'
312
356
  # coverage_policy.rb
313
357
  ->(model) do
314
358
  # All files must have >= 80% coverage
315
- model.all_files.all? { |f| f['percentage'] >= 80 }
359
+ model.list['files'].all? { |f| f['percentage'] >= 80 }
316
360
  end
317
361
  ```
318
362
 
@@ -323,7 +367,7 @@ end
323
367
  class CoveragePolicy
324
368
  def call(model)
325
369
  threshold = 80
326
- low_files = model.all_files.select { |f| f['percentage'] < threshold }
370
+ low_files = model.list['files'].select { |f| f['percentage'] < threshold }
327
371
 
328
372
  if low_files.empty?
329
373
  puts "✓ All files have >= #{threshold}% coverage"
@@ -344,26 +388,27 @@ CoveragePolicy.new
344
388
  - `1` - Predicate returned falsy (fail)
345
389
  - `2` - Predicate raised an error
346
390
 
347
- See [examples/success_predicates/README.md](../../examples/success_predicates/README.md) for more examples.
391
+ See [examples/success_predicates/README.md](../examples/success_predicates.md) for more examples.
348
392
 
349
393
  ### Path Relativization
350
394
 
351
395
  Convert absolute paths to relative for cleaner output:
352
396
 
353
397
  ```ruby
354
- model = CovLoupe::CoverageModel.new(root: '/project')
398
+ model = CovLoupe::CoverageModel.new(root: '/path/to/project')
355
399
 
356
400
  # Get data with absolute paths
357
401
  data = model.summary_for('app/models/order.rb')
358
- # => { 'file' => '/project/app/models/order.rb', ... }
402
+ # => { 'file' => '/path/to/project/app/models/order.rb', ... }
359
403
 
360
404
  # Relativize paths
361
405
  relative_data = model.relativize(data)
362
406
  # => { 'file' => 'app/models/order.rb', ... }
363
407
 
364
- # Works with arrays too
365
- files = model.all_files
366
- relative_files = model.relativize(files)
408
+ # Works with list payloads too
409
+ list_result = model.list
410
+ relative_list = model.relativize(list_result)
411
+ relative_files = relative_list['files']
367
412
  ```
368
413
 
369
414
  ---
@@ -376,7 +421,7 @@ The CLI is designed for CI/CD use with features that integrate naturally into pi
376
421
 
377
422
  - **Exit codes**: Non-zero on failure, making it suitable for pipeline failure conditions
378
423
  - **JSON output**: `-fJ` format for parsing by CI tools and custom processing
379
- - **Staleness checking**: `--stale error` to fail on outdated coverage data
424
+ - **Staleness checking**: `--raise-on-stale true` to fail on outdated coverage data
380
425
  - **Success predicates**: Custom Ruby policies for coverage enforcement
381
426
 
382
427
  ### Basic CI Pattern
@@ -386,7 +431,7 @@ The CLI is designed for CI/CD use with features that integrate naturally into pi
386
431
  bundle exec rspec
387
432
 
388
433
  # 2. Validate coverage freshness (fails with exit code 1 if stale)
389
- clp --staleness error -g "lib/**/*.rb"
434
+ clp -S true -g "lib/**/*.rb"
390
435
 
391
436
  # 3. Export data for CI artifacts or further processing
392
437
  clp -fJ list > coverage.json
@@ -419,37 +464,67 @@ For platform-specific integration examples (GitHub Actions, GitLab CI, Jenkins,
419
464
 
420
465
  ### Tracked Globs Overview
421
466
 
422
- Tracked globs serve two purposes:
423
- 1. **Filter output** - Only show matching files
424
- 2. **Validate coverage** - Ensure new files have coverage
467
+ **Default behavior:** By default, `--tracked-globs` is empty (`[]`), which means all files in the coverage resultset are shown. This ensures transparency—you see exactly what SimpleCov measured without any filtering.
468
+
469
+ **Why opt-in filtering?**
470
+ - **Coverage results are not hidden** - Results are not excluded because their filespecs did not match default tracked globs
471
+ - **Meaningful validation** - `missing_tracked_files` only flags files you explicitly expect to have coverage
472
+ - **Project flexibility** - Different projects use different directory structures
473
+
474
+ **Important:** Files lacking any coverage at all (not loaded during tests) will not appear in the resultset and therefore won't be visible with the default empty array. To detect such files, you must set `--tracked-globs` to match the files you expect to have coverage.
475
+
476
+ **Two purposes of tracked globs:**
477
+ 1. **Exclude unwanted results** - Only show files from the resultset that match the patterns
478
+ 2. **Include files with or without coverage** - Report files that match the patterns but aren't in the resultset (reported in `missing_tracked_files` for `list`, `missing_from_coverage` for `totals`)
479
+
480
+ **Best practice:** Set `COV_LOUPE_OPTS` to match your SimpleCov `track_files` configuration:
481
+
482
+ ```ruby
483
+ # spec_helper.rb
484
+ SimpleCov.start do
485
+ add_filter '/spec/'
486
+ track_files 'lib/**/*.rb'
487
+ track_files 'app/**/*.rb'
488
+ end
489
+ ```
490
+
491
+ ```sh
492
+ # Shell config (.bashrc, .zshrc, etc.)
493
+ export COV_LOUPE_OPTS="--tracked-globs lib/**/*.rb,app/**/*.rb"
494
+ ```
495
+
496
+ This alignment ensures:
497
+ - `list` and `totals` output matches SimpleCov's scope
498
+ - `missing_tracked_files` (in `list`) reports files that SimpleCov should track but hasn't measured
499
+ - No surprises from default patterns that don't match your project
425
500
 
426
501
  ### Pattern Syntax
427
502
 
428
503
  Uses Ruby's `File.fnmatch` with extended glob support:
429
504
 
430
505
  ```sh
431
- # Single directory
432
- --tracked-globs "lib/**/*.rb"
506
+ # Single directory, recursive
507
+ -g "lib/**/*.rb"
433
508
 
434
509
  # Multiple patterns
435
- --tracked-globs "lib/payments/**/*.rb" --tracked-globs "lib/ops/jobs/**/*.rb"
510
+ -g "lib/payments/**/*.rb" -g "lib/ops/jobs/**/*.rb"
436
511
 
437
- # Exclude patterns (use CLI filtering)
438
- clp -fJ list | jq '.files[] | select(.file | test("spec") | not)'
512
+ # Exclude patterns (use CLI filtering to exclude ops jobs)
513
+ clp -fJ list | jq '.files[] | select(.file | test("ops") | not)'
439
514
 
440
515
  # Ruby alternative:
441
516
  clp -fJ list | ruby -r json -e '
442
- JSON.parse($stdin.read)["files"].reject { |f| f["file"].include?("spec") }.each do |f|
517
+ JSON.parse($stdin.read)["files"].reject { |f| f["file"].include?("ops") }.each do |f|
443
518
  puts JSON.pretty_generate(f)
444
519
  end
445
520
  '
446
521
 
447
522
  # Rexe alternative:
448
- clp -fJ list | rexe -ij -mb -oJ 'self["files"].reject { |f| f["file"].include?("spec") }'
523
+ clp -fJ list | rexe -ij -mb -oJ 'self["files"].reject { |f| f["file"].include?("ops") }'
449
524
 
450
525
  # Complex patterns
451
- --tracked-globs "lib/{models,controllers}/**/*.rb"
452
- --tracked-globs "app/**/concerns/*.rb"
526
+ -g "lib/{models,controllers}/**/*.rb"
527
+ -g "app/**/concerns/*.rb"
453
528
  ```
454
529
 
455
530
  ### Use Cases
@@ -466,7 +541,7 @@ clp -g "lib/domain/**/*.rb" list
466
541
  **2. Ensure New Files Have Coverage:**
467
542
  ```sh
468
543
  # Fail if any tracked file lacks coverage
469
- clp --staleness error -g "lib/features/**/*.rb"
544
+ clp -S true -g "lib/features/**/*.rb"
470
545
  ```
471
546
 
472
547
  **3. Multi-tier Reporting:**
@@ -483,22 +558,22 @@ done
483
558
  model = CovLoupe::CoverageModel.new
484
559
 
485
560
  # Filter files in output
486
- api_files = model.all_files(
561
+ api_files = model.list(
487
562
  tracked_globs: ['lib/api/**/*.rb']
488
- )
563
+ )['files']
489
564
 
490
565
  # Multi-pattern filtering
491
- core_files = model.all_files(
566
+ core_files = model.list(
492
567
  tracked_globs: [
493
568
  'lib/core/**/*.rb',
494
569
  'lib/domain/**/*.rb'
495
570
  ]
496
- )
571
+ )['files']
497
572
 
498
573
  # Validate specific subsystems
499
574
  begin
500
- model.all_files(
501
- check_stale: true,
575
+ model.list(
576
+ raise_on_stale: true,
502
577
  tracked_globs: ['lib/critical/**/*.rb']
503
578
  )
504
579
  rescue CovLoupe::CoverageDataProjectStaleError => e
@@ -519,13 +594,13 @@ The `CoverageModel` reads `.resultset.json` once at initialization:
519
594
  ```ruby
520
595
  # Good: Single model for multiple queries
521
596
  model = CovLoupe::CoverageModel.new
522
- files = model.all_files
597
+ files = model.list['files']
523
598
  file1 = model.summary_for('lib/a.rb')
524
599
  file2 = model.summary_for('lib/b.rb')
525
600
 
526
601
  # Bad: Re-reads coverage for each operation
527
602
  model1 = CovLoupe::CoverageModel.new
528
- files = model1.all_files
603
+ files = model1.list['files']
529
604
 
530
605
  model2 = CovLoupe::CoverageModel.new
531
606
  file1 = model2.summary_for('lib/a.rb')
@@ -554,13 +629,13 @@ Use `tracked_globs` to reduce data processing:
554
629
 
555
630
  ```ruby
556
631
  # Bad: Filter after loading all data
557
- all_files = model.all_files
558
- api_files = all_files.select { |f| f['file'].include?('api') }
632
+ list = model.list['files']
633
+ api_files = list.select { |f| f['file'].include?('api') }
559
634
 
560
635
  # Good: Filter during query
561
- api_files = model.all_files(
636
+ api_files = model.list(
562
637
  tracked_globs: ['lib/api/**/*.rb']
563
- )
638
+ )['files']
564
639
  ```
565
640
 
566
641
  ### Caching Coverage Models
@@ -591,7 +666,7 @@ class CoverageCache
591
666
  end
592
667
 
593
668
  cache = CoverageCache.new
594
- model = cache.model_for('/project')
669
+ model = cache.model_for('/path/to/project')
595
670
  ```
596
671
 
597
672
  ---
@@ -605,7 +680,7 @@ model = cache.model_for('/project')
605
680
  require 'csv'
606
681
 
607
682
  model = CovLoupe::CoverageModel.new
608
- files = model.all_files
683
+ files = model.list['files']
609
684
 
610
685
  CSV.open('coverage.csv', 'w') do |csv|
611
686
  csv << ['File', 'Coverage %', 'Lines Covered', 'Total Lines', 'Stale']
@@ -648,7 +723,9 @@ template = ERB.new(<<~HTML)
648
723
  HTML
649
724
 
650
725
  model = CovLoupe::CoverageModel.new
651
- files = model.relativize(model.all_files)
726
+ list_result = model.list
727
+ relative_list = model.relativize(list_result)
728
+ files = relative_list['files']
652
729
  File.write('coverage.html', template.result(binding))
653
730
  ```
654
731
 
@@ -659,13 +736,13 @@ The CLI supports annotated source viewing:
659
736
  ```sh
660
737
  # Show uncovered lines with context
661
738
  clp uncovered app/models/order.rb \
662
- --source uncovered \
663
- --source-context 3
739
+ -s uncovered \
740
+ -c 3 # -s = --source, -c = --context-lines
664
741
 
665
742
  # Show full file with coverage annotations
666
743
  clp uncovered app/models/order.rb \
667
- --source full \
668
- --source-context 0
744
+ -s full \
745
+ -c 0
669
746
  ```
670
747
 
671
748
  **Programmatic Source Annotation:**
@@ -748,7 +825,7 @@ require 'net/http'
748
825
  require 'json'
749
826
 
750
827
  model = CovLoupe::CoverageModel.new
751
- files = model.all_files
828
+ files = model.list['files']
752
829
 
753
830
  coveralls_data = {
754
831
  repo_token: ENV['COVERALLS_REPO_TOKEN'],
@@ -768,6 +845,22 @@ Net::HTTP.post(uri, coveralls_data.to_json, {
768
845
 
769
846
  ---
770
847
 
848
+ ## Multi-Suite Coverage Merging
849
+
850
+ ### How It Works
851
+
852
+ 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.
853
+
854
+ **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.
855
+
856
+ ### Current Limitations
857
+
858
+ **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. Use `--raise-on-stale` (or `-S`) on the CLI, `raise_on_stale: true` via the Ruby API, or the MCP tool parameter to turn these warnings into hard failures. A per-file timestamp refinement is planned; until then, treat multi-suite staleness flags as advisory rather than definitive.
859
+
860
+ **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.
861
+
862
+ ---
863
+
771
864
  ## Additional Resources
772
865
 
773
866
  - [CLI Usage Guide](CLI_USAGE.md)
@@ -1,5 +1,7 @@
1
1
  # CLI Fallback for LLMs
2
2
 
3
+ [Back to main README](../index.md)
4
+
3
5
  When the MCP server integration isn't working or available, LLMs can use the `cov-loupe` CLI directly with the `-fJ` flag to get the same coverage data that the MCP tools provide.
4
6
 
5
7
  ## Overview