simplecov-mcp 1.0.1 → 2.0.1
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/README.md +98 -50
- data/docs/{ARCHITECTURE.md → dev/ARCHITECTURE.md} +11 -10
- data/docs/dev/BRANCH_ONLY_COVERAGE.md +158 -0
- data/docs/{DEVELOPMENT.md → dev/DEVELOPMENT.md} +2 -1
- data/docs/dev/README.md +10 -0
- data/docs/dev/RELEASING.md +146 -0
- data/docs/{arch-decisions → dev/arch-decisions}/001-x-arch-decision.md +3 -1
- data/docs/{arch-decisions → dev/arch-decisions}/002-x-arch-decision.md +7 -5
- data/docs/{arch-decisions → dev/arch-decisions}/003-x-arch-decision.md +2 -0
- data/docs/{arch-decisions → dev/arch-decisions}/004-x-arch-decision.md +6 -2
- data/docs/{arch-decisions → dev/arch-decisions}/005-x-arch-decision.md +4 -2
- data/docs/{arch-decisions → dev/arch-decisions}/README.md +3 -3
- data/docs/{presentations → dev/presentations}/simplecov-mcp-presentation.md +28 -22
- data/docs/fixtures/demo_project/README.md +9 -0
- data/docs/{ADVANCED_USAGE.md → user/ADVANCED_USAGE.md} +129 -319
- data/docs/user/CLI_FALLBACK_FOR_LLMS.md +34 -0
- data/docs/user/CLI_USAGE.md +750 -0
- data/docs/{ERROR_HANDLING.md → user/ERROR_HANDLING.md} +12 -12
- data/docs/user/EXAMPLES.md +588 -0
- data/docs/user/INSTALLATION.md +130 -0
- data/docs/{LIBRARY_API.md → user/LIBRARY_API.md} +90 -32
- data/docs/{MCP_INTEGRATION.md → user/MCP_INTEGRATION.md} +36 -34
- data/docs/user/README.md +14 -0
- data/docs/{TROUBLESHOOTING.md → user/TROUBLESHOOTING.md} +21 -100
- data/docs/user/V2-BREAKING-CHANGES.md +472 -0
- data/exe/simplecov-mcp +1 -1
- data/lib/simplecov_mcp/{cli_config.rb → app_config.rb} +12 -12
- data/lib/simplecov_mcp/app_context.rb +1 -1
- data/lib/simplecov_mcp/base_tool.rb +66 -38
- data/lib/simplecov_mcp/cli.rb +67 -123
- data/lib/simplecov_mcp/commands/base_command.rb +16 -27
- data/lib/simplecov_mcp/commands/command_factory.rb +8 -2
- data/lib/simplecov_mcp/commands/detailed_command.rb +16 -2
- data/lib/simplecov_mcp/commands/list_command.rb +1 -1
- data/lib/simplecov_mcp/commands/raw_command.rb +18 -2
- data/lib/simplecov_mcp/commands/summary_command.rb +20 -3
- data/lib/simplecov_mcp/commands/totals_command.rb +53 -0
- data/lib/simplecov_mcp/commands/uncovered_command.rb +24 -5
- data/lib/simplecov_mcp/commands/validate_command.rb +60 -0
- data/lib/simplecov_mcp/commands/version_command.rb +19 -4
- data/lib/simplecov_mcp/config_parser.rb +32 -0
- data/lib/simplecov_mcp/constants.rb +3 -3
- data/lib/simplecov_mcp/coverage_reporter.rb +31 -0
- data/lib/simplecov_mcp/error_handler.rb +81 -40
- data/lib/simplecov_mcp/error_handler_factory.rb +2 -2
- data/lib/simplecov_mcp/errors.rb +12 -19
- data/lib/simplecov_mcp/formatters/source_formatter.rb +23 -19
- data/lib/simplecov_mcp/formatters.rb +51 -0
- data/lib/simplecov_mcp/mcp_server.rb +9 -7
- data/lib/simplecov_mcp/mode_detector.rb +6 -5
- data/lib/simplecov_mcp/model.rb +122 -88
- data/lib/simplecov_mcp/option_normalizers.rb +39 -18
- data/lib/simplecov_mcp/option_parser_builder.rb +85 -72
- data/lib/simplecov_mcp/option_parsers/env_options_parser.rb +3 -5
- data/lib/simplecov_mcp/option_parsers/error_helper.rb +18 -17
- data/lib/simplecov_mcp/path_relativizer.rb +17 -14
- data/lib/simplecov_mcp/predicate_evaluator.rb +72 -0
- data/lib/simplecov_mcp/presenters/base_coverage_presenter.rb +1 -3
- data/lib/simplecov_mcp/presenters/coverage_detailed_presenter.rb +1 -3
- data/lib/simplecov_mcp/presenters/coverage_raw_presenter.rb +1 -3
- data/lib/simplecov_mcp/presenters/coverage_summary_presenter.rb +1 -3
- data/lib/simplecov_mcp/presenters/coverage_uncovered_presenter.rb +1 -3
- data/lib/simplecov_mcp/presenters/project_coverage_presenter.rb +1 -3
- data/lib/simplecov_mcp/presenters/project_totals_presenter.rb +27 -0
- data/lib/simplecov_mcp/resolvers/coverage_line_resolver.rb +14 -18
- data/lib/simplecov_mcp/resolvers/resultset_path_resolver.rb +7 -9
- data/lib/simplecov_mcp/resultset_loader.rb +20 -25
- data/lib/simplecov_mcp/staleness_checker.rb +50 -46
- data/lib/simplecov_mcp/table_formatter.rb +64 -0
- data/lib/simplecov_mcp/tools/all_files_coverage_tool.rb +20 -50
- data/lib/simplecov_mcp/tools/coverage_detailed_tool.rb +13 -7
- data/lib/simplecov_mcp/tools/coverage_raw_tool.rb +12 -7
- data/lib/simplecov_mcp/tools/coverage_summary_tool.rb +13 -8
- data/lib/simplecov_mcp/tools/coverage_table_tool.rb +20 -60
- data/lib/simplecov_mcp/tools/coverage_totals_tool.rb +44 -0
- data/lib/simplecov_mcp/tools/help_tool.rb +38 -66
- data/lib/simplecov_mcp/tools/uncovered_lines_tool.rb +13 -8
- data/lib/simplecov_mcp/tools/validate_tool.rb +72 -0
- data/lib/simplecov_mcp/tools/version_tool.rb +7 -14
- data/lib/simplecov_mcp/util.rb +18 -12
- data/lib/simplecov_mcp/version.rb +1 -1
- data/lib/simplecov_mcp.rb +23 -29
- data/spec/all_files_coverage_tool_spec.rb +4 -3
- data/spec/{cli_config_spec.rb → app_config_spec.rb} +31 -26
- data/spec/base_tool_spec.rb +17 -14
- data/spec/cli/show_default_report_spec.rb +2 -2
- data/spec/cli_enumerated_options_spec.rb +31 -9
- data/spec/cli_error_spec.rb +46 -23
- data/spec/cli_format_spec.rb +123 -0
- data/spec/cli_json_options_spec.rb +50 -0
- data/spec/cli_source_spec.rb +11 -63
- data/spec/cli_spec.rb +82 -97
- data/spec/cli_usage_spec.rb +15 -15
- data/spec/commands/base_command_spec.rb +12 -92
- data/spec/commands/command_factory_spec.rb +7 -3
- data/spec/commands/detailed_command_spec.rb +10 -24
- data/spec/commands/list_command_spec.rb +28 -0
- data/spec/commands/raw_command_spec.rb +43 -20
- data/spec/commands/summary_command_spec.rb +10 -23
- data/spec/commands/totals_command_spec.rb +34 -0
- data/spec/commands/uncovered_command_spec.rb +29 -23
- data/spec/commands/validate_command_spec.rb +213 -0
- data/spec/commands/version_command_spec.rb +38 -0
- data/spec/constants_spec.rb +3 -3
- data/spec/coverage_reporter_spec.rb +102 -0
- data/spec/coverage_table_tool_spec.rb +21 -10
- data/spec/coverage_totals_tool_spec.rb +37 -0
- data/spec/error_handler_spec.rb +120 -4
- data/spec/error_mode_spec.rb +18 -22
- data/spec/errors_edge_cases_spec.rb +101 -28
- data/spec/errors_stale_spec.rb +34 -0
- data/spec/file_based_mcp_tools_spec.rb +6 -6
- data/spec/fixtures/project1/lib/bar.rb +2 -0
- data/spec/fixtures/project1/lib/foo.rb +2 -0
- data/spec/help_tool_spec.rb +2 -18
- data/spec/integration_spec.rb +103 -161
- data/spec/logging_fallback_spec.rb +3 -3
- data/spec/mcp_server_integration_spec.rb +1 -1
- data/spec/mcp_server_spec.rb +70 -53
- data/spec/mode_detector_spec.rb +46 -41
- data/spec/model_error_handling_spec.rb +139 -78
- data/spec/model_staleness_spec.rb +13 -13
- data/spec/option_normalizers_spec.rb +111 -112
- data/spec/option_parsers/env_options_parser_spec.rb +25 -37
- data/spec/option_parsers/error_helper_spec.rb +56 -56
- data/spec/path_relativizer_spec.rb +15 -0
- data/spec/presenters/coverage_detailed_presenter_spec.rb +1 -1
- data/spec/presenters/coverage_summary_presenter_spec.rb +1 -1
- data/spec/presenters/coverage_uncovered_presenter_spec.rb +1 -1
- data/spec/presenters/project_coverage_presenter_spec.rb +9 -8
- data/spec/presenters/project_totals_presenter_spec.rb +144 -0
- data/spec/resolvers/coverage_line_resolver_spec.rb +261 -36
- data/spec/resolvers/resultset_path_resolver_spec.rb +13 -8
- data/spec/shared_examples/file_based_mcp_tools.rb +23 -18
- data/spec/shared_examples/formatted_command_examples.rb +64 -0
- data/spec/simple_cov_mcp_module_spec.rb +24 -3
- data/spec/simplecov_mcp/formatters/source_formatter_spec.rb +267 -0
- data/spec/simplecov_mcp/formatters_spec.rb +76 -0
- data/spec/simplecov_mcp/presenters/base_coverage_presenter_spec.rb +79 -0
- data/spec/simplecov_mcp_model_spec.rb +97 -47
- data/spec/simplecov_mcp_opts_spec.rb +42 -39
- data/spec/spec_helper.rb +27 -92
- data/spec/staleness_checker_spec.rb +10 -9
- data/spec/staleness_more_spec.rb +4 -4
- data/spec/support/cli_helpers.rb +22 -0
- data/spec/support/control_flow_helpers.rb +20 -0
- data/spec/support/fake_mcp.rb +40 -0
- data/spec/support/io_helpers.rb +29 -0
- data/spec/support/mcp_helpers.rb +35 -0
- data/spec/support/mcp_runner.rb +10 -8
- data/spec/support/mocking_helpers.rb +30 -0
- data/spec/table_format_spec.rb +70 -0
- data/spec/tools/validate_tool_spec.rb +132 -0
- data/spec/tools_error_handling_spec.rb +34 -48
- data/spec/util_spec.rb +5 -4
- data/spec/version_spec.rb +7 -7
- data/spec/version_tool_spec.rb +20 -22
- metadata +90 -23
- data/docs/BRANCH_ONLY_COVERAGE.md +0 -81
- data/docs/CLI_USAGE.md +0 -637
- data/docs/EXAMPLES.md +0 -430
- data/docs/INSTALLATION.md +0 -352
- data/spec/cli_success_predicate_spec.rb +0 -141
|
@@ -10,18 +10,23 @@ module SimpleCovMcp
|
|
|
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
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/
|
|
14
|
-
Output: JSON object {"file": String, "summary": {"covered": Integer, "total": Integer, "
|
|
13
|
+
Inputs: file path (required) plus optional root/resultset/staleness mode inherited from BaseTool.
|
|
14
|
+
Output: JSON object {"file": String, "summary": {"covered": Integer, "total": Integer, "percentage": Float}, "stale": String|False}.
|
|
15
15
|
Examples: "What is the coverage for lib/simple_cov_mcp/tools/all_files_coverage_tool.rb?".
|
|
16
16
|
DESC
|
|
17
17
|
input_schema(**input_schema_def)
|
|
18
18
|
class << self
|
|
19
|
-
def call(path:, root: '.', resultset: nil,
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
|
25
30
|
end
|
|
26
31
|
end
|
|
27
32
|
end
|
|
@@ -14,75 +14,35 @@ module SimpleCovMcp
|
|
|
14
14
|
Output: text block containing the formatted coverage table with headers and percentages.
|
|
15
15
|
Example: "Show me the CLI coverage table sorted descending".
|
|
16
16
|
DESC
|
|
17
|
-
input_schema(
|
|
18
|
-
|
|
19
|
-
additionalProperties: false,
|
|
20
|
-
properties: {
|
|
21
|
-
root: {
|
|
22
|
-
type: 'string',
|
|
23
|
-
description: 'Project root used to resolve relative inputs.',
|
|
24
|
-
default: '.'
|
|
25
|
-
},
|
|
26
|
-
resultset: {
|
|
27
|
-
type: 'string',
|
|
28
|
-
description: 'Path to the SimpleCov .resultset.json file.'
|
|
29
|
-
},
|
|
17
|
+
input_schema(**coverage_schema(
|
|
18
|
+
additional_properties: {
|
|
30
19
|
sort_order: {
|
|
31
20
|
type: 'string',
|
|
32
21
|
description: 'Sort order for the printed coverage table (ascending or descending).',
|
|
33
22
|
default: 'ascending',
|
|
34
23
|
enum: ['ascending', 'descending']
|
|
35
24
|
},
|
|
36
|
-
|
|
37
|
-
type: 'string',
|
|
38
|
-
description: 'How to handle missing/outdated coverage data. ' \
|
|
39
|
-
"'off' skips checks; 'error' raises.",
|
|
40
|
-
enum: ['off', 'error'],
|
|
41
|
-
default: 'off'
|
|
42
|
-
},
|
|
43
|
-
tracked_globs: {
|
|
44
|
-
type: 'array',
|
|
45
|
-
description: 'Glob patterns for files that should exist in the coverage report ' \
|
|
46
|
-
'(helps flag new files).',
|
|
47
|
-
items: { type: 'string' }
|
|
48
|
-
},
|
|
49
|
-
error_mode: {
|
|
50
|
-
type: 'string',
|
|
51
|
-
description:
|
|
52
|
-
"Error handling mode: 'off' (silent), 'on' (log errors), 'trace' (verbose).",
|
|
53
|
-
enum: ['off', 'on', 'trace'],
|
|
54
|
-
default: 'on'
|
|
55
|
-
}
|
|
25
|
+
tracked_globs: TRACKED_GLOBS_PROPERTY
|
|
56
26
|
}
|
|
57
|
-
)
|
|
58
|
-
|
|
27
|
+
))
|
|
59
28
|
class << self
|
|
60
|
-
def call(root: '.', resultset: nil, sort_order: 'ascending',
|
|
61
|
-
tracked_globs: nil, error_mode: '
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
check_stale = (stale_sym == :error)
|
|
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
|
|
67
35
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
relativized,
|
|
79
|
-
sort_order: sort_order_sym,
|
|
80
|
-
check_stale: check_stale,
|
|
81
|
-
tracked_globs: nil # rows already filtered via all_files
|
|
82
|
-
)
|
|
83
|
-
::MCP::Tool::Response.new([{ type: 'text', text: table }])
|
|
84
|
-
rescue => e
|
|
85
|
-
handle_mcp_error(e, 'CoverageTableTool', error_mode: error_mode)
|
|
36
|
+
model = CoverageModel.new(root: root, resultset: resultset, staleness: staleness_sym,
|
|
37
|
+
tracked_globs: tracked_globs)
|
|
38
|
+
table = model.format_table(
|
|
39
|
+
sort_order: sort_order_sym,
|
|
40
|
+
check_stale: (staleness_sym == :error),
|
|
41
|
+
tracked_globs: tracked_globs
|
|
42
|
+
)
|
|
43
|
+
# Return text response
|
|
44
|
+
::MCP::Tool::Response.new([{ 'type' => 'text', 'text' => table }])
|
|
45
|
+
end
|
|
86
46
|
end
|
|
87
47
|
end
|
|
88
48
|
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../model'
|
|
4
|
+
require_relative '../base_tool'
|
|
5
|
+
require_relative '../presenters/project_totals_presenter'
|
|
6
|
+
|
|
7
|
+
module SimpleCovMcp
|
|
8
|
+
module Tools
|
|
9
|
+
class CoverageTotalsTool < BaseTool
|
|
10
|
+
description <<~DESC
|
|
11
|
+
Use this when you want aggregated coverage counts for the entire project.
|
|
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"}}.
|
|
15
|
+
Example: "Give me total/covered/uncovered line counts and the overall coverage percent."
|
|
16
|
+
DESC
|
|
17
|
+
|
|
18
|
+
input_schema(**coverage_schema(
|
|
19
|
+
additional_properties: {
|
|
20
|
+
tracked_globs: TRACKED_GLOBS_PROPERTY
|
|
21
|
+
}
|
|
22
|
+
))
|
|
23
|
+
|
|
24
|
+
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
|
|
30
|
+
|
|
31
|
+
model = CoverageModel.new(root: root, resultset: resultset, staleness: staleness_sym,
|
|
32
|
+
tracked_globs: tracked_globs)
|
|
33
|
+
presenter = Presenters::ProjectTotalsPresenter.new(
|
|
34
|
+
model: model,
|
|
35
|
+
check_stale: (staleness_sym == :error),
|
|
36
|
+
tracked_globs: tracked_globs
|
|
37
|
+
)
|
|
38
|
+
respond_json(presenter.relativized_payload, name: 'coverage_totals.json', pretty: true)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -6,30 +6,14 @@ module SimpleCovMcp
|
|
|
6
6
|
module Tools
|
|
7
7
|
class HelpTool < BaseTool
|
|
8
8
|
description <<~DESC
|
|
9
|
-
|
|
10
|
-
Do not use this once you know the correct tool; call that tool directly.
|
|
11
|
-
Inputs: optional query string to filter the list of tools.
|
|
12
|
-
Output: JSON {"tools": [...]} with per-tool "use_when", "avoid_when", "inputs",#{' '}
|
|
13
|
-
and "example" guidance.
|
|
14
|
-
Example: "Which tool shows uncovered lines?".
|
|
9
|
+
Returns help containing descriptions of all tools, including: use_when, avoid_when, inputs.
|
|
15
10
|
DESC
|
|
16
11
|
|
|
17
12
|
input_schema(
|
|
18
13
|
type: 'object',
|
|
19
14
|
additionalProperties: false,
|
|
20
15
|
properties: {
|
|
21
|
-
|
|
22
|
-
type: 'string',
|
|
23
|
-
description:
|
|
24
|
-
'Optional keywords to filter the help entries (e.g., "uncovered", "summary").'
|
|
25
|
-
},
|
|
26
|
-
error_mode: {
|
|
27
|
-
type: 'string',
|
|
28
|
-
description:
|
|
29
|
-
"Error handling mode: 'off' (silent), 'on' (log errors), 'trace' (verbose).",
|
|
30
|
-
enum: ['off', 'on', 'trace'],
|
|
31
|
-
default: 'on'
|
|
32
|
-
}
|
|
16
|
+
error_mode: ERROR_MODE_PROPERTY
|
|
33
17
|
}
|
|
34
18
|
)
|
|
35
19
|
|
|
@@ -39,104 +23,92 @@ module SimpleCovMcp
|
|
|
39
23
|
label: 'Single-file coverage summary',
|
|
40
24
|
use_when: 'User wants covered/total line counts or percentage for one file.',
|
|
41
25
|
avoid_when: 'User needs repo-wide stats or specific uncovered lines.',
|
|
42
|
-
inputs: ['path (required)', 'root/resultset/
|
|
43
|
-
example: 'What is the coverage for lib/simple_cov_mcp/model.rb?'
|
|
26
|
+
inputs: ['path (required)', 'root/resultset/staleness (optional)']
|
|
44
27
|
},
|
|
45
28
|
{
|
|
46
29
|
tool: UncoveredLinesTool,
|
|
47
30
|
label: 'Uncovered line numbers',
|
|
48
31
|
use_when: 'User asks which lines in a file still lack tests.',
|
|
49
32
|
avoid_when: 'User only wants overall percentages or detailed per-line hit data.',
|
|
50
|
-
inputs: ['path (required)', 'root/resultset/
|
|
51
|
-
example: 'List uncovered lines for lib/simple_cov_mcp/tools/coverage_summary_tool.rb.'
|
|
33
|
+
inputs: ['path (required)', 'root/resultset/staleness (optional)']
|
|
52
34
|
},
|
|
53
35
|
{
|
|
54
36
|
tool: CoverageDetailedTool,
|
|
55
37
|
label: 'Per-line coverage details',
|
|
56
38
|
use_when: 'User needs per-line hit counts for a file.',
|
|
57
39
|
avoid_when: 'User only wants totals or uncovered line numbers.',
|
|
58
|
-
inputs: ['path (required)', 'root/resultset/
|
|
59
|
-
example: 'Show detailed coverage for lib/simple_cov_mcp/util.rb.'
|
|
40
|
+
inputs: ['path (required)', 'root/resultset/staleness (optional)']
|
|
60
41
|
},
|
|
61
42
|
{
|
|
62
43
|
tool: CoverageRawTool,
|
|
63
44
|
label: 'Raw SimpleCov lines array',
|
|
64
45
|
use_when: 'User needs the raw SimpleCov `lines` array for a file.',
|
|
65
46
|
avoid_when: 'User expects human-friendly summaries or explanations.',
|
|
66
|
-
inputs: ['path (required)', 'root/resultset/
|
|
67
|
-
example: 'Fetch the raw coverage array for spec/support/helpers.rb.'
|
|
47
|
+
inputs: ['path (required)', 'root/resultset/staleness (optional)']
|
|
68
48
|
},
|
|
69
49
|
{
|
|
70
50
|
tool: AllFilesCoverageTool,
|
|
71
51
|
label: 'Repo-wide file coverage',
|
|
72
52
|
use_when: 'User wants coverage percentages for every tracked file.',
|
|
73
53
|
avoid_when: 'User asks about a single file.',
|
|
74
|
-
inputs: ['root/resultset (optional)', 'sort_order', '
|
|
75
|
-
|
|
54
|
+
inputs: ['root/resultset (optional)', 'sort_order', 'staleness', 'tracked_globs']
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
tool: CoverageTotalsTool,
|
|
58
|
+
label: 'Project coverage totals',
|
|
59
|
+
use_when: 'User wants total/covered/uncovered line counts or the average percent.',
|
|
60
|
+
avoid_when: 'User needs per-file breakdowns.',
|
|
61
|
+
inputs: ['root/resultset (optional)', 'staleness', 'tracked_globs']
|
|
76
62
|
},
|
|
77
63
|
{
|
|
78
64
|
tool: CoverageTableTool,
|
|
79
65
|
label: 'Formatted coverage table',
|
|
80
66
|
use_when: 'User wants the plain-text table produced by the CLI.',
|
|
81
67
|
avoid_when: 'User needs JSON data for automation.',
|
|
82
|
-
inputs: ['root/resultset (optional)', 'sort_order', '
|
|
83
|
-
|
|
68
|
+
inputs: ['root/resultset (optional)', 'sort_order', 'staleness']
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
tool: ValidateTool,
|
|
72
|
+
label: 'Validate coverage policy',
|
|
73
|
+
use_when: 'User needs to enforce coverage rules (e.g., minimum percentage) in CI.',
|
|
74
|
+
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)']
|
|
84
83
|
},
|
|
85
84
|
{
|
|
86
85
|
tool: VersionTool,
|
|
87
86
|
label: 'simplecov-mcp version',
|
|
88
87
|
use_when: 'User needs to confirm the running gem version.',
|
|
89
88
|
avoid_when: 'User is asking for coverage information.',
|
|
90
|
-
inputs: ['(no arguments)']
|
|
91
|
-
example: 'What version of simplecov-mcp is installed?'
|
|
89
|
+
inputs: ['(no arguments)']
|
|
92
90
|
}
|
|
93
91
|
].freeze
|
|
94
92
|
|
|
95
93
|
class << self
|
|
96
|
-
def call(
|
|
97
|
-
|
|
98
|
-
|
|
94
|
+
def call(error_mode: 'log', server_context:, **_unused)
|
|
95
|
+
with_error_handling('HelpTool', error_mode: error_mode) do
|
|
96
|
+
entries = TOOL_GUIDE.map { |guide| format_entry(guide) }
|
|
99
97
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
handle_mcp_error(e, 'HelpTool', error_mode: error_mode)
|
|
98
|
+
data = { tools: entries }
|
|
99
|
+
respond_json(data, name: 'tools_help.json')
|
|
100
|
+
end
|
|
104
101
|
end
|
|
105
102
|
|
|
106
|
-
private
|
|
107
|
-
|
|
108
|
-
def format_entry(guide)
|
|
103
|
+
private def format_entry(guide)
|
|
109
104
|
{
|
|
110
105
|
'tool' => guide[:tool].tool_name,
|
|
111
106
|
'label' => guide[:label],
|
|
112
107
|
'use_when' => guide[:use_when],
|
|
113
108
|
'avoid_when' => guide[:avoid_when],
|
|
114
|
-
'inputs' => guide[:inputs]
|
|
115
|
-
'example' => guide[:example]
|
|
109
|
+
'inputs' => guide[:inputs]
|
|
116
110
|
}
|
|
117
111
|
end
|
|
118
|
-
|
|
119
|
-
def filter_entries(entries, query)
|
|
120
|
-
tokens = query.downcase.scan(/\w+/)
|
|
121
|
-
return entries if tokens.empty?
|
|
122
|
-
|
|
123
|
-
entries.select do |entry|
|
|
124
|
-
tokens.all? do |token|
|
|
125
|
-
entry.any? { |_, value| value_matches?(value, token) }
|
|
126
|
-
end
|
|
127
|
-
end
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
def value_matches?(value, token)
|
|
131
|
-
case value
|
|
132
|
-
when String
|
|
133
|
-
value.downcase.include?(token)
|
|
134
|
-
when Array
|
|
135
|
-
value.any? { |element| element.downcase.include?(token) }
|
|
136
|
-
else
|
|
137
|
-
false
|
|
138
|
-
end
|
|
139
|
-
end
|
|
140
112
|
end
|
|
141
113
|
end
|
|
142
114
|
end
|
|
@@ -10,18 +10,23 @@ module SimpleCovMcp
|
|
|
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/
|
|
14
|
-
Output: JSON object with keys "file", "uncovered" (array of integers), "summary" {"covered","total","
|
|
13
|
+
Inputs: file path (required) plus optional root/resultset/staleness mode inherited from BaseTool.
|
|
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/simple_cov_mcp/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,
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
|
25
30
|
end
|
|
26
31
|
end
|
|
27
32
|
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../base_tool'
|
|
4
|
+
require_relative '../model'
|
|
5
|
+
require_relative '../predicate_evaluator'
|
|
6
|
+
|
|
7
|
+
module SimpleCovMcp
|
|
8
|
+
module Tools
|
|
9
|
+
class ValidateTool < BaseTool
|
|
10
|
+
description <<~DESC
|
|
11
|
+
Validates coverage data against a predicate (Ruby code that evaluates to true/false).
|
|
12
|
+
Use this to enforce coverage policies programmatically.
|
|
13
|
+
Inputs: Either 'code' (Ruby string) OR 'file' (path to Ruby file), plus optional root/resultset/staleness/error_mode.
|
|
14
|
+
Output: JSON object {"result": Boolean} where true means policy passed, false means failed.
|
|
15
|
+
On error (syntax error, file not found, etc.), returns an MCP error response.
|
|
16
|
+
Security Warning: Predicates execute as arbitrary Ruby code with full system privileges.
|
|
17
|
+
Examples:
|
|
18
|
+
- "Check if all files have at least 80% coverage" → {"code": "->(m) { m.all_files.all? { |f| f['percentage'] >= 80 } }"}
|
|
19
|
+
- "Run coverage policy from file" → {"file": "coverage_policy.rb"}
|
|
20
|
+
DESC
|
|
21
|
+
|
|
22
|
+
input_schema(**coverage_schema(
|
|
23
|
+
additional_properties: {
|
|
24
|
+
code: {
|
|
25
|
+
type: 'string',
|
|
26
|
+
description: 'Ruby code string that returns a callable predicate. ' \
|
|
27
|
+
'Must evaluate to a lambda, proc, or object with #call method.'
|
|
28
|
+
},
|
|
29
|
+
file: {
|
|
30
|
+
type: 'string',
|
|
31
|
+
description:
|
|
32
|
+
'Path to Ruby file containing predicate code (absolute or relative to root).'
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
))
|
|
36
|
+
class << self
|
|
37
|
+
def call(code: nil, file: nil, root: '.', resultset: nil, staleness: :off,
|
|
38
|
+
error_mode: 'log', server_context:)
|
|
39
|
+
with_error_handling('ValidateTool', error_mode: error_mode) do
|
|
40
|
+
# Re-use logic from ValidateCommand, but adapt for MCP return format
|
|
41
|
+
require_relative '../cli'
|
|
42
|
+
|
|
43
|
+
# Create a minimal CLI shim to reuse command logic
|
|
44
|
+
cli = CoverageCLI.new
|
|
45
|
+
cli.config.root = root
|
|
46
|
+
cli.config.resultset = resultset
|
|
47
|
+
cli.config.staleness = staleness.to_sym
|
|
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)
|
|
55
|
+
|
|
56
|
+
result = if code
|
|
57
|
+
PredicateEvaluator.evaluate_code(code, model)
|
|
58
|
+
elsif file
|
|
59
|
+
# Resolve file path relative to root if needed
|
|
60
|
+
predicate_path = File.expand_path(file, root)
|
|
61
|
+
PredicateEvaluator.evaluate_file(predicate_path, model)
|
|
62
|
+
else
|
|
63
|
+
raise UsageError, "Either 'code' or 'file' must be provided"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
respond_json({ result: result }, name: 'validate_result.json', pretty: true)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -15,23 +15,16 @@ module SimpleCovMcp
|
|
|
15
15
|
type: 'object',
|
|
16
16
|
additionalProperties: false,
|
|
17
17
|
properties: {
|
|
18
|
-
error_mode:
|
|
19
|
-
type: 'string',
|
|
20
|
-
description:
|
|
21
|
-
"Error handling mode: 'off' (silent), 'on' (log errors), 'trace' (verbose).",
|
|
22
|
-
enum: ['off', 'on', 'trace'],
|
|
23
|
-
default: 'on'
|
|
24
|
-
}
|
|
18
|
+
error_mode: ERROR_MODE_PROPERTY
|
|
25
19
|
}
|
|
26
20
|
)
|
|
27
|
-
|
|
28
21
|
class << self
|
|
29
|
-
def call(error_mode: '
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
22
|
+
def call(error_mode: 'log', server_context: nil, **_args)
|
|
23
|
+
with_error_handling('VersionTool', error_mode: error_mode) do
|
|
24
|
+
::MCP::Tool::Response.new([
|
|
25
|
+
{ 'type' => 'text', 'text' => "SimpleCovMcp version: #{SimpleCovMcp::VERSION}" }
|
|
26
|
+
])
|
|
27
|
+
end
|
|
35
28
|
end
|
|
36
29
|
end
|
|
37
30
|
end
|
data/lib/simplecov_mcp/util.rb
CHANGED
|
@@ -12,9 +12,7 @@ module SimpleCovMcp
|
|
|
12
12
|
DEFAULT_LOG_FILESPEC = './simplecov_mcp.log'
|
|
13
13
|
|
|
14
14
|
module CovUtil
|
|
15
|
-
module_function
|
|
16
|
-
|
|
17
|
-
def log(msg)
|
|
15
|
+
module_function def log(msg)
|
|
18
16
|
log_file = SimpleCovMcp.active_log_file
|
|
19
17
|
|
|
20
18
|
case log_file
|
|
@@ -27,39 +25,47 @@ module SimpleCovMcp
|
|
|
27
25
|
path_to_log = log_file || DEFAULT_LOG_FILESPEC
|
|
28
26
|
File.open(File.expand_path(path_to_log), 'a') { |f| f.puts "[#{Time.now.iso8601}] #{msg}" }
|
|
29
27
|
end
|
|
30
|
-
rescue
|
|
28
|
+
rescue => e
|
|
31
29
|
# Fallback to stderr if file logging fails, but suppress in MCP mode
|
|
32
30
|
# to avoid interfering with JSON-RPC protocol
|
|
33
31
|
unless SimpleCovMcp.context.mcp_mode?
|
|
34
32
|
begin
|
|
35
33
|
$stderr.puts "[#{Time.now.iso8601}] LOGGING ERROR: #{e.message}"
|
|
36
34
|
$stderr.puts "[#{Time.now.iso8601}] #{msg}"
|
|
37
|
-
rescue
|
|
35
|
+
rescue
|
|
38
36
|
# Silently ignore only stderr fallback failures
|
|
39
37
|
end
|
|
40
38
|
end
|
|
41
39
|
end
|
|
42
40
|
|
|
43
|
-
|
|
41
|
+
# Safe logging that never raises - use when logging should not interrupt execution.
|
|
42
|
+
# Unlike `log`, this method guarantees it will never propagate exceptions.
|
|
43
|
+
module_function def safe_log(msg)
|
|
44
|
+
log(msg)
|
|
45
|
+
rescue
|
|
46
|
+
# Silently ignore all logging failures
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
module_function def find_resultset(root, resultset: nil)
|
|
44
50
|
Resolvers::ResolverFactory.find_resultset(root, resultset: resultset)
|
|
45
51
|
end
|
|
46
52
|
|
|
47
|
-
def lookup_lines(cov, file_abs)
|
|
53
|
+
module_function def lookup_lines(cov, file_abs)
|
|
48
54
|
Resolvers::ResolverFactory.lookup_lines(cov, file_abs)
|
|
49
55
|
end
|
|
50
56
|
|
|
51
|
-
def summary(arr)
|
|
57
|
+
module_function def summary(arr)
|
|
52
58
|
total = 0
|
|
53
59
|
covered = 0
|
|
54
60
|
arr.compact.each do |hits|
|
|
55
61
|
total += 1
|
|
56
62
|
covered += 1 if hits.to_i > 0
|
|
57
63
|
end
|
|
58
|
-
|
|
59
|
-
{ 'covered' => covered, 'total' => total, '
|
|
64
|
+
percentage = total.zero? ? 100.0 : ((covered.to_f * 100.0 / total) * 100).round / 100.0
|
|
65
|
+
{ 'covered' => covered, 'total' => total, 'percentage' => percentage }
|
|
60
66
|
end
|
|
61
67
|
|
|
62
|
-
def uncovered(arr)
|
|
68
|
+
module_function def uncovered(arr)
|
|
63
69
|
out = []
|
|
64
70
|
|
|
65
71
|
arr.each_with_index do |hits, i|
|
|
@@ -70,7 +76,7 @@ module SimpleCovMcp
|
|
|
70
76
|
out
|
|
71
77
|
end
|
|
72
78
|
|
|
73
|
-
def detailed(arr)
|
|
79
|
+
module_function def detailed(arr)
|
|
74
80
|
rows = []
|
|
75
81
|
arr.each_with_index do |hits, i|
|
|
76
82
|
h = hits&.to_i
|