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,6 +1,6 @@
1
1
  # Error Handling Guide
2
2
 
3
- [Back to main README](../README.md)
3
+ [Back to main README](../index.md)
4
4
 
5
5
  Error handling differs by usage mode:
6
6
 
@@ -72,9 +72,9 @@ end
72
72
 
73
73
  ## Stale Coverage Errors
74
74
 
75
- When strict staleness checking is enabled (`--staleness error`), the model (and CLI) raise a `CoverageDataStaleError` if a source file appears newer than the coverage data or the line counts differ.
75
+ When strict staleness checking is enabled (`--raise-on-stale`), the model (and CLI) raise a `CoverageDataStaleError` if a source file appears newer than the coverage data or the line counts differ.
76
76
 
77
- - Enable per instance: `CovLoupe::CoverageModel.new(staleness: 'error')`
77
+ - Enable per instance: `CovLoupe::CoverageModel.new(raise_on_stale: true)`
78
78
 
79
79
  The error message is detailed and includes:
80
80
 
@@ -89,5 +89,5 @@ Coverage data stale: Coverage data appears stale for lib/foo.rb
89
89
  File - time: 2025-09-16T14:03:22Z (local 2025-09-16T07:03:22-07:00), lines: 226
90
90
  Coverage - time: 2025-09-15T21:11:09Z (local 2025-09-15T14:11:09-07:00), lines: 220
91
91
  Delta - file is +123s newer than coverage
92
- Resultset - /path/to/your/project/coverage/.resultset.json
92
+ Resultset - /path/to/project/coverage/.resultset.json
93
93
  ```
@@ -1,12 +1,15 @@
1
1
  # Examples and Recipes
2
2
 
3
- [Back to main README](../README.md)
3
+ [Back to main README](../index.md)
4
4
 
5
5
  Practical examples for common tasks with cov-loupe. Examples are organized by skill level and use case.
6
6
 
7
7
  > For brevity, these examples use `clp`, an alias to the demo fixture with partial coverage:
8
- > `alias clp='cov-loupe --root docs/fixtures/demo_project'`
8
+ >
9
+ > `alias clp='cov-loupe -R docs/fixtures/demo_project' # -R = --root`
10
+ >
9
11
  > Swap `clp` for `cov-loupe` to run against your own project and resultset.
12
+ > The demo fixture is a small Rails-like project in `docs/fixtures/demo_project` with intentional coverage gaps for testing `--tracked-globs`.
10
13
 
11
14
  ## Table of Contents
12
15
 
@@ -22,11 +25,11 @@ Practical examples for common tasks with cov-loupe. Examples are organized by sk
22
25
  ### View All Coverage
23
26
 
24
27
  ```bash
25
- # Default: show all files, worst coverage first
28
+ # Default: show all files, best coverage first
26
29
  clp
27
30
 
28
- # Show files with best coverage first
29
- clp -o d list # -o = --sort-order, d = descending
31
+ # Show files with worst coverage first
32
+ clp -o a list # -o = --sort-order, a = ascending
30
33
 
31
34
  # Export to JSON for processing
32
35
  clp -fJ list > coverage-report.json
@@ -48,21 +51,21 @@ clp -s u -c 3 uncovered app/controllers/orders_controller.rb # -s = --source (u
48
51
  ### Find Coverage Gaps
49
52
 
50
53
  ```bash
51
- # Files with worst coverage
52
- clp list | head -10
54
+ # Files with worst coverage (account for header/footer)
55
+ clp list | tail -12
53
56
 
54
57
  # Only show files below 80%
55
- clp -fJ list | jq '.files[] | select(.percentage < 95)'
58
+ clp -fJ list | jq '.files[] | select(.percentage < 80)'
56
59
 
57
60
  # Ruby alternative:
58
61
  clp -fJ list | ruby -r json -e '
59
- JSON.parse($stdin.read)["files"].select { |f| f["percentage"] < 95 }.each do |f|
62
+ JSON.parse($stdin.read)["files"].select { |f| f["percentage"] < 80 }.each do |f|
60
63
  puts JSON.pretty_generate(f)
61
64
  end
62
65
  '
63
66
 
64
67
  # Rexe alternative:
65
- clp -fJ list | rexe -ij -mb -oJ 'self["files"].select { |f| f["percentage"] < 95 }'
68
+ clp -fJ list | rexe -ij -mb -oJ 'self["files"].select { |f| f["percentage"] < 80 }'
66
69
 
67
70
  # Check specific directory
68
71
  clp -g "lib/payments/**/*.rb" list # -g = --tracked-globs
@@ -92,18 +95,18 @@ Here are some examples:
92
95
  **Parse and filter:**
93
96
  ```bash
94
97
  # Files below threshold
95
- clp -fJ list | jq '.files[] | select(.percentage < 95) | {file, coverage: .percentage}'
98
+ clp -fJ list | jq '.files[] | select(.percentage < 80) | {file, coverage: .percentage}'
96
99
 
97
100
  # Ruby alternative:
98
101
  clp -fJ list | ruby -r json -e '
99
- JSON.parse($stdin.read)["files"].select { |f| f["percentage"] < 95 }.each do |f|
102
+ JSON.parse($stdin.read)["files"].select { |f| f["percentage"] < 80 }.each do |f|
100
103
  puts JSON.pretty_generate({file: f["file"], coverage: f["percentage"]})
101
104
  end
102
105
  '
103
106
 
104
107
  # Rexe alternative:
105
108
  clp -fJ list | rexe -ij -mb -oJ '
106
- self["files"].select { |f| f["percentage"] < 95 }.map do |f|
109
+ self["files"].select { |f| f["percentage"] < 80 }.map do |f|
107
110
  {file: f["file"], coverage: f["percentage"]}
108
111
  end
109
112
  '
@@ -199,7 +202,7 @@ model = CovLoupe::CoverageModel.new(root: root)
199
202
  # Project totals
200
203
  totals = model.project_totals
201
204
  puts "Total files: #{totals['files']['total']}"
202
- puts "Average coverage: #{totals['percentage']}%"
205
+ puts "Average coverage: #{totals['lines']['percent_covered']}%"
203
206
 
204
207
  # Check specific file
205
208
  summary = model.summary_for("app/models/order.rb")
@@ -217,11 +220,11 @@ require "cov_loupe"
217
220
 
218
221
  root = "docs/fixtures/demo_project"
219
222
  model = CovLoupe::CoverageModel.new(root: root)
220
- all_files = model.all_files
223
+ list = model.list['files']
221
224
 
222
225
  # Find files below threshold
223
226
  THRESHOLD = 80.0
224
- low_coverage = all_files.select { |f| f['percentage'] < THRESHOLD }
227
+ low_coverage = list.select { |f| f['percentage'] < THRESHOLD }
225
228
 
226
229
  if low_coverage.any?
227
230
  puts "Files below #{THRESHOLD}%:"
@@ -235,7 +238,7 @@ dirs = %w[app lib lib/payments lib/ops/jobs].uniq
235
238
  dirs.each do |dir|
236
239
  pattern = File.join(dir, '**/*.rb')
237
240
  totals = model.project_totals(tracked_globs: pattern)
238
- puts "#{dir}: #{totals['percentage'].round(2)}% (#{totals['files']['total']} files)"
241
+ puts "#{dir}: #{totals['lines']['percent_covered'].round(2)}% (#{totals['files']['total']} files)"
239
242
  end
240
243
  ```
241
244
 
@@ -247,11 +250,11 @@ require "pathname"
247
250
 
248
251
  root = "docs/fixtures/demo_project"
249
252
  model = CovLoupe::CoverageModel.new(root: root)
250
- all_files = model.all_files
253
+ list = model.list['files']
251
254
 
252
255
  # Filter to lib/payments (coverage data stores absolute paths)
253
256
  lib_root = File.expand_path("lib/payments", File.expand_path(root, Dir.pwd))
254
- lib_files = all_files.select { |f| f['file'].start_with?(lib_root) }
257
+ lib_files = list.select { |f| f['file'].start_with?(lib_root) }
255
258
 
256
259
  # Generate custom table
257
260
  table = model.format_table(lib_files, sort_order: :ascending)
@@ -269,65 +272,48 @@ end
269
272
 
270
273
  ### Coverage Analysis
271
274
 
272
- ```
273
- Using cov-loupe, show me a table of all files sorted by coverage percentage.
274
- ```
275
+ "Using cov-loupe, show me a table of all files sorted by coverage percentage."
275
276
 
276
- ```
277
- Using cov-loupe, find the 10 files with the lowest coverage and create a markdown report with:
277
+ "Using cov-loupe, find the 10 files with the lowest coverage and create a markdown report with:
278
278
  1. File path
279
279
  2. Current coverage %
280
- 3. Number of uncovered lines
281
- ```
280
+ 3. Number of uncovered lines"
282
281
 
283
- ```
284
- Using cov-loupe, analyze the lib/payments/ directory and identify which files should be prioritized for additional testing based on coverage gaps and file complexity.
285
- ```
282
+ "Using cov-loupe, analyze the lib/payments/ directory and identify which files should be prioritized for additional testing based on coverage gaps and file complexity."
286
283
 
287
284
  ### Finding Specific Issues
288
285
 
289
- ```
290
- Using cov-loupe, show me the uncovered lines in app/controllers/orders_controller.rb with 5 lines of context around each uncovered block.
291
- ```
286
+ "Using cov-loupe, show me the uncovered lines in app/controllers/orders_controller.rb with 5 lines of context around each uncovered block."
292
287
 
293
- ```
294
- Using cov-loupe, find all files in lib/payments/ with less than 80% coverage and list the specific uncovered line numbers for each.
295
- ```
288
+ "Using cov-loupe, find all files in lib/payments/ with less than 80% coverage and list the specific uncovered line numbers for each."
296
289
 
297
290
  ### Test Generation
298
291
 
299
- ```
300
- Using cov-loupe, identify the uncovered lines in lib/ops/jobs/report_job.rb and write RSpec tests to cover them.
301
- ```
292
+ "Using cov-loupe, identify the uncovered lines in lib/ops/jobs/report_job.rb and write *meaningful* RSpec tests to cover them."
302
293
 
303
- ```
304
- Using cov-loupe, analyze coverage gaps in the app/controllers/ directory and generate a test plan prioritizing:
294
+ "Using cov-loupe, analyze coverage gaps in the app/controllers/ directory and generate a test plan prioritizing:
305
295
  1. Public API methods
306
296
  2. Error handling paths
307
- 3. Edge cases
308
- ```
297
+ 3. Edge cases"
309
298
 
310
299
  ### Reporting
311
300
 
312
- ```
313
- Using cov-loupe, create a coverage report showing:
301
+ "Using cov-loupe, create a coverage report showing:
314
302
  - Overall project coverage percentage
315
303
  - Top 5 files with worst coverage
316
304
  - Recommended next steps to improve coverage
317
305
 
318
- Format as markdown.
319
- ```
306
+ Format as markdown."
320
307
 
321
308
  ## Test Run Integration
322
309
 
323
- ### Display Low Coverage Files After RSpec
310
+ ### Display Low Coverage Files
324
311
 
325
312
  Add this to your `spec/spec_helper.rb` to automatically report files below a coverage threshold after each test run:
326
313
 
327
314
  ```ruby
328
315
  require 'simplecov'
329
316
  SimpleCov.start do
330
- enable_coverage :branch
331
317
  add_filter %r{^/spec/}
332
318
  track_files 'lib/**/*.rb' # Ensures new/untested files show up with 0%
333
319
  end
@@ -355,29 +341,72 @@ Lowest coverage files (< 80%):
355
341
  **Parameters:**
356
342
  - `threshold:` - Coverage percentage below which files are included (default: 80)
357
343
  - `count:` - Maximum number of files to show (default: 5)
344
+ - `root:` - Project root directory (defaults to `SimpleCov.root` when SimpleCov is loaded, otherwise `'.'`)
345
+ - `resultset:` - Path or directory to `.resultset.json` (defaults to `SimpleCov.coverage_dir/.resultset.json` when SimpleCov is loaded)
346
+ - `model:` - Pre-configured `CoverageModel` instance (optional, overrides `root:`/`resultset:`)
358
347
 
359
348
  **Returns:** Formatted string, or `nil` if no files are below the threshold.
360
349
 
350
+ **SimpleCov Integration:** When SimpleCov is loaded, `CoverageReporter.report` automatically uses SimpleCov's configured root and coverage directory. You can override these by passing explicit `root:` or `resultset:` parameters, or provide a custom `model:` instance.
351
+
352
+ ### Custom Coverage Directory
353
+
354
+ If your project uses a custom coverage directory:
355
+
356
+ ```ruby
357
+ require 'simplecov'
358
+ SimpleCov.start do
359
+ add_filter %r{^/spec/}
360
+ coverage_dir 'reports/coverage' # Custom coverage directory
361
+ track_files 'lib/**/*.rb'
362
+ end
363
+
364
+ SimpleCov.at_exit do
365
+ SimpleCov.result.format!
366
+ require 'cov_loupe'
367
+
368
+ # CoverageReporter will automatically find the coverage in reports/coverage
369
+ report = CovLoupe::CoverageReporter.report(threshold: 80, count: 5)
370
+ puts report if report
371
+ end
372
+ ```
373
+
374
+ Or specify the resultset path explicitly:
375
+
376
+ ```ruby
377
+ report = CovLoupe::CoverageReporter.report(
378
+ threshold: 80,
379
+ count: 5,
380
+ resultset: 'reports/coverage/.resultset.json'
381
+ )
382
+ ```
383
+
361
384
  ## CI/CD Integration
362
385
 
363
386
  ### GitHub Actions
364
387
 
365
- **Fail on low coverage:**
388
+ **Fail on low coverage (Cross-Platform):**
366
389
  ```yaml
367
390
  name: Coverage Check
368
391
 
369
392
  on: [push, pull_request]
370
393
 
371
394
  jobs:
372
- coverage:
373
- runs-on: ubuntu-latest
395
+ test:
396
+ runs-on: ${{ matrix.os }}
397
+ strategy:
398
+ fail-fast: false
399
+ matrix:
400
+ os: [ubuntu-latest, windows-latest, macos-latest]
401
+ ruby-version: ['3.4']
402
+
374
403
  steps:
375
- - uses: actions/checkout@v3
404
+ - uses: actions/checkout@v4
376
405
 
377
406
  - name: Setup Ruby
378
407
  uses: ruby/setup-ruby@v1
379
408
  with:
380
- ruby-version: 3.3
409
+ ruby-version: ${{ matrix.ruby-version }}
381
410
  bundler-cache: true
382
411
 
383
412
  - name: Run tests
@@ -387,63 +416,55 @@ jobs:
387
416
  run: gem install cov-loupe
388
417
 
389
418
  - name: Check coverage threshold
419
+ shell: bash
390
420
  run: |
391
- clp -fJ list > coverage.json
392
- BELOW_THRESHOLD=$(jq '[.files[] | select(.percentage < 80)] | length' coverage.json)
393
-
394
- # Ruby alternative:
395
- # BELOW_THRESHOLD=$(ruby -r json -e '
396
- # puts JSON.parse(File.read("coverage.json"))["files"].count { |f| f["percentage"] < 80 }
397
- # ')
398
-
399
- # Rexe alternative:
400
- # BELOW_THRESHOLD=$(rexe -f coverage.json -op 'self["files"].count { |f| f["percentage"] < 80 }')
401
-
402
- if [ "$BELOW_THRESHOLD" -gt 0 ]; then
403
- echo "❌ $BELOW_THRESHOLD files below 80% coverage"
404
- jq -r '.files[] | select(.percentage < 80) | "\(.file): \(.percentage)%"' coverage.json
405
-
406
- # Ruby alternative:
407
- # ruby -r json -e '
408
- # JSON.parse(File.read("coverage.json"))["files"].select { |f| f["percentage"] < 80 }.each do |f|
409
- # puts "#{f["file"]}: #{f["percentage"]}%"
410
- # end
411
- # '
412
-
413
- # Rexe alternative:
414
- # rexe -f coverage.json '
415
- # self["files"].select { |f| f["percentage"] < 80 }.each do |f|
416
- # puts "#{f["file"]}: #{f["percentage"]}%"
417
- # end
418
- # '
419
-
420
- exit 1
421
- fi
422
- echo "✓ All files meet coverage threshold"
421
+ # Generate JSON report using the full command (aliases like 'clp' are not available here)
422
+ cov-loupe -fJ list > coverage.json
423
+
424
+ # Verify coverage using Ruby for cross-platform compatibility
425
+ # (Tools like jq and rexe are not guaranteed to be installed on all runners)
426
+ ruby -r json -e '
427
+ data = JSON.parse(File.read("coverage.json"))
428
+ files = data["files"]
429
+ low_cov_files = files.select { |f| f["percentage"] < 80 }
430
+
431
+ if low_cov_files.any?
432
+ puts " #{low_cov_files.count} files below 80% coverage:"
433
+ low_cov_files.each do |f|
434
+ puts " #{f["percentage"]}% #{f["file"]}"
435
+ end
436
+ exit 1
437
+ end
438
+ puts " All files meet coverage threshold"
439
+ '
423
440
 
424
441
  - name: Upload coverage report
425
- uses: actions/upload-artifact@v3
442
+ # Saves the coverage file as an artifact so you can download/inspect it
443
+ # from the GitHub Actions run summary page.
444
+ uses: actions/upload-artifact@v4
445
+ if: always()
426
446
  with:
427
- name: coverage-report
447
+ name: coverage-report-${{ matrix.os }}
428
448
  path: coverage.json
429
449
  ```
430
450
 
431
451
  **Check for stale coverage:**
432
452
  ```yaml
433
453
  - name: Verify coverage is fresh
434
- run: cov-loupe --staleness error || exit 1
454
+ shell: bash
455
+ run: cov-loupe --raise-on-stale true list || exit 1
435
456
  ```
436
457
 
437
458
  ### GitLab CI
438
459
 
439
460
  ```yaml
440
461
  test:
441
- image: ruby:3.3
462
+ image: ruby:3.4
442
463
  before_script:
443
464
  - gem install cov-loupe
444
465
  script:
445
466
  - bundle exec rspec
446
- - cov-loupe --staleness error
467
+ - cov-loupe --raise-on-stale true list
447
468
  artifacts:
448
469
  paths:
449
470
  - coverage/
@@ -458,18 +479,18 @@ test:
458
479
  ```ruby
459
480
  # coverage_policy.rb
460
481
  ->(model) do
461
- all_files = model.all_files
482
+ list = model.list['files']
462
483
 
463
484
  # Must have at least 80% average coverage
464
485
  totals = model.project_totals
465
- return false if totals['percentage'] < 80.0
486
+ return false if totals['lines']['percent_covered'] < 80.0
466
487
 
467
488
  # No files below 60%
468
- return false if all_files.any? { |f| f['percentage'] < 60.0 }
489
+ return false if list.any? { |f| f['percentage'] < 60.0 }
469
490
 
470
491
  # lib/ files must average 90%
471
492
  lib_totals = model.project_totals(tracked_globs: ['lib/**/*.rb'])
472
- return false if lib_totals['percentage'] < 90.0
493
+ return false if lib_totals['lines']['percent_covered'] < 90.0
473
494
 
474
495
  true
475
496
  end
@@ -477,7 +498,7 @@ end
477
498
 
478
499
  ```bash
479
500
  # Use in CI
480
- clp validate coverage_policy.rb
501
+ cov-loupe validate coverage_policy.rb
481
502
  ```
482
503
 
483
504
  ## Advanced Usage
@@ -489,7 +510,6 @@ require "cov_loupe"
489
510
 
490
511
  root = "docs/fixtures/demo_project"
491
512
  model = CovLoupe::CoverageModel.new(root: root)
492
- all_files = model.all_files
493
513
 
494
514
  # Calculate coverage by directory (uses the same data as `cov-loupe totals`)
495
515
  patterns = %w[
@@ -504,7 +524,7 @@ results = patterns.map do |pattern|
504
524
  {
505
525
  directory: pattern,
506
526
  files: totals['files']['total'],
507
- coverage: totals['percentage'].round(2),
527
+ coverage: totals['lines']['percent_covered'].round(2),
508
528
  covered: totals['lines']['covered'],
509
529
  total: totals['lines']['total']
510
530
  }
@@ -516,34 +536,6 @@ results.sort_by { |r| r[:coverage] }.each do |r|
516
536
  end
517
537
  ```
518
538
 
519
- ### Coverage Delta Tracking
520
-
521
- ```ruby
522
- require "cov_loupe"
523
- require "json"
524
-
525
- # Save current coverage
526
- root = "docs/fixtures/demo_project"
527
- model = CovLoupe::CoverageModel.new(root: root)
528
- current = model.all_files
529
-
530
- File.write("coverage_baseline.json", JSON.pretty_generate(current))
531
-
532
- # Later, compare with baseline
533
- baseline = JSON.parse(File.read("coverage_baseline.json"))
534
-
535
- current.each do |file|
536
- baseline_file = baseline.find { |f| f['file'] == file['file'] }
537
- next unless baseline_file
538
-
539
- delta = file['percentage'] - baseline_file['percentage']
540
- if delta.abs > 0.1 # Changed by more than 0.1%
541
- symbol = delta > 0 ? '↑' : '↓'
542
- puts "#{symbol} #{file['file']}: #{baseline_file['percentage']}% → #{file['percentage']}%"
543
- end
544
- end
545
- ```
546
-
547
539
  ### Integration with Code Review
548
540
 
549
541
  ```ruby
@@ -575,10 +567,11 @@ end
575
567
 
576
568
  ## Example Scripts
577
569
 
578
- The [`/examples`](/examples) directory contains runnable scripts:
570
+ The `examples/` directory contains runnable scripts:
579
571
 
580
- - **[filter_and_table_demo.rb](../examples/filter_and_table_demo.rb)** - Filter and format coverage data
581
- - **[success_predicates/](/examples/success_predicates/)** - Custom coverage policy examples
572
+ - **filter_and_table_demo.rb** - Filter and format coverage data (in `examples/` directory)
573
+ - **[success_predicates](../examples/success_predicates.md)** - Custom coverage policy examples
574
+ - **[Coverage Delta Tracking recipe in the Library API Guide](LIBRARY_API.md#coverage-delta-tracking)**
582
575
 
583
576
  ## Related Documentation
584
577
 
@@ -1,6 +1,6 @@
1
1
  # Installation Guide
2
2
 
3
- [Back to main README](../README.md)
3
+ [Back to main README](../index.md)
4
4
 
5
5
  ## Prerequisites
6
6
 
@@ -49,33 +49,6 @@ require "cov_loupe"
49
49
 
50
50
  The executable is `cov-loupe` (with hyphen).
51
51
 
52
- ## PATH Configuration
53
-
54
- ### With Version Managers
55
-
56
- Most version managers (rbenv, asdf, RVM, chruby) automatically configure PATH. After installation:
57
-
58
- ```sh
59
- # Refresh shims if needed
60
- rbenv rehash # rbenv
61
- asdf reshim ruby # asdf
62
-
63
- # Verify executable is accessible
64
- which cov-loupe
65
- ```
66
-
67
- **Important:** When changing Ruby versions, reinstall the gem and update any MCP configurations that use absolute paths.
68
-
69
- ### Bundler Execution
70
-
71
- If PATH setup is problematic, use bundler:
72
-
73
- ```sh
74
- bundle exec cov-loupe
75
- ```
76
-
77
- This works from any project directory that has cov-loupe in its Gemfile.
78
-
79
52
  ## Verification
80
53
 
81
54
  ### Test Installation
@@ -122,6 +95,14 @@ Works with system Ruby or any version manager.
122
95
 
123
96
  Should work with Ruby installed via RubyInstaller. PATH configuration may differ.
124
97
 
98
+ ## Upgrading
99
+
100
+ If you are upgrading from an earlier version of `cov-loupe`, please check the migration guides:
101
+
102
+ - [Migrating to v4](migrations/MIGRATING_TO_V4.md)
103
+ - [Migrating to v3](migrations/MIGRATING_TO_V3.md)
104
+ - [Migrating to v2](migrations/MIGRATING_TO_V2.md)
105
+
125
106
  ## Next Steps
126
107
 
127
108
  - **[CLI Usage](CLI_USAGE.md)** - Learn command-line options