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,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../base_tool'
4
- require_relative '../model'
5
- require_relative '../presenters/coverage_detailed_presenter'
4
+ require_relative '../model/model'
5
+ require_relative '../presenters/coverage_payload_presenter'
6
6
 
7
7
  module CovLoupe
8
8
  module Tools
@@ -10,24 +10,23 @@ module CovLoupe
10
10
  description <<~DESC
11
11
  Use this when the user needs per-line coverage data for a single file.
12
12
  Do not use this for high-level counts; coverage.summary is cheaper for aggregate numbers.
13
- Inputs: file path (required) plus optional root/resultset/staleness mode inherited from BaseTool.
13
+ Inputs: file path (required) plus optional root/resultset/raise_on_stale flag inherited from BaseTool.
14
14
  Output: JSON object with "file", "lines" => [{"line": 12, "hits": 0, "covered": false}], plus "summary" with totals and "stale" status.
15
15
  Example: "Show detailed coverage for lib/cov_loupe/model.rb".
16
16
  DESC
17
17
  input_schema(**input_schema_def)
18
18
  class << self
19
- def call(path:, root: '.', resultset: nil, staleness: :off, error_mode: 'log',
20
- server_context:)
21
- with_error_handling('CoverageDetailedTool', error_mode: error_mode) do
22
- model = CoverageModel.new(
23
- root: root,
24
- resultset: resultset,
25
- staleness: staleness.to_sym
26
- )
27
- presenter = Presenters::CoverageDetailedPresenter.new(model: model, path: path)
28
- respond_json(presenter.relativized_payload, name: 'coverage_detailed.json',
29
- pretty: true)
30
- end
19
+ def call(path:, root: nil, resultset: nil, raise_on_stale: nil, error_mode: 'log',
20
+ output_chars: nil, server_context:)
21
+ call_with_file_payload(
22
+ path: path,
23
+ error_mode: error_mode,
24
+ output_chars: output_chars,
25
+ server_context: server_context,
26
+ root: root,
27
+ resultset: resultset,
28
+ raise_on_stale: raise_on_stale
29
+ )
31
30
  end
32
31
  end
33
32
  end
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../base_tool'
4
- require_relative '../model'
5
- require_relative '../presenters/coverage_raw_presenter'
4
+ require_relative '../model/model'
5
+ require_relative '../presenters/coverage_payload_presenter'
6
6
 
7
7
  module CovLoupe
8
8
  module Tools
@@ -10,23 +10,23 @@ module CovLoupe
10
10
  description <<~DESC
11
11
  Use this when you need the raw SimpleCov `lines` array for a file exactly as stored on disk.
12
12
  Do not use this for human-friendly explanations; choose coverage.detailed or coverage.summary instead.
13
- Inputs: file path (required) plus optional root/resultset/staleness mode inherited from BaseTool.
13
+ Inputs: file path (required) plus optional root/resultset/raise_on_stale flag inherited from BaseTool.
14
14
  Output: JSON object with "file" and "lines" (array of integers/nulls) mirroring SimpleCov's native structure, plus "stale" status.
15
15
  Example: "Fetch the raw coverage array for spec/support/foo_helper.rb".
16
16
  DESC
17
17
  input_schema(**input_schema_def)
18
18
  class << self
19
- def call(path:, root: '.', resultset: nil, staleness: :off, error_mode: 'log',
20
- server_context:)
21
- with_error_handling('CoverageRawTool', error_mode: error_mode) do
22
- model = CoverageModel.new(
23
- root: root,
24
- resultset: resultset,
25
- staleness: staleness.to_sym
26
- )
27
- presenter = Presenters::CoverageRawPresenter.new(model: model, path: path)
28
- respond_json(presenter.relativized_payload, name: 'coverage_raw.json', pretty: true)
29
- end
19
+ def call(path:, root: nil, resultset: nil, raise_on_stale: nil, error_mode: 'log',
20
+ output_chars: nil, server_context:)
21
+ call_with_file_payload(
22
+ path: path,
23
+ error_mode: error_mode,
24
+ output_chars: output_chars,
25
+ server_context: server_context,
26
+ root: root,
27
+ resultset: resultset,
28
+ raise_on_stale: raise_on_stale
29
+ )
30
30
  end
31
31
  end
32
32
  end
@@ -1,32 +1,32 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../base_tool'
4
- require_relative '../model'
5
- require_relative '../presenters/coverage_summary_presenter'
4
+ require_relative '../model/model'
5
+ require_relative '../presenters/coverage_payload_presenter'
6
6
 
7
7
  module CovLoupe
8
8
  module Tools
9
9
  class CoverageSummaryTool < BaseTool
10
10
  description <<~DESC
11
11
  Use this when the user asks for the covered/total line counts and percentage for a specific file.
12
- Do not use this for multi-file reports; coverage.all_files or coverage.table handle those.
13
- Inputs: file path (required) plus optional root/resultset/staleness mode inherited from BaseTool.
12
+ Do not use this for multi-file reports; coverage.list or coverage.table handle those.
13
+ Inputs: file path (required) plus optional root/resultset/raise_on_stale flag inherited from BaseTool.
14
14
  Output: JSON object {"file": String, "summary": {"covered": Integer, "total": Integer, "percentage": Float}, "stale": String|False}.
15
- Examples: "What is the coverage for lib/cov_loupe/tools/all_files_coverage_tool.rb?".
15
+ Examples: "What is the coverage for lib/cov_loupe/tools/list_tool.rb?".
16
16
  DESC
17
17
  input_schema(**input_schema_def)
18
18
  class << self
19
- def call(path:, root: '.', resultset: nil, staleness: :off, error_mode: 'log',
20
- server_context:)
21
- with_error_handling('CoverageSummaryTool', error_mode: error_mode) do
22
- model = CoverageModel.new(
23
- root: root,
24
- resultset: resultset,
25
- staleness: staleness.to_sym
26
- )
27
- presenter = Presenters::CoverageSummaryPresenter.new(model: model, path: path)
28
- respond_json(presenter.relativized_payload, name: 'coverage_summary.json', pretty: true)
29
- end
19
+ def call(path:, root: nil, resultset: nil, raise_on_stale: nil, error_mode: 'log',
20
+ output_chars: nil, server_context:)
21
+ call_with_file_payload(
22
+ path: path,
23
+ error_mode: error_mode,
24
+ output_chars: output_chars,
25
+ server_context: server_context,
26
+ root: root,
27
+ resultset: resultset,
28
+ raise_on_stale: raise_on_stale
29
+ )
30
30
  end
31
31
  end
32
32
  end
@@ -3,47 +3,165 @@
3
3
 
4
4
  require_relative '../base_tool'
5
5
  require_relative '../presenters/project_coverage_presenter'
6
+ require_relative '../config/option_normalizers'
7
+ require_relative '../output_chars'
6
8
 
7
9
  module CovLoupe
8
10
  module Tools
9
11
  class CoverageTableTool < BaseTool
10
12
  description <<~DESC
11
- Use this when a user wants the plain text coverage table exactly like `cov-loupe --table` would print (no ANSI colors).
12
- Do not use this for machine-readable data; coverage.all_files returns structured JSON.
13
- Inputs: optional project root/resultset path/sort order/staleness mode matching the CLI flags.
14
- Output: text block containing the formatted coverage table with headers and percentages.
13
+ Use this when a user wants the plain text coverage table exactly like `cov-loupe --format table` would print (no ANSI colors).
14
+ Do not use this for machine-readable data; coverage.list returns structured JSON.
15
+ Inputs: optional project root/resultset path/sort order/raise_on_stale flag matching the CLI flags.
16
+ Output: text block containing the formatted coverage table with headers and percentages, plus
17
+ any exclusions summary (missing/stale/deleted files) and skipped row warnings, exactly as the CLI displays.
15
18
  Example: "Show me the CLI coverage table sorted descending".
16
19
  DESC
17
20
  input_schema(**coverage_schema(
18
21
  additional_properties: {
19
- sort_order: {
20
- type: 'string',
21
- description: 'Sort order for the printed coverage table (ascending or descending).',
22
- default: 'ascending',
23
- enum: ['ascending', 'descending']
24
- },
22
+ sort_order: SORT_ORDER_PROPERTY,
25
23
  tracked_globs: TRACKED_GLOBS_PROPERTY
26
24
  }
27
25
  ))
28
26
  class << self
29
- def call(root: '.', resultset: nil, sort_order: 'ascending', staleness: :off,
30
- tracked_globs: nil, error_mode: 'log', server_context:)
31
- with_error_handling('CoverageTableTool', error_mode: error_mode) do
32
- # Convert string inputs from MCP to symbols for internal use
33
- sort_order_sym = sort_order.to_sym
34
- staleness_sym = staleness.to_sym
35
-
36
- model = CoverageModel.new(root: root, resultset: resultset, staleness: staleness_sym,
37
- tracked_globs: tracked_globs)
27
+ def call(root: nil, resultset: nil, sort_order: nil, raise_on_stale: nil,
28
+ tracked_globs: nil, error_mode: 'log', output_chars: nil, server_context:)
29
+ output_chars_sym = resolve_output_chars(output_chars, server_context)
30
+ with_error_handling('CoverageTableTool', error_mode: error_mode, output_chars: output_chars_sym) do
31
+ model, config = create_configured_model(
32
+ server_context: server_context,
33
+ root: root,
34
+ resultset: resultset,
35
+ raise_on_stale: raise_on_stale,
36
+ tracked_globs: tracked_globs
37
+ )
38
+
39
+ # Normalize and validate sort_order (supports 'a'/'d' abbreviations)
40
+ sort_order_sym = OptionNormalizers.normalize_sort_order(
41
+ sort_order || BaseTool::DEFAULT_SORT_ORDER, strict: true
42
+ )
43
+
44
+ # Create presenter to access file summaries and exclusion data
45
+ presenter = Presenters::ProjectCoveragePresenter.new(
46
+ model: model,
47
+ sort_order: sort_order_sym,
48
+ raise_on_stale: config[:raise_on_stale],
49
+ tracked_globs: config[:tracked_globs]
50
+ )
51
+
52
+ # Format the table with file summaries
53
+ file_summaries = presenter.relative_files
38
54
  table = model.format_table(
55
+ file_summaries,
39
56
  sort_order: sort_order_sym,
40
- check_stale: (staleness_sym == :error),
41
- tracked_globs: tracked_globs
57
+ raise_on_stale: config[:raise_on_stale],
58
+ tracked_globs: nil,
59
+ output_chars: output_chars_sym
42
60
  )
61
+
62
+ # Append exclusions summary (matching CLI behavior)
63
+ exclusions = format_exclusions_summary(presenter, output_chars_sym)
64
+ table += exclusions unless exclusions.empty?
65
+
66
+ # Append timestamp warning (matching CLI behavior)
67
+ timestamp_warning = format_timestamp_warning(presenter)
68
+ table += timestamp_warning unless timestamp_warning.empty?
69
+
70
+ # Append skipped rows warning (matching CLI behavior)
71
+ skipped_warning = format_skipped_rows_warning(presenter, output_chars_sym)
72
+ table += skipped_warning unless skipped_warning.empty?
73
+
43
74
  # Return text response
44
75
  ::MCP::Tool::Response.new([{ 'type' => 'text', 'text' => table }])
45
76
  end
46
77
  end
78
+
79
+ private def format_exclusions_summary(presenter, output_chars)
80
+ missing = presenter.relative_missing_tracked_files
81
+ newer = presenter.relative_newer_files
82
+ deleted = presenter.relative_deleted_files
83
+ length_mismatch = presenter.relative_length_mismatch_files
84
+ unreadable = presenter.relative_unreadable_files
85
+ skipped = presenter.relative_skipped_files
86
+
87
+ # Only format if there are any exclusions
88
+ return '' if missing.empty? && newer.empty? && deleted.empty? &&
89
+ length_mismatch.empty? && unreadable.empty? && skipped.empty?
90
+
91
+ # Helper to convert paths to ASCII if needed
92
+ convert_path = ->(path) { OutputChars.convert(path, output_chars) }
93
+
94
+ output = ["\nFiles excluded from coverage:"]
95
+
96
+ unless missing.empty?
97
+ output << "\nMissing tracked files (#{missing.length}):"
98
+ missing.each { |file| output << " - #{convert_path.call(file)}" }
99
+ end
100
+
101
+ unless newer.empty?
102
+ output << "\nFiles newer than coverage (#{newer.length}):"
103
+ newer.each { |file| output << " - #{convert_path.call(file)}" }
104
+ end
105
+
106
+ unless deleted.empty?
107
+ output << "\nDeleted files with coverage (#{deleted.length}):"
108
+ deleted.each { |file| output << " - #{convert_path.call(file)}" }
109
+ end
110
+
111
+ unless length_mismatch.empty?
112
+ output << "\nLine count mismatches (#{length_mismatch.length}):"
113
+ length_mismatch.each { |file| output << " - #{convert_path.call(file)}" }
114
+ end
115
+
116
+ unless unreadable.empty?
117
+ output << "\nUnreadable files (#{unreadable.length}):"
118
+ unreadable.each { |file| output << " - #{convert_path.call(file)}" }
119
+ end
120
+
121
+ unless skipped.empty?
122
+ output << "\nFiles skipped due to errors (#{skipped.length}):"
123
+ skipped.each do |row|
124
+ file_path = OutputChars.convert(row['file'], output_chars)
125
+ error_msg = OutputChars.convert(row['error'], output_chars)
126
+ output << " - #{file_path}: #{error_msg}"
127
+ end
128
+ end
129
+
130
+ output << "\nRun with --raise-on-stale to exit when files are excluded."
131
+ output.join("\n")
132
+ end
133
+
134
+ # Formats the timestamp warning matching CLI warn_missing_timestamps behavior
135
+ private def format_timestamp_warning(presenter)
136
+ return '' unless presenter.timestamp_status == 'missing'
137
+
138
+ <<~WARNING
139
+
140
+ WARNING: Coverage timestamps are missing. Time-based staleness checks were skipped.
141
+ Files may appear "ok" even if source code is newer than the coverage data.
142
+ Check your coverage tool configuration to ensure timestamps are recorded.
143
+ WARNING
144
+ end
145
+
146
+ # Formats the skipped rows warning matching CLI warn_skipped_rows behavior
147
+ private def format_skipped_rows_warning(presenter, output_chars)
148
+ skipped = presenter.relative_skipped_files
149
+ return '' if skipped.nil? || skipped.empty?
150
+
151
+ count = skipped.length
152
+ output = [
153
+ '',
154
+ "WARNING: #{count} coverage row#{count == 1 ? '' : 's'} skipped due to errors:"
155
+ ]
156
+ skipped.each do |row|
157
+ # Paths are already relativized by presenter
158
+ file_path = OutputChars.convert(row['file'], output_chars)
159
+ error_msg = OutputChars.convert(row['error'], output_chars)
160
+ output << " - #{file_path}: #{error_msg}"
161
+ end
162
+ output << 'Run again with --raise-on-stale to exit when rows are skipped.'
163
+ output.join("\n")
164
+ end
47
165
  end
48
166
  end
49
167
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../model'
3
+ require_relative '../model/model'
4
4
  require_relative '../base_tool'
5
5
  require_relative '../presenters/project_totals_presenter'
6
6
 
@@ -10,8 +10,10 @@ module CovLoupe
10
10
  description <<~DESC
11
11
  Use this when you want aggregated coverage counts for the entire project.
12
12
  It reports covered/total lines, uncovered line counts, and the overall average percentage.
13
- Inputs: optional project root, alternate .resultset path, staleness mode, tracked_globs, and error mode.
14
- Output: JSON {"lines":{"total","covered","uncovered"},"percentage":Float,"files":{"total","ok","stale"}}.
13
+ Inputs: optional project root, alternate .resultset path, raise_on_stale flag, tracked_globs, and error mode.
14
+ Output: JSON {"lines":{"total","covered","uncovered","percent_covered"},"tracking":{"enabled","globs"},"files":{"total","with_coverage","without_coverage"},"timestamp_status":"ok"|"missing","warnings":[string,...]}.
15
+ When raise_on_stale is enabled, the tool will raise an error immediately if any files have coverage data errors or staleness issues.
16
+ "timestamp_status" indicates whether coverage timestamps are available for time-based staleness checks. "warnings" array is present when timestamp_status is "missing".
15
17
  Example: "Give me total/covered/uncovered line counts and the overall coverage percent."
16
18
  DESC
17
19
 
@@ -22,20 +24,36 @@ module CovLoupe
22
24
  ))
23
25
 
24
26
  class << self
25
- def call(root: '.', resultset: nil, staleness: :off, tracked_globs: nil,
26
- error_mode: 'log', server_context:)
27
- with_error_handling('CoverageTotalsTool', error_mode: error_mode) do
28
- # Convert string inputs from MCP to symbols for internal use
29
- staleness_sym = staleness.to_sym
27
+ def call(root: nil, resultset: nil, raise_on_stale: nil, tracked_globs: nil,
28
+ error_mode: 'log', output_chars: nil, server_context:)
29
+ output_chars_sym = resolve_output_chars(output_chars, server_context)
30
+ with_error_handling('CoverageTotalsTool', error_mode: error_mode, output_chars: output_chars_sym) do
31
+ model, config = create_configured_model(
32
+ server_context: server_context,
33
+ root: root,
34
+ resultset: resultset,
35
+ raise_on_stale: raise_on_stale,
36
+ tracked_globs: tracked_globs
37
+ )
30
38
 
31
- model = CoverageModel.new(root: root, resultset: resultset, staleness: staleness_sym,
32
- tracked_globs: tracked_globs)
33
39
  presenter = Presenters::ProjectTotalsPresenter.new(
34
40
  model: model,
35
- check_stale: (staleness_sym == :error),
36
- tracked_globs: tracked_globs
41
+ raise_on_stale: config[:raise_on_stale],
42
+ tracked_globs: config[:tracked_globs]
37
43
  )
38
- respond_json(presenter.relativized_payload, name: 'coverage_totals.json', pretty: true)
44
+ payload = presenter.relativized_payload
45
+
46
+ # Add warnings array if timestamp_status is missing
47
+ if payload['timestamp_status'] == 'missing'
48
+ payload['warnings'] = [
49
+ 'Coverage timestamps are missing. Time-based staleness checks were skipped.',
50
+ 'Files may appear "ok" even if source code is newer than the coverage data.',
51
+ 'Check your coverage tool configuration to ensure timestamps are recorded.'
52
+ ]
53
+ end
54
+
55
+ respond_json(payload, name: 'coverage_totals.json', pretty: true,
56
+ output_chars: output_chars_sym)
39
57
  end
40
58
  end
41
59
  end
@@ -13,7 +13,8 @@ module CovLoupe
13
13
  type: 'object',
14
14
  additionalProperties: false,
15
15
  properties: {
16
- error_mode: ERROR_MODE_PROPERTY
16
+ error_mode: ERROR_MODE_PROPERTY,
17
+ output_chars: COMMON_PROPERTIES[:output_chars]
17
18
  }
18
19
  )
19
20
 
@@ -23,63 +24,56 @@ module CovLoupe
23
24
  label: 'Single-file coverage summary',
24
25
  use_when: 'User wants covered/total line counts or percentage for one file.',
25
26
  avoid_when: 'User needs repo-wide stats or specific uncovered lines.',
26
- inputs: ['path (required)', 'root/resultset/staleness (optional)']
27
+ inputs: ['path (required)', 'root/resultset/raise_on_stale (optional)']
27
28
  },
28
29
  {
29
30
  tool: UncoveredLinesTool,
30
31
  label: 'Uncovered line numbers',
31
32
  use_when: 'User asks which lines in a file still lack tests.',
32
33
  avoid_when: 'User only wants overall percentages or detailed per-line hit data.',
33
- inputs: ['path (required)', 'root/resultset/staleness (optional)']
34
+ inputs: ['path (required)', 'root/resultset/raise_on_stale (optional)']
34
35
  },
35
36
  {
36
37
  tool: CoverageDetailedTool,
37
38
  label: 'Per-line coverage details',
38
39
  use_when: 'User needs per-line hit counts for a file.',
39
40
  avoid_when: 'User only wants totals or uncovered line numbers.',
40
- inputs: ['path (required)', 'root/resultset/staleness (optional)']
41
+ inputs: ['path (required)', 'root/resultset/raise_on_stale (optional)']
41
42
  },
42
43
  {
43
44
  tool: CoverageRawTool,
44
45
  label: 'Raw SimpleCov lines array',
45
46
  use_when: 'User needs the raw SimpleCov `lines` array for a file.',
46
47
  avoid_when: 'User expects human-friendly summaries or explanations.',
47
- inputs: ['path (required)', 'root/resultset/staleness (optional)']
48
+ inputs: ['path (required)', 'root/resultset/raise_on_stale (optional)']
48
49
  },
49
50
  {
50
- tool: AllFilesCoverageTool,
51
+ tool: ListTool,
51
52
  label: 'Repo-wide file coverage',
52
53
  use_when: 'User wants coverage percentages for every tracked file.',
53
54
  avoid_when: 'User asks about a single file.',
54
- inputs: ['root/resultset (optional)', 'sort_order', 'staleness', 'tracked_globs']
55
+ inputs: ['root/resultset (optional)', 'sort_order', 'raise_on_stale', 'tracked_globs']
55
56
  },
56
57
  {
57
58
  tool: CoverageTotalsTool,
58
59
  label: 'Project coverage totals',
59
60
  use_when: 'User wants total/covered/uncovered line counts or the average percent.',
60
61
  avoid_when: 'User needs per-file breakdowns.',
61
- inputs: ['root/resultset (optional)', 'staleness', 'tracked_globs']
62
+ inputs: ['root/resultset (optional)', 'raise_on_stale', 'tracked_globs']
62
63
  },
63
64
  {
64
65
  tool: CoverageTableTool,
65
66
  label: 'Formatted coverage table',
66
67
  use_when: 'User wants the plain-text table produced by the CLI.',
67
68
  avoid_when: 'User needs JSON data for automation.',
68
- inputs: ['root/resultset (optional)', 'sort_order', 'staleness']
69
+ inputs: ['root/resultset (optional)', 'sort_order', 'raise_on_stale']
69
70
  },
70
71
  {
71
72
  tool: ValidateTool,
72
73
  label: 'Validate coverage policy',
73
74
  use_when: 'User needs to enforce coverage rules (e.g., minimum percentage) in CI.',
74
75
  avoid_when: 'User just wants to view coverage data.',
75
- inputs: ['path (required)', 'root/resultset (optional)']
76
- },
77
- {
78
- tool: ValidateTool,
79
- label: 'Validate coverage policy',
80
- use_when: 'User needs to enforce coverage rules (e.g., minimum percentage) in CI.',
81
- avoid_when: 'User just wants to view coverage data.',
82
- inputs: ['path (required)', 'root/resultset (optional)']
76
+ inputs: ['code OR file (one required)', 'root/resultset/raise_on_stale (optional)']
83
77
  },
84
78
  {
85
79
  tool: VersionTool,
@@ -91,12 +85,14 @@ module CovLoupe
91
85
  ].freeze
92
86
 
93
87
  class << self
94
- def call(error_mode: 'log', server_context:, **_unused)
95
- with_error_handling('HelpTool', error_mode: error_mode) do
88
+ def call(error_mode: 'log', output_chars: nil, server_context:, **_unused)
89
+ # Normalize output_chars before error handling so errors also get converted
90
+ output_chars_sym = resolve_output_chars(output_chars, server_context)
91
+ with_error_handling('HelpTool', error_mode: error_mode, output_chars: output_chars_sym) do
96
92
  entries = TOOL_GUIDE.map { |guide| format_entry(guide) }
97
93
 
98
94
  data = { tools: entries }
99
- respond_json(data, name: 'tools_help.json')
95
+ respond_json(data, name: 'tools_help.json', output_chars: output_chars_sym)
100
96
  end
101
97
  end
102
98
 
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../model/model'
4
+ require_relative '../base_tool'
5
+ require_relative '../presenters/project_coverage_presenter'
6
+ require_relative '../config/option_normalizers'
7
+
8
+ module CovLoupe
9
+ module Tools
10
+ class ListTool < BaseTool
11
+ description <<~DESC
12
+ Use this when the user wants coverage percentages for every tracked file in the project.
13
+ Do not use this for single-file stats; prefer coverage.summary or coverage.uncovered_lines for that.
14
+ Inputs: optional project root, alternate .resultset path, sort order, raise_on_stale flag, and tracked_globs to alert on new files.
15
+ Output: JSON {"files": [{"file","covered","total","percentage","stale"}, ...], "counts": {"total", "ok", "stale"}, "skipped_files": [...], "missing_tracked_files": [...], "newer_files": [...], "deleted_files": [...], "length_mismatch_files": [...], "unreadable_files": [...], "timestamp_status": "ok"|"missing", "warnings": [string, ...]} sorted as requested. "stale" is "ok", "missing", "newer", "length_mismatch", or "error". "timestamp_status" indicates whether coverage timestamps are available for time-based staleness checks. "warnings" array is present when timestamp_status is "missing".
16
+ Examples: "List files with the lowest coverage"; "Show repo coverage sorted descending".
17
+ DESC
18
+ input_schema(**coverage_schema(
19
+ additional_properties: {
20
+ sort_order: SORT_ORDER_PROPERTY,
21
+ tracked_globs: TRACKED_GLOBS_PROPERTY
22
+ }
23
+ ))
24
+ class << self
25
+ def call(root: nil, resultset: nil, sort_order: nil, raise_on_stale: nil,
26
+ tracked_globs: nil, error_mode: 'log', output_chars: nil, server_context:)
27
+ output_chars_sym = resolve_output_chars(output_chars, server_context)
28
+ with_error_handling('ListTool', error_mode: error_mode, output_chars: output_chars_sym) do
29
+ model, config = create_configured_model(
30
+ server_context: server_context,
31
+ root: root,
32
+ resultset: resultset,
33
+ raise_on_stale: raise_on_stale,
34
+ tracked_globs: tracked_globs
35
+ )
36
+
37
+ # Normalize and validate sort_order (supports 'a'/'d' abbreviations)
38
+ sort_order_sym = OptionNormalizers.normalize_sort_order(
39
+ sort_order || BaseTool::DEFAULT_SORT_ORDER, strict: true
40
+ )
41
+
42
+ presenter = Presenters::ProjectCoveragePresenter.new(
43
+ model: model,
44
+ sort_order: sort_order_sym,
45
+ raise_on_stale: config[:raise_on_stale],
46
+ tracked_globs: config[:tracked_globs]
47
+ )
48
+ payload = presenter.relativized_payload
49
+
50
+ # Add warnings array if timestamp_status is missing
51
+ if payload['timestamp_status'] == 'missing'
52
+ payload['warnings'] = [
53
+ 'Coverage timestamps are missing. Time-based staleness checks were skipped.',
54
+ 'Files may appear "ok" even if source code is newer than the coverage data.',
55
+ 'Check your coverage tool configuration to ensure timestamps are recorded.'
56
+ ]
57
+ end
58
+
59
+ respond_json(payload, name: 'list_coverage.json', output_chars: output_chars_sym)
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../base_tool'
4
- require_relative '../model'
5
- require_relative '../presenters/coverage_uncovered_presenter'
4
+ require_relative '../model/model'
5
+ require_relative '../presenters/coverage_payload_presenter'
6
6
 
7
7
  module CovLoupe
8
8
  module Tools
@@ -10,23 +10,23 @@ module CovLoupe
10
10
  description <<~DESC
11
11
  Use this when the user wants to know which lines in a file still lack coverage.
12
12
  Do not use this for overall percentages; coverage.summary is faster when counts are enough.
13
- Inputs: file path (required) plus optional root/resultset/staleness mode inherited from BaseTool.
13
+ Inputs: file path (required) plus optional root/resultset/raise_on_stale flag inherited from BaseTool.
14
14
  Output: JSON object with keys "file", "uncovered" (array of integers), "summary" {"covered","total","percentage"}, and "stale" status.
15
15
  Example: "List uncovered lines for lib/cov_loupe/tools/coverage_summary_tool.rb".
16
16
  DESC
17
17
  input_schema(**input_schema_def)
18
18
  class << self
19
- def call(path:, root: '.', resultset: nil, staleness: :off, error_mode: 'log',
20
- server_context:)
21
- with_error_handling('UncoveredLinesTool', error_mode: error_mode) do
22
- model = CoverageModel.new(
23
- root: root,
24
- resultset: resultset,
25
- staleness: staleness.to_sym
26
- )
27
- presenter = Presenters::CoverageUncoveredPresenter.new(model: model, path: path)
28
- respond_json(presenter.relativized_payload, name: 'uncovered_lines.json', pretty: true)
29
- end
19
+ def call(path:, root: nil, resultset: nil, raise_on_stale: nil, error_mode: 'log',
20
+ output_chars: nil, server_context:)
21
+ call_with_file_payload(
22
+ path: path,
23
+ error_mode: error_mode,
24
+ output_chars: output_chars,
25
+ server_context: server_context,
26
+ root: root,
27
+ resultset: resultset,
28
+ raise_on_stale: raise_on_stale
29
+ )
30
30
  end
31
31
  end
32
32
  end