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.
- checksums.yaml +4 -4
- data/AGENTS.md +230 -0
- data/CLAUDE.md +5 -0
- data/CODE_OF_CONDUCT.md +62 -0
- data/CONTRIBUTING.md +102 -0
- data/GEMINI.md +5 -0
- data/README.md +154 -51
- data/RELEASE_NOTES.md +452 -0
- data/dev/images/cov-loupe-icon-lores.png +0 -0
- data/dev/images/cov-loupe-icon-square.png +0 -0
- data/dev/images/cov-loupe-icon.png +0 -0
- data/dev/images/cov-loupe-logo.png +0 -0
- data/dev/prompts/README.md +74 -0
- data/dev/prompts/archive/architectural-review-and-actions-prompt.md +53 -0
- data/dev/prompts/archive/investigate-and-report-issues-prompt.md +33 -0
- data/dev/prompts/archive/produce-action-items-prompt.md +25 -0
- data/dev/prompts/guidelines/ai-code-evaluator-guidelines.md +337 -0
- data/dev/prompts/improve/refactor-test-suite.md +18 -0
- data/dev/prompts/improve/simplify-code-logic.md +133 -0
- data/dev/prompts/improve/update-documentation.md +21 -0
- data/dev/prompts/review/comprehensive-codebase-review.md +176 -0
- data/dev/prompts/review/identify-action-items.md +143 -0
- data/dev/prompts/review/verify-code-changes.md +54 -0
- data/dev/prompts/validate/create-screencast-outline.md +234 -0
- data/dev/prompts/validate/test-documentation-examples.md +180 -0
- data/docs/QUICKSTART.md +63 -0
- data/docs/assets/images/cov-loupe-logo-lores.png +0 -0
- data/docs/assets/images/cov-loupe-logo.png +0 -0
- data/docs/assets/images/favicon.png +0 -0
- data/docs/assets/stylesheets/branding.css +16 -0
- data/docs/assets/stylesheets/extra.css +15 -0
- data/docs/code_of_conduct.md +1 -0
- data/docs/contributing.md +1 -0
- data/docs/dev/ARCHITECTURE.md +56 -11
- data/docs/dev/DEVELOPMENT.md +116 -12
- data/docs/dev/FUTURE_ENHANCEMENTS.md +14 -0
- data/docs/dev/README.md +3 -2
- data/docs/dev/RELEASING.md +2 -0
- data/docs/dev/arch-decisions/README.md +10 -7
- data/docs/dev/arch-decisions/application-architecture.md +259 -0
- data/docs/dev/arch-decisions/coverage-data-quality.md +193 -0
- data/docs/dev/arch-decisions/output-character-mode.md +217 -0
- data/docs/dev/arch-decisions/path-resolution.md +90 -0
- data/docs/dev/arch-decisions/{004-x-arch-decision.md → policy-validation.md} +32 -28
- data/docs/dev/arch-decisions/{005-x-arch-decision.md → simplecov-integration.md} +47 -44
- data/docs/dev/presentations/cov-loupe-presentation.md +15 -13
- data/docs/examples/mcp-inputs.md +3 -0
- data/docs/examples/prompts.md +3 -0
- data/docs/examples/success_predicates.md +3 -0
- data/docs/fixtures/demo_project/.resultset.json +170 -0
- data/docs/fixtures/demo_project/README.md +6 -0
- data/docs/fixtures/demo_project/app/controllers/admin/audit_logs_controller.rb +19 -0
- data/docs/fixtures/demo_project/app/controllers/orders_controller.rb +26 -0
- data/docs/fixtures/demo_project/app/models/order.rb +20 -0
- data/docs/fixtures/demo_project/app/models/user.rb +19 -0
- data/docs/fixtures/demo_project/lib/api/client.rb +22 -0
- data/docs/fixtures/demo_project/lib/ops/jobs/cleanup_job.rb +16 -0
- data/docs/fixtures/demo_project/lib/ops/jobs/report_job.rb +17 -0
- data/docs/fixtures/demo_project/lib/payments/processor.rb +15 -0
- data/docs/fixtures/demo_project/lib/payments/refund_service.rb +15 -0
- data/docs/fixtures/demo_project/lib/payments/reporting/exporter.rb +16 -0
- data/docs/index.md +1 -0
- data/docs/license.md +3 -0
- data/docs/release_notes.md +3 -0
- data/docs/user/ADVANCED_USAGE.md +208 -115
- data/docs/user/CLI_FALLBACK_FOR_LLMS.md +2 -0
- data/docs/user/CLI_USAGE.md +276 -101
- data/docs/user/ERROR_HANDLING.md +4 -4
- data/docs/user/EXAMPLES.md +121 -128
- data/docs/user/INSTALLATION.md +9 -28
- data/docs/user/LIBRARY_API.md +227 -122
- data/docs/user/MCP_INTEGRATION.md +114 -203
- data/docs/user/README.md +5 -1
- data/docs/user/TROUBLESHOOTING.md +49 -27
- data/docs/user/installing-a-prelease-version-of-covloupe.md +43 -0
- data/docs/user/{V2-BREAKING-CHANGES.md → migrations/MIGRATING_TO_V2.md} +62 -72
- data/docs/user/migrations/MIGRATING_TO_V3.md +72 -0
- data/docs/user/migrations/MIGRATING_TO_V4.md +591 -0
- data/docs/user/migrations/README.md +22 -0
- data/docs/user/prompts/README.md +9 -0
- data/docs/user/prompts/non-web-coverage-analysis-prompt.md +103 -0
- data/docs/user/prompts/rails-coverage-analysis-prompt.md +94 -0
- data/docs/user/prompts/use-cli-not-mcp-prompt.md +53 -0
- data/examples/cli_demo.sh +77 -0
- data/examples/filter_and_table_demo-output.md +114 -0
- data/examples/filter_and_table_demo.rb +174 -0
- data/examples/fixtures/demo_project/coverage/.resultset.json +10 -0
- data/examples/mcp-inputs/README.md +66 -0
- data/examples/mcp-inputs/coverage_detailed.json +1 -0
- data/examples/mcp-inputs/coverage_raw.json +1 -0
- data/examples/mcp-inputs/coverage_summary.json +1 -0
- data/examples/mcp-inputs/list.json +1 -0
- data/examples/mcp-inputs/uncovered_lines.json +1 -0
- data/examples/prompts/README.md +27 -0
- data/examples/prompts/custom_resultset.txt +2 -0
- data/examples/prompts/detailed_with_source.txt +2 -0
- data/examples/prompts/list_lowest.txt +2 -0
- data/examples/prompts/summary.txt +2 -0
- data/examples/prompts/uncovered.txt +2 -0
- data/examples/success_predicates/README.md +198 -0
- data/examples/success_predicates/all_files_above_threshold_predicate.rb +21 -0
- data/examples/success_predicates/directory_specific_thresholds_predicate.rb +30 -0
- data/examples/success_predicates/project_coverage_minimum_predicate.rb +6 -0
- data/lib/cov_loupe/base_tool.rb +229 -20
- data/lib/cov_loupe/cli.rb +132 -23
- data/lib/cov_loupe/commands/base_command.rb +25 -6
- data/lib/cov_loupe/commands/command_factory.rb +0 -1
- data/lib/cov_loupe/commands/detailed_command.rb +10 -5
- data/lib/cov_loupe/commands/list_command.rb +2 -1
- data/lib/cov_loupe/commands/raw_command.rb +7 -5
- data/lib/cov_loupe/commands/summary_command.rb +12 -7
- data/lib/cov_loupe/commands/totals_command.rb +74 -10
- data/lib/cov_loupe/commands/uncovered_command.rb +7 -5
- data/lib/cov_loupe/commands/validate_command.rb +11 -3
- data/lib/cov_loupe/commands/version_command.rb +6 -4
- data/lib/cov_loupe/{app_config.rb → config/app_config.rb} +13 -5
- data/lib/cov_loupe/config/app_context.rb +43 -0
- data/lib/cov_loupe/config/boolean_type.rb +91 -0
- data/lib/cov_loupe/config/logger.rb +92 -0
- data/lib/cov_loupe/{option_normalizers.rb → config/option_normalizers.rb} +55 -24
- data/lib/cov_loupe/{option_parser_builder.rb → config/option_parser_builder.rb} +46 -24
- data/lib/cov_loupe/coverage/coverage_calculator.rb +53 -0
- data/lib/cov_loupe/coverage/coverage_reporter.rb +63 -0
- data/lib/cov_loupe/coverage/coverage_table_formatter.rb +133 -0
- data/lib/cov_loupe/{error_handler.rb → errors/error_handler.rb} +21 -33
- data/lib/cov_loupe/{errors.rb → errors/errors.rb} +48 -71
- data/lib/cov_loupe/formatters/formatters.rb +75 -0
- data/lib/cov_loupe/formatters/source_formatter.rb +18 -7
- data/lib/cov_loupe/formatters/table_formatter.rb +80 -0
- data/lib/cov_loupe/loaders/all.rb +15 -0
- data/lib/cov_loupe/loaders/all_cli.rb +10 -0
- data/lib/cov_loupe/loaders/all_mcp.rb +23 -0
- data/lib/cov_loupe/loaders/resultset_loader.rb +147 -0
- data/lib/cov_loupe/mcp_server.rb +3 -2
- data/lib/cov_loupe/model/model.rb +520 -0
- data/lib/cov_loupe/model/model_data.rb +13 -0
- data/lib/cov_loupe/model/model_data_cache.rb +116 -0
- data/lib/cov_loupe/option_parsers/env_options_parser.rb +17 -6
- data/lib/cov_loupe/option_parsers/error_helper.rb +16 -10
- data/lib/cov_loupe/output_chars.rb +192 -0
- data/lib/cov_loupe/paths/glob_utils.rb +100 -0
- data/lib/cov_loupe/{path_relativizer.rb → paths/path_relativizer.rb} +5 -13
- data/lib/cov_loupe/paths/path_utils.rb +265 -0
- data/lib/cov_loupe/paths/volume_case_sensitivity.rb +173 -0
- data/lib/cov_loupe/presenters/base_coverage_presenter.rb +9 -13
- data/lib/cov_loupe/presenters/coverage_payload_presenter.rb +21 -0
- data/lib/cov_loupe/presenters/payload_caching.rb +23 -0
- data/lib/cov_loupe/presenters/project_coverage_presenter.rb +73 -21
- data/lib/cov_loupe/presenters/project_totals_presenter.rb +16 -10
- data/lib/cov_loupe/repositories/coverage_repository.rb +149 -0
- data/lib/cov_loupe/resolvers/coverage_line_resolver.rb +90 -76
- data/lib/cov_loupe/resolvers/{resolver_factory.rb → resolver_helpers.rb} +6 -5
- data/lib/cov_loupe/resolvers/resultset_path_resolver.rb +40 -12
- data/lib/cov_loupe/scripts/command_execution.rb +113 -0
- data/lib/cov_loupe/scripts/latest_ci_status.rb +97 -0
- data/lib/cov_loupe/scripts/pre_release_check.rb +164 -0
- data/lib/cov_loupe/scripts/setup_doc_server.rb +23 -0
- data/lib/cov_loupe/scripts/start_doc_server.rb +24 -0
- data/lib/cov_loupe/staleness/stale_status.rb +23 -0
- data/lib/cov_loupe/staleness/staleness_checker.rb +328 -0
- data/lib/cov_loupe/staleness/staleness_message_formatter.rb +91 -0
- data/lib/cov_loupe/tools/coverage_detailed_tool.rb +14 -15
- data/lib/cov_loupe/tools/coverage_raw_tool.rb +14 -14
- data/lib/cov_loupe/tools/coverage_summary_tool.rb +16 -16
- data/lib/cov_loupe/tools/coverage_table_tool.rb +139 -21
- data/lib/cov_loupe/tools/coverage_totals_tool.rb +31 -13
- data/lib/cov_loupe/tools/help_tool.rb +16 -20
- data/lib/cov_loupe/tools/list_tool.rb +65 -0
- data/lib/cov_loupe/tools/uncovered_lines_tool.rb +14 -14
- data/lib/cov_loupe/tools/validate_tool.rb +18 -24
- data/lib/cov_loupe/tools/version_tool.rb +8 -3
- data/lib/cov_loupe/version.rb +1 -1
- data/lib/cov_loupe.rb +83 -55
- metadata +184 -154
- data/docs/dev/BRANCH_ONLY_COVERAGE.md +0 -158
- data/docs/dev/arch-decisions/001-x-arch-decision.md +0 -95
- data/docs/dev/arch-decisions/002-x-arch-decision.md +0 -159
- data/docs/dev/arch-decisions/003-x-arch-decision.md +0 -165
- data/lib/cov_loupe/app_context.rb +0 -26
- data/lib/cov_loupe/constants.rb +0 -22
- data/lib/cov_loupe/coverage_reporter.rb +0 -31
- data/lib/cov_loupe/formatters.rb +0 -51
- data/lib/cov_loupe/mode_detector.rb +0 -56
- data/lib/cov_loupe/model.rb +0 -339
- data/lib/cov_loupe/presenters/coverage_detailed_presenter.rb +0 -14
- data/lib/cov_loupe/presenters/coverage_raw_presenter.rb +0 -14
- data/lib/cov_loupe/presenters/coverage_summary_presenter.rb +0 -14
- data/lib/cov_loupe/presenters/coverage_uncovered_presenter.rb +0 -14
- data/lib/cov_loupe/resultset_loader.rb +0 -131
- data/lib/cov_loupe/staleness_checker.rb +0 -247
- data/lib/cov_loupe/table_formatter.rb +0 -64
- data/lib/cov_loupe/tools/all_files_coverage_tool.rb +0 -51
- data/lib/cov_loupe/util.rb +0 -88
- data/spec/MCP_INTEGRATION_TESTS_README.md +0 -111
- data/spec/TIMESTAMPS.md +0 -48
- data/spec/all_files_coverage_tool_spec.rb +0 -53
- data/spec/app_config_spec.rb +0 -142
- data/spec/base_tool_spec.rb +0 -62
- data/spec/cli/show_default_report_spec.rb +0 -33
- data/spec/cli_enumerated_options_spec.rb +0 -90
- data/spec/cli_error_spec.rb +0 -184
- data/spec/cli_format_spec.rb +0 -123
- data/spec/cli_json_options_spec.rb +0 -50
- data/spec/cli_source_spec.rb +0 -44
- data/spec/cli_spec.rb +0 -192
- data/spec/cli_table_spec.rb +0 -28
- data/spec/cli_usage_spec.rb +0 -42
- data/spec/commands/base_command_spec.rb +0 -107
- data/spec/commands/command_factory_spec.rb +0 -76
- data/spec/commands/detailed_command_spec.rb +0 -34
- data/spec/commands/list_command_spec.rb +0 -28
- data/spec/commands/raw_command_spec.rb +0 -69
- data/spec/commands/summary_command_spec.rb +0 -34
- data/spec/commands/totals_command_spec.rb +0 -34
- data/spec/commands/uncovered_command_spec.rb +0 -55
- data/spec/commands/validate_command_spec.rb +0 -213
- data/spec/commands/version_command_spec.rb +0 -38
- data/spec/constants_spec.rb +0 -61
- data/spec/cov_loupe/formatters/source_formatter_spec.rb +0 -267
- data/spec/cov_loupe/formatters_spec.rb +0 -76
- data/spec/cov_loupe/presenters/base_coverage_presenter_spec.rb +0 -79
- data/spec/cov_loupe_model_spec.rb +0 -454
- data/spec/cov_loupe_module_spec.rb +0 -37
- data/spec/cov_loupe_opts_spec.rb +0 -185
- data/spec/coverage_reporter_spec.rb +0 -102
- data/spec/coverage_table_tool_spec.rb +0 -59
- data/spec/coverage_totals_tool_spec.rb +0 -37
- data/spec/error_handler_spec.rb +0 -197
- data/spec/error_mode_spec.rb +0 -139
- data/spec/errors_edge_cases_spec.rb +0 -312
- data/spec/errors_stale_spec.rb +0 -83
- data/spec/file_based_mcp_tools_spec.rb +0 -99
- data/spec/help_tool_spec.rb +0 -26
- data/spec/integration_spec.rb +0 -789
- data/spec/logging_fallback_spec.rb +0 -128
- data/spec/mcp_logging_spec.rb +0 -44
- data/spec/mcp_server_integration_spec.rb +0 -23
- data/spec/mcp_server_spec.rb +0 -106
- data/spec/mode_detector_spec.rb +0 -153
- data/spec/model_error_handling_spec.rb +0 -269
- data/spec/model_staleness_spec.rb +0 -79
- data/spec/option_normalizers_spec.rb +0 -203
- data/spec/option_parsers/env_options_parser_spec.rb +0 -221
- data/spec/option_parsers/error_helper_spec.rb +0 -222
- data/spec/path_relativizer_spec.rb +0 -98
- data/spec/presenters/coverage_detailed_presenter_spec.rb +0 -19
- data/spec/presenters/coverage_raw_presenter_spec.rb +0 -15
- data/spec/presenters/coverage_summary_presenter_spec.rb +0 -15
- data/spec/presenters/coverage_uncovered_presenter_spec.rb +0 -16
- data/spec/presenters/project_coverage_presenter_spec.rb +0 -87
- data/spec/presenters/project_totals_presenter_spec.rb +0 -144
- data/spec/resolvers/coverage_line_resolver_spec.rb +0 -282
- data/spec/resolvers/resolver_factory_spec.rb +0 -61
- data/spec/resolvers/resultset_path_resolver_spec.rb +0 -60
- data/spec/resultset_loader_spec.rb +0 -167
- data/spec/shared_examples/README.md +0 -115
- data/spec/shared_examples/coverage_presenter_examples.rb +0 -66
- data/spec/shared_examples/file_based_mcp_tools.rb +0 -179
- data/spec/shared_examples/formatted_command_examples.rb +0 -64
- data/spec/shared_examples/mcp_tool_text_json_response.rb +0 -16
- data/spec/spec_helper.rb +0 -127
- data/spec/staleness_checker_spec.rb +0 -374
- data/spec/staleness_more_spec.rb +0 -42
- data/spec/support/cli_helpers.rb +0 -22
- data/spec/support/control_flow_helpers.rb +0 -20
- data/spec/support/fake_mcp.rb +0 -40
- data/spec/support/io_helpers.rb +0 -29
- data/spec/support/mcp_helpers.rb +0 -35
- data/spec/support/mcp_runner.rb +0 -66
- data/spec/support/mocking_helpers.rb +0 -30
- data/spec/table_format_spec.rb +0 -70
- data/spec/tools/validate_tool_spec.rb +0 -132
- data/spec/tools_error_handling_spec.rb +0 -130
- data/spec/util_spec.rb +0 -154
- data/spec/version_spec.rb +0 -123
- data/spec/version_tool_spec.rb +0 -141
- /data/{spec/fixtures/project1 → examples/fixtures/demo_project}/lib/bar.rb +0 -0
- /data/{spec/fixtures/project1 → examples/fixtures/demo_project}/lib/foo.rb +0 -0
- /data/lib/cov_loupe/{config_parser.rb → config/config_parser.rb} +0 -0
- /data/lib/cov_loupe/{predicate_evaluator.rb → config/predicate_evaluator.rb} +0 -0
- /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 '../predicate_evaluator'
|
|
4
|
+
require_relative '../model/model'
|
|
5
|
+
require_relative '../config/predicate_evaluator'
|
|
6
6
|
|
|
7
7
|
module CovLoupe
|
|
8
8
|
module Tools
|
|
@@ -10,12 +10,12 @@ module CovLoupe
|
|
|
10
10
|
description <<~DESC
|
|
11
11
|
Validates coverage data against a predicate (Ruby code that evaluates to true/false).
|
|
12
12
|
Use this to enforce coverage policies programmatically.
|
|
13
|
-
Inputs: Either 'code' (Ruby string) OR 'file' (path to Ruby file), plus optional root/resultset/
|
|
13
|
+
Inputs: Either 'code' (Ruby string) OR 'file' (path to Ruby file), plus optional root/resultset/raise_on_stale/error_mode.
|
|
14
14
|
Output: JSON object {"result": Boolean} where true means policy passed, false means failed.
|
|
15
15
|
On error (syntax error, file not found, etc.), returns an MCP error response.
|
|
16
16
|
Security Warning: Predicates execute as arbitrary Ruby code with full system privileges.
|
|
17
17
|
Examples:
|
|
18
|
-
- "Check if all files have at least 80% coverage" → {"code": "->(m) { m.
|
|
18
|
+
- "Check if all files have at least 80% coverage" → {"code": "->(m) { m.list.all? { |f| f['percentage'] >= 80 } }"}
|
|
19
19
|
- "Run coverage policy from file" → {"file": "coverage_policy.rb"}
|
|
20
20
|
DESC
|
|
21
21
|
|
|
@@ -34,36 +34,30 @@ module CovLoupe
|
|
|
34
34
|
}
|
|
35
35
|
))
|
|
36
36
|
class << self
|
|
37
|
-
def call(code: nil, file: nil, root:
|
|
38
|
-
error_mode: 'log', server_context:)
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
cli.config.error_mode = error_mode.to_sym
|
|
49
|
-
|
|
50
|
-
# We need to capture the boolean result instead of letting it exit
|
|
51
|
-
# Commands::ValidateCommand is designed to exit, so we'll use the model and evaluator directly
|
|
52
|
-
# This duplicates some logic from ValidateCommand#execute but avoids the exit(status) call
|
|
53
|
-
|
|
54
|
-
model = CoverageModel.new(**cli.config.model_options)
|
|
37
|
+
def call(code: nil, file: nil, root: nil, resultset: nil, raise_on_stale: nil,
|
|
38
|
+
error_mode: 'log', output_chars: nil, server_context:)
|
|
39
|
+
# Normalize output_chars before error handling so errors also get converted
|
|
40
|
+
output_chars_sym = resolve_output_chars(output_chars, server_context)
|
|
41
|
+
with_error_handling('ValidateTool', error_mode: error_mode, output_chars: output_chars_sym) do
|
|
42
|
+
model, config = create_configured_model(
|
|
43
|
+
server_context: server_context,
|
|
44
|
+
root: root,
|
|
45
|
+
resultset: resultset,
|
|
46
|
+
raise_on_stale: raise_on_stale
|
|
47
|
+
)
|
|
55
48
|
|
|
56
49
|
result = if code
|
|
57
50
|
PredicateEvaluator.evaluate_code(code, model)
|
|
58
51
|
elsif file
|
|
59
52
|
# Resolve file path relative to root if needed
|
|
60
|
-
predicate_path = File.expand_path(file, root)
|
|
53
|
+
predicate_path = File.expand_path(file, config[:root])
|
|
61
54
|
PredicateEvaluator.evaluate_file(predicate_path, model)
|
|
62
55
|
else
|
|
63
56
|
raise UsageError, "Either 'code' or 'file' must be provided"
|
|
64
57
|
end
|
|
65
58
|
|
|
66
|
-
respond_json({ result: result }, name: 'validate_result.json', pretty: true
|
|
59
|
+
respond_json({ result: result }, name: 'validate_result.json', pretty: true,
|
|
60
|
+
output_chars: output_chars_sym)
|
|
67
61
|
end
|
|
68
62
|
end
|
|
69
63
|
end
|
|
@@ -15,12 +15,17 @@ module CovLoupe
|
|
|
15
15
|
type: 'object',
|
|
16
16
|
additionalProperties: false,
|
|
17
17
|
properties: {
|
|
18
|
-
error_mode: ERROR_MODE_PROPERTY
|
|
18
|
+
error_mode: ERROR_MODE_PROPERTY,
|
|
19
|
+
output_chars: COMMON_PROPERTIES[:output_chars]
|
|
19
20
|
}
|
|
20
21
|
)
|
|
21
22
|
class << self
|
|
22
|
-
|
|
23
|
-
|
|
23
|
+
# NOTE: output_chars is accepted for consistency and used in error handling,
|
|
24
|
+
# though the version string itself is already ASCII-only.
|
|
25
|
+
def call(error_mode: 'log', output_chars: nil, server_context: nil, **_args)
|
|
26
|
+
# Normalize output_chars before error handling so errors also get converted
|
|
27
|
+
output_chars_sym = resolve_output_chars(output_chars, server_context)
|
|
28
|
+
with_error_handling('VersionTool', error_mode: error_mode, output_chars: output_chars_sym) do
|
|
24
29
|
::MCP::Tool::Response.new([
|
|
25
30
|
{ 'type' => 'text', 'text' => "CovLoupe version: #{CovLoupe::VERSION}" }
|
|
26
31
|
])
|
data/lib/cov_loupe/version.rb
CHANGED
data/lib/cov_loupe.rb
CHANGED
|
@@ -5,49 +5,63 @@ require 'time'
|
|
|
5
5
|
require 'pathname'
|
|
6
6
|
require 'set' # rubocop:disable Lint/RedundantRequireStatement -- Ruby >= 3.4 requires explicit require for set; RuboCop targets 3.2
|
|
7
7
|
require 'optparse'
|
|
8
|
-
require 'mcp'
|
|
9
|
-
require 'mcp/server/transports/stdio_transport'
|
|
10
8
|
|
|
11
9
|
require_relative 'cov_loupe/version'
|
|
12
|
-
require_relative 'cov_loupe/app_context'
|
|
13
|
-
require_relative 'cov_loupe/
|
|
14
|
-
require_relative 'cov_loupe/errors'
|
|
15
|
-
require_relative 'cov_loupe/
|
|
16
|
-
require_relative 'cov_loupe/
|
|
17
|
-
require_relative 'cov_loupe/
|
|
18
|
-
require_relative 'cov_loupe/
|
|
19
|
-
require_relative 'cov_loupe/
|
|
20
|
-
require_relative 'cov_loupe/model'
|
|
21
|
-
require_relative 'cov_loupe/coverage_reporter'
|
|
22
|
-
require_relative 'cov_loupe/base_tool'
|
|
23
|
-
require_relative 'cov_loupe/tools/coverage_raw_tool'
|
|
24
|
-
require_relative 'cov_loupe/tools/coverage_summary_tool'
|
|
25
|
-
require_relative 'cov_loupe/tools/uncovered_lines_tool'
|
|
26
|
-
require_relative 'cov_loupe/tools/coverage_detailed_tool'
|
|
27
|
-
require_relative 'cov_loupe/tools/all_files_coverage_tool'
|
|
28
|
-
require_relative 'cov_loupe/tools/coverage_totals_tool'
|
|
29
|
-
require_relative 'cov_loupe/tools/coverage_table_tool'
|
|
30
|
-
require_relative 'cov_loupe/tools/validate_tool'
|
|
31
|
-
require_relative 'cov_loupe/tools/version_tool'
|
|
32
|
-
require_relative 'cov_loupe/tools/help_tool'
|
|
33
|
-
require_relative 'cov_loupe/mcp_server'
|
|
34
|
-
require_relative 'cov_loupe/cli'
|
|
10
|
+
require_relative 'cov_loupe/config/app_context'
|
|
11
|
+
require_relative 'cov_loupe/errors/errors'
|
|
12
|
+
require_relative 'cov_loupe/errors/error_handler'
|
|
13
|
+
require_relative 'cov_loupe/errors/error_handler_factory'
|
|
14
|
+
require_relative 'cov_loupe/paths/path_relativizer'
|
|
15
|
+
require_relative 'cov_loupe/loaders/resultset_loader'
|
|
16
|
+
require_relative 'cov_loupe/model/model'
|
|
17
|
+
require_relative 'cov_loupe/coverage/coverage_reporter'
|
|
35
18
|
|
|
36
19
|
module CovLoupe
|
|
37
20
|
class << self
|
|
21
|
+
# === Context Management and Thread Safety ===
|
|
22
|
+
#
|
|
23
|
+
# CovLoupe manages configuration (logging, error handling) via `AppContext` objects.
|
|
24
|
+
# The resolution strategy is:
|
|
25
|
+
# 1. Thread-local: Use `Thread.current[:cov_loupe_context]` if set.
|
|
26
|
+
# 2. Global default: Fall back to `@internal_default_context`.
|
|
27
|
+
#
|
|
28
|
+
# This design supports both simple CLI usage (one global context) and multi-threaded
|
|
29
|
+
# library usage (per-thread contexts).
|
|
30
|
+
#
|
|
31
|
+
# Thread Safety:
|
|
32
|
+
# - `mutex` protects all reads/writes to `@internal_default_context`.
|
|
33
|
+
# - `default_log_file=` atomically updates the global default. Threads using the
|
|
34
|
+
# default (nil thread-local context) will immediately see the new value.
|
|
35
|
+
# - `active_log_file=` creates or updates a *thread-local* context, isolating
|
|
36
|
+
# changes to the current thread.
|
|
37
|
+
#
|
|
38
|
+
# This separation ensures that changing the global default is safe and predictable,
|
|
39
|
+
# while allowing threads to diverge when necessary without race conditions.
|
|
40
|
+
|
|
38
41
|
THREAD_CONTEXT_KEY = :cov_loupe_context
|
|
42
|
+
private_constant :THREAD_CONTEXT_KEY
|
|
39
43
|
|
|
40
44
|
def run(argv)
|
|
41
|
-
#
|
|
42
|
-
|
|
45
|
+
# Parse config to determine mode
|
|
46
|
+
require_relative 'cov_loupe/config/config_parser'
|
|
43
47
|
|
|
44
|
-
|
|
45
|
-
#
|
|
48
|
+
begin
|
|
49
|
+
# Prepend environment options once at entry point
|
|
50
|
+
full_argv = extract_env_opts + argv
|
|
51
|
+
config = ConfigParser.parse(full_argv.dup)
|
|
52
|
+
rescue OptionParser::ParseError, ConfigurationError => e
|
|
53
|
+
warn "Error: #{e.message}"
|
|
54
|
+
warn "Run 'cov-loupe --help' for usage information."
|
|
55
|
+
exit 2
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
if config.mode == :cli
|
|
59
|
+
# CLI mode: load CLI components only
|
|
60
|
+
require_relative 'cov_loupe/loaders/all_cli'
|
|
46
61
|
CoverageCLI.new.run(full_argv)
|
|
47
62
|
else
|
|
48
|
-
# MCP server mode:
|
|
49
|
-
require_relative 'cov_loupe/
|
|
50
|
-
config = ConfigParser.parse(full_argv)
|
|
63
|
+
# MCP server mode: load MCP server components only
|
|
64
|
+
require_relative 'cov_loupe/loaders/all_mcp'
|
|
51
65
|
|
|
52
66
|
if config.log_file == 'stdout'
|
|
53
67
|
raise ConfigurationError,
|
|
@@ -57,7 +71,7 @@ module CovLoupe
|
|
|
57
71
|
|
|
58
72
|
handler = ErrorHandlerFactory.for_mcp_server(error_mode: config.error_mode)
|
|
59
73
|
context = create_context(error_handler: handler, log_target: config.log_file,
|
|
60
|
-
mode: :mcp)
|
|
74
|
+
mode: :mcp, app_config: config)
|
|
61
75
|
with_context(context) { MCPServer.new(context: context).run }
|
|
62
76
|
end
|
|
63
77
|
end
|
|
@@ -74,11 +88,12 @@ module CovLoupe
|
|
|
74
88
|
Thread.current[THREAD_CONTEXT_KEY] || default_context
|
|
75
89
|
end
|
|
76
90
|
|
|
77
|
-
def create_context(error_handler:, log_target: nil, mode: :library)
|
|
91
|
+
def create_context(error_handler:, log_target: nil, mode: :library, app_config: nil)
|
|
78
92
|
AppContext.new(
|
|
79
93
|
error_handler: error_handler,
|
|
80
94
|
log_target: log_target.nil? ? default_context.log_target : log_target,
|
|
81
|
-
mode: mode
|
|
95
|
+
mode: mode,
|
|
96
|
+
app_config: app_config
|
|
82
97
|
)
|
|
83
98
|
end
|
|
84
99
|
|
|
@@ -87,13 +102,11 @@ module CovLoupe
|
|
|
87
102
|
end
|
|
88
103
|
|
|
89
104
|
def default_log_file=(value)
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if active.nil? || active.log_target == previous_default.log_target
|
|
94
|
-
Thread.current[THREAD_CONTEXT_KEY] = @default_context
|
|
105
|
+
mutex.synchronize do
|
|
106
|
+
previous_default = internal_default_context
|
|
107
|
+
@internal_default_context = previous_default.with(log_target: value)
|
|
95
108
|
end
|
|
96
|
-
value # rubocop:disable Lint/Void -- return assigned
|
|
109
|
+
value # rubocop:disable Lint/Void -- Setter should return assigned value for direct calls.
|
|
97
110
|
end
|
|
98
111
|
|
|
99
112
|
def active_log_file
|
|
@@ -103,11 +116,11 @@ module CovLoupe
|
|
|
103
116
|
def active_log_file=(value)
|
|
104
117
|
current = Thread.current[THREAD_CONTEXT_KEY]
|
|
105
118
|
Thread.current[THREAD_CONTEXT_KEY] = if current
|
|
106
|
-
current.
|
|
119
|
+
current.with(log_target: value)
|
|
107
120
|
else
|
|
108
|
-
|
|
121
|
+
base = mutex.synchronize { internal_default_context }
|
|
122
|
+
base.with(log_target: value)
|
|
109
123
|
end
|
|
110
|
-
value # rubocop:disable Lint/Void -- return assigned log target for symmetry
|
|
111
124
|
end
|
|
112
125
|
|
|
113
126
|
def error_handler
|
|
@@ -115,26 +128,41 @@ module CovLoupe
|
|
|
115
128
|
end
|
|
116
129
|
|
|
117
130
|
def error_handler=(handler)
|
|
118
|
-
|
|
131
|
+
mutex.synchronize do
|
|
132
|
+
previous_default = internal_default_context
|
|
133
|
+
@internal_default_context = previous_default.with(error_handler: handler)
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def logger
|
|
138
|
+
context.logger
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# Returns true if running on Windows (mingw, mswin, cygwin).
|
|
142
|
+
def windows?
|
|
143
|
+
return @windows if defined?(@windows)
|
|
144
|
+
|
|
145
|
+
@windows = RUBY_PLATFORM.match?(/mingw|mswin|cygwin/)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
private def mutex
|
|
149
|
+
@mutex ||= Mutex.new
|
|
119
150
|
end
|
|
120
151
|
|
|
121
152
|
private def default_context
|
|
122
|
-
|
|
153
|
+
mutex.synchronize { internal_default_context }
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
private def internal_default_context
|
|
157
|
+
@internal_default_context ||= AppContext.new(
|
|
123
158
|
error_handler: ErrorHandlerFactory.for_cli,
|
|
124
159
|
log_target: nil
|
|
125
160
|
)
|
|
126
161
|
end
|
|
127
162
|
|
|
128
163
|
private def extract_env_opts
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
return [] unless opts_string && !opts_string.empty?
|
|
132
|
-
|
|
133
|
-
begin
|
|
134
|
-
Shellwords.split(opts_string)
|
|
135
|
-
rescue ArgumentError
|
|
136
|
-
[] # Ignore parsing errors
|
|
137
|
-
end
|
|
164
|
+
require_relative 'cov_loupe/option_parsers/env_options_parser'
|
|
165
|
+
OptionParsers::EnvOptionsParser.new.parse_env_opts
|
|
138
166
|
end
|
|
139
167
|
end
|
|
140
168
|
end
|