simplecov-ai 0.10.0
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 +7 -0
- checksums.yaml.gz.sig +0 -0
- data/LICENSE.txt +21 -0
- data/README.md +1 -0
- data/certs/simplecov-ai-public_cert.pem +25 -0
- data/lib/simplecov-ai/ast_resolver.rb +140 -0
- data/lib/simplecov-ai/configuration.rb +57 -0
- data/lib/simplecov-ai/markdown_builder.rb +155 -0
- data/lib/simplecov-ai/version.rb +14 -0
- data/lib/simplecov-ai.rb +64 -0
- data.tar.gz.sig +0 -0
- metadata +303 -0
- metadata.gz.sig +0 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 03eef8493da2d38018d6be59cde9dccf4cf0811cd916183fc99a3af4b194bef9
|
|
4
|
+
data.tar.gz: fe85e118ff57bf66a74a6bd9f27ee2f778b0f3a77d745e38dce36909a2cfb637
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 2ef39fea3f09836adb1b9ace8cc556578b0b301a9704168328193880370942d93d19e0ad527ed2f062ab67d669d2814a92fd9df421db4d8d5589a5a5b4b29eee
|
|
7
|
+
data.tar.gz: 6690600cd0625a0f3ed9df1b7a6aa65018b1b5bba9fd89f7e1eeb4f7db316110f6201f537bad18cc7adccde819b2d88cd6a3cde5fbd0ddf0f85f0a861d98edc2
|
checksums.yaml.gz.sig
ADDED
|
Binary file
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Vitalii Lazebnyi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# simplecov-ai
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
|
2
|
+
MIIEOTCCAqGgAwIBAgIUBONmsFo7fxLGkUHsKe65onH+5ogwDQYJKoZIhvcNAQEL
|
|
3
|
+
BQAwLDEqMCgGA1UEAwwhdml0YWxpaS5sYXplYm55aS5naXRodWJAZ21haWwuY29t
|
|
4
|
+
MB4XDTI2MDQxNTEzNTMyOVoXDTM2MDQxMjEzNTMyOVowLDEqMCgGA1UEAwwhdml0
|
|
5
|
+
YWxpaS5sYXplYm55aS5naXRodWJAZ21haWwuY29tMIIBojANBgkqhkiG9w0BAQEF
|
|
6
|
+
AAOCAY8AMIIBigKCAYEA5zdezJE+Zrsk9j53/IxBfRoaqvLcPvrcfl+EaEwWhIkV
|
|
7
|
+
0+08GtgS9N7VpB8cgaH2rkLJPjHIetsN/g5GMkDRsbJNXMrPVhxe1e1lI/r6j0Tm
|
|
8
|
+
JD0PaU4r8VzitxkqY9BBmSI8GjDjAfrT1u5jSXH1iAtKUoq5F116uYrxbgiDpvqa
|
|
9
|
+
kUQYcTf+6cZaPlF4KKhULnhKqs8u/NxyH4vPZyxEfg/gA4bODvcjW1A6d59BTiLV
|
|
10
|
+
yrJPebwU+F+URb8aoQ4AGvPKFiG1Y1fxRHuPrOpyymFnBnjwgMyQkNHtzTeEriV9
|
|
11
|
+
z1BUb10Pb/pjLBCrOvnStTPmcm1GE8HL2psYvlLvBlYqq3gzpQPBBKE3Jefa7ilC
|
|
12
|
+
cYsBYOGpynpA9uu9cXKa4jtpPDGQ7Qrpnk9gHy/0xfbgLdAkRCoZJeR7wDL/1xmm
|
|
13
|
+
nXwcUOLSOBj1Y4P9M+uQSQUZFTAaLbwyaBfE1gvVjwbTv3+rNP1ck1hACt+numGG
|
|
14
|
+
m7R6MF+Hmh8pNnDBYpBNAgMBAAGjUzBRMB0GA1UdDgQWBBRbuaz1EhdG6T4KIeWr
|
|
15
|
+
ac8LULxO9zAfBgNVHSMEGDAWgBRbuaz1EhdG6T4KIeWrac8LULxO9zAPBgNVHRMB
|
|
16
|
+
Af8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBgQBgfGTDIMxlm6o8o7dzCR0HosRm
|
|
17
|
+
DSeUrx46EG1knTEqO05CooEHW98hrHa1/EwzkPaH1KhjjserQb6VtczMnySlfySu
|
|
18
|
+
HbKWAIaqzlpf8zaE5tCiAKgFKr77b2XB7xKt25p/Vf/Kn/RLm3+sYQ2izzzMimei
|
|
19
|
+
tBHo29cLV9bB/5HHFDwjrtdC5a0HJHiir0w4MCSDDGtnsKird4RKD2xESpoVjiNg
|
|
20
|
+
L9nEGk25YDeIfKn8UtxduMv53T86CiBSsDcEb6oVjNiMOA0HFucwFKX+Vy5u0/qx
|
|
21
|
+
ZRoLbZiCkTTGyNkBh4o6RCCTn37Lj98FBxYMbAHLNhEcKnAGxB7XP/CYsV4+QHOy
|
|
22
|
+
h0PctylhIvm24QeKgIWJUWamFPfqdvlP660T4umxl2wMqvNpWmGMmGTMCraoKwxl
|
|
23
|
+
zpp6uA15MXgTU7CxGivRgUKM64TqBMZKkOJcCtPkruSobxiR8cROrBNTqEbrmedM
|
|
24
|
+
26EUEoxwDzfSzHU2SKz5pMR+8DClMUKB1rctg68=
|
|
25
|
+
-----END CERTIFICATE-----
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'parser/current'
|
|
5
|
+
|
|
6
|
+
module SimpleCov
|
|
7
|
+
module Formatter
|
|
8
|
+
class AIFormatter
|
|
9
|
+
# Employs statically-parsed Abstract Syntax Tree processing via the `parser` gem
|
|
10
|
+
# to correlate raw line-based deficits with high-level semantically meaningful concepts
|
|
11
|
+
# like Classes and Methods. This negates the line-number volatility often experienced
|
|
12
|
+
# by Large Language Models when patching test coverage.
|
|
13
|
+
class ASTResolver
|
|
14
|
+
extend T::Sig
|
|
15
|
+
|
|
16
|
+
# An immutable struct housing bounds, identification metrics, and static bypassing
|
|
17
|
+
# definitions derived from traversing the AST nodes.
|
|
18
|
+
class SemanticNode
|
|
19
|
+
extend T::Sig
|
|
20
|
+
|
|
21
|
+
sig { returns(String) }
|
|
22
|
+
attr_reader :name, :type
|
|
23
|
+
|
|
24
|
+
sig { returns(Integer) }
|
|
25
|
+
attr_reader :start_line, :end_line
|
|
26
|
+
|
|
27
|
+
sig { returns(T::Array[String]) }
|
|
28
|
+
attr_reader :bypasses
|
|
29
|
+
|
|
30
|
+
sig do
|
|
31
|
+
params(
|
|
32
|
+
name: String,
|
|
33
|
+
type: String,
|
|
34
|
+
start_line: Integer,
|
|
35
|
+
end_line: Integer,
|
|
36
|
+
bypasses: T::Array[String]
|
|
37
|
+
).void
|
|
38
|
+
end
|
|
39
|
+
def initialize(name:, type:, start_line:, end_line:, bypasses: [])
|
|
40
|
+
@name = name
|
|
41
|
+
@type = type
|
|
42
|
+
@start_line = start_line
|
|
43
|
+
@end_line = end_line
|
|
44
|
+
@bypasses = bypasses
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Orchestrates the initial mapping algorithm on a target file to extract structural
|
|
49
|
+
# metadata, circumventing potential syntax violations explicitly.
|
|
50
|
+
#
|
|
51
|
+
# @param file_path [String] The absolute path to the Ruby script to parse.
|
|
52
|
+
# @return [Array<SemanticNode>] A collection of resolvable structural entities.
|
|
53
|
+
sig { params(file_path: String).returns(T::Array[SemanticNode]) }
|
|
54
|
+
def self.resolve(file_path)
|
|
55
|
+
return [] unless File.exist?(file_path)
|
|
56
|
+
|
|
57
|
+
begin
|
|
58
|
+
source = File.read(file_path)
|
|
59
|
+
ast, comments = Parser::CurrentRuby.parse_with_comments(source)
|
|
60
|
+
new.traverse(ast, comments)
|
|
61
|
+
rescue Parser::SyntaxError
|
|
62
|
+
[]
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Recursively navigates an abstract node hierarchy, building SemanticNodes mappings
|
|
67
|
+
# around modules, classes, singleton, and instance methods while aggregating parent paths.
|
|
68
|
+
#
|
|
69
|
+
# @param node [Parser::AST::Node] The root AST node from which traversal executes.
|
|
70
|
+
# @param comments [Array<Parser::Source::Comment>] Lexical comments corresponding to nodes.
|
|
71
|
+
# @param context [String] An accumulated identifier linking namespaces to inner entities.
|
|
72
|
+
# @return [Array<SemanticNode>] Accumulation of all sub-tree defined endpoints.
|
|
73
|
+
sig do
|
|
74
|
+
params(node: Parser::AST::Node, comments: T::Array[Parser::Source::Comment],
|
|
75
|
+
context: String).returns(T::Array[SemanticNode])
|
|
76
|
+
end
|
|
77
|
+
def traverse(node, comments, context = '')
|
|
78
|
+
return [] unless node.is_a?(Parser::AST::Node)
|
|
79
|
+
|
|
80
|
+
nodes = T.let([], T::Array[SemanticNode])
|
|
81
|
+
current_context = context
|
|
82
|
+
|
|
83
|
+
case node.type
|
|
84
|
+
when :class, :module
|
|
85
|
+
const_node = T.cast(node.children[0], Parser::AST::Node)
|
|
86
|
+
const_node_loc = T.cast(const_node.loc, Parser::Source::Map::Constant)
|
|
87
|
+
const_node_name = T.cast(const_node_loc.name, Parser::Source::Range)
|
|
88
|
+
name = T.cast(const_node_name.source, String)
|
|
89
|
+
current_context = context.empty? ? name : "#{context}::#{name}"
|
|
90
|
+
nodes << build_node(node, comments, current_context, node.type.to_s.capitalize)
|
|
91
|
+
when :def
|
|
92
|
+
name = T.cast(node.children.first, Symbol).to_s
|
|
93
|
+
current_context = context.empty? ? "##{name}" : "#{context}##{name}"
|
|
94
|
+
nodes << build_node(node, comments, current_context, 'Instance Method')
|
|
95
|
+
when :defs
|
|
96
|
+
name = T.cast(node.children[1], Symbol).to_s
|
|
97
|
+
current_context = context.empty? ? ".#{name}" : "#{context}.#{name}"
|
|
98
|
+
nodes << build_node(node, comments, current_context, 'Singleton Method')
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
node.children.each do |child|
|
|
102
|
+
case child
|
|
103
|
+
when Parser::AST::Node
|
|
104
|
+
nodes.concat(traverse(child, comments, current_context))
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
nodes
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
private
|
|
112
|
+
|
|
113
|
+
sig do
|
|
114
|
+
params(node: Parser::AST::Node, comments: T::Array[Parser::Source::Comment], name: String,
|
|
115
|
+
type: String).returns(SemanticNode)
|
|
116
|
+
end
|
|
117
|
+
def build_node(node, comments, name, type)
|
|
118
|
+
node_loc = T.cast(node.loc, Parser::Source::Map)
|
|
119
|
+
node_line = T.cast(node_loc.line, Integer)
|
|
120
|
+
node_last_line = T.cast(node_loc.last_line, Integer)
|
|
121
|
+
|
|
122
|
+
bypasses = comments.select do |c|
|
|
123
|
+
c_loc = T.cast(c.loc, Parser::Source::Map)
|
|
124
|
+
c_line = T.cast(c_loc.line, Integer)
|
|
125
|
+
c_text = T.cast(c.text, String)
|
|
126
|
+
c_line >= node_line - 1 && c_line <= node_last_line + 1 && c_text.include?(':nocov:')
|
|
127
|
+
end.map { |c| T.cast(c.text, String).strip }
|
|
128
|
+
|
|
129
|
+
SemanticNode.new(
|
|
130
|
+
name: name,
|
|
131
|
+
type: type,
|
|
132
|
+
start_line: node_line,
|
|
133
|
+
end_line: node_last_line,
|
|
134
|
+
bypasses: bypasses
|
|
135
|
+
)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module SimpleCov
|
|
5
|
+
module Formatter
|
|
6
|
+
class AIFormatter
|
|
7
|
+
# Encapsulates all global tuning parameters that dictate the execution size,
|
|
8
|
+
# structure, and output verbosity of the AST-driven Markdown report generator.
|
|
9
|
+
# Exposes strongly-typed attributes through Sorbet to preempt runtime invalidities.
|
|
10
|
+
class Configuration
|
|
11
|
+
extend T::Sig
|
|
12
|
+
|
|
13
|
+
# The absolute or relative system path where the final token-efficient markdown
|
|
14
|
+
# document acts as an artifact.
|
|
15
|
+
# @return [String]
|
|
16
|
+
sig { returns(String) }
|
|
17
|
+
attr_accessor :report_path
|
|
18
|
+
|
|
19
|
+
# The maximum allowed byte limit to prevent the generation pipeline from overflowing
|
|
20
|
+
# LLM token bounds before terminating the traversal algorithm.
|
|
21
|
+
# @return [Integer]
|
|
22
|
+
sig { returns(Integer) }
|
|
23
|
+
attr_accessor :max_file_size_kb
|
|
24
|
+
|
|
25
|
+
# Limits the number of lines included in code snippets to conserve token usage
|
|
26
|
+
# while maintaining enough structural context for the AI to reason about the logic.
|
|
27
|
+
sig { returns(Integer) }
|
|
28
|
+
attr_accessor :max_snippet_lines
|
|
29
|
+
|
|
30
|
+
# Determines whether the generated markdown report is printed directly to standard output,
|
|
31
|
+
# facilitating pipeline integrations where artifacts are piped rather than read from disk.
|
|
32
|
+
sig { returns(T::Boolean) }
|
|
33
|
+
attr_accessor :output_to_console
|
|
34
|
+
|
|
35
|
+
# Specifies the level of detail in the coverage report (e.g., :fine, :coarse)
|
|
36
|
+
# to balance between comprehensive reporting and strict token constraints.
|
|
37
|
+
sig { returns(Symbol) }
|
|
38
|
+
attr_accessor :granularity
|
|
39
|
+
|
|
40
|
+
# Controls whether to include lines skipped via coverage bypass directives (e.g., :nocov:),
|
|
41
|
+
# allowing the AI to audit skipped regions for potential testing mandate violations.
|
|
42
|
+
sig { returns(T::Boolean) }
|
|
43
|
+
attr_accessor :include_bypasses
|
|
44
|
+
|
|
45
|
+
sig { void }
|
|
46
|
+
def initialize
|
|
47
|
+
@report_path = T.let('coverage/ai_report.md', String)
|
|
48
|
+
@max_file_size_kb = T.let(50, Integer)
|
|
49
|
+
@max_snippet_lines = T.let(5, Integer)
|
|
50
|
+
@output_to_console = T.let(false, T::Boolean)
|
|
51
|
+
@granularity = T.let(:fine, Symbol)
|
|
52
|
+
@include_bypasses = T.let(true, T::Boolean)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
module SimpleCov
|
|
5
|
+
module Formatter
|
|
6
|
+
class AIFormatter
|
|
7
|
+
# Responsible for compiling static text representations from evaluated coverage metrics,
|
|
8
|
+
# optimizing layout size, orchestrating string IO buffers, and halting upon token exhaustion.
|
|
9
|
+
# Serves as the primary mutation boundary to format AI consumption targets.
|
|
10
|
+
class MarkdownBuilder
|
|
11
|
+
extend T::Sig
|
|
12
|
+
|
|
13
|
+
# Initializes the Markdown sequence compilation.
|
|
14
|
+
#
|
|
15
|
+
# @param result [SimpleCov::Result] Application-wide coverage aggregation metrics
|
|
16
|
+
# @param config [Configuration] Pre-registered runtime toggles
|
|
17
|
+
sig { params(result: SimpleCov::Result, config: Configuration).void }
|
|
18
|
+
def initialize(result, config)
|
|
19
|
+
@result = T.let(result, SimpleCov::Result)
|
|
20
|
+
@config = T.let(config, Configuration)
|
|
21
|
+
@buffer = T.let(StringIO.new, StringIO)
|
|
22
|
+
@file_count = T.let(0, Integer)
|
|
23
|
+
@truncated = T.let(false, T::Boolean)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Executes the primary buffer composition logic yielding a monolithic compiled output.
|
|
27
|
+
# Deficits are intrinsically sorted to surface the most crucial test gaps immediately.
|
|
28
|
+
#
|
|
29
|
+
# @return [String] Synthesized string digest of resolved target files and metrics
|
|
30
|
+
sig { returns(String) }
|
|
31
|
+
def build
|
|
32
|
+
write_header
|
|
33
|
+
write_deficits
|
|
34
|
+
write_bypasses if @config.include_bypasses
|
|
35
|
+
write_truncation_warning if @truncated
|
|
36
|
+
@buffer.string
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
sig { void }
|
|
42
|
+
def write_header
|
|
43
|
+
status = T.cast(@result.covered_percent, Float) >= 100.0 ? 'PASSED' : 'FAILED'
|
|
44
|
+
time_str = Time.now.to_s # UI timezone requirement
|
|
45
|
+
|
|
46
|
+
@buffer.puts '# AI Coverage Digest'
|
|
47
|
+
@buffer.puts "**Status:** #{status}"
|
|
48
|
+
@buffer.puts "**Global Line Coverage:** #{T.cast(@result.covered_percent, Float).round(1)}%"
|
|
49
|
+
|
|
50
|
+
branch_pct = begin
|
|
51
|
+
T.cast(@result.covered_branches, Float) / T.cast(@result.total_branches, Numeric) * 100
|
|
52
|
+
rescue StandardError
|
|
53
|
+
0.0
|
|
54
|
+
end
|
|
55
|
+
@buffer.puts "**Global Branch Coverage:** #{T.cast(branch_pct, Float).round(1)}%"
|
|
56
|
+
@buffer.puts "**Generated At:** #{time_str}"
|
|
57
|
+
@buffer.puts ''
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
sig { void }
|
|
61
|
+
def write_deficits
|
|
62
|
+
@buffer.puts "## Coverage Deficits\n\n"
|
|
63
|
+
|
|
64
|
+
# SCMD-REQ-014: Sort by coverage percent ASC, then by filename
|
|
65
|
+
files = T.let(
|
|
66
|
+
T.cast(@result.files, T::Array[SimpleCov::SourceFile]).reject { |f| T.cast(f.covered_percent, Float) >= 100.0 }
|
|
67
|
+
.sort_by { |f| [T.cast(f.covered_percent, Float), T.cast(f.filename, String)] },
|
|
68
|
+
T::Array[SimpleCov::SourceFile]
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
files.each do |file|
|
|
72
|
+
# Check size limits SCMD-REQ-012
|
|
73
|
+
if @buffer.size / 1024.0 > @config.max_file_size_kb
|
|
74
|
+
@truncated = true
|
|
75
|
+
break
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
@buffer.puts "### `#{T.cast(file.project_filename, String)}`"
|
|
79
|
+
|
|
80
|
+
begin
|
|
81
|
+
nodes = ASTResolver.resolve(T.cast(file.filename, String))
|
|
82
|
+
rescue StandardError => e
|
|
83
|
+
@buffer.puts "- **ERROR:** AST Parsing Failed (`#{e.class}`)"
|
|
84
|
+
next
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
process_deficits(file, nodes)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
sig { params(file: SimpleCov::SourceFile, nodes: T::Array[ASTResolver::SemanticNode]).void }
|
|
92
|
+
def process_deficits(file, nodes)
|
|
93
|
+
T.cast(file.missed_lines, T::Array[SimpleCov::SourceFile::Line]).each do |line|
|
|
94
|
+
line_num = T.cast(line.line_number, Integer)
|
|
95
|
+
node = nodes.find { |n| line_num >= n.start_line && line_num <= n.end_line }
|
|
96
|
+
node_name = node ? node.name : "Line #{line_num}"
|
|
97
|
+
@buffer.puts "- `#{node_name}`\n - **Line Deficit:** Unexecuted code."
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
if file.respond_to?(:branches) && T.cast(file.branches, T::Boolean)
|
|
101
|
+
T.cast(file.missed_branches, T::Array[SimpleCov::SourceFile::Branch]).each do |branch|
|
|
102
|
+
start_line = T.cast(branch.start_line, Integer)
|
|
103
|
+
end_line = T.cast(branch.end_line, Integer)
|
|
104
|
+
node = nodes.find { |n| start_line >= n.start_line && end_line <= n.end_line }
|
|
105
|
+
node_name = node ? node.name : "Lines #{start_line}-#{end_line}"
|
|
106
|
+
@buffer.puts "- `#{node_name}`\n - **Branch Deficit:** Missing coverage for conditional."
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
@buffer.puts ''
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
sig { void }
|
|
114
|
+
def write_bypasses
|
|
115
|
+
has_bypasses = T.let(false, T::Boolean)
|
|
116
|
+
bypass_buffer = T.let(StringIO.new, StringIO)
|
|
117
|
+
|
|
118
|
+
T.cast(@result.files, T::Array[SimpleCov::SourceFile]).each do |file|
|
|
119
|
+
begin
|
|
120
|
+
nodes = ASTResolver.resolve(T.cast(file.filename, String))
|
|
121
|
+
rescue StandardError
|
|
122
|
+
next
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
nodes_with_bypasses = nodes.select { |n| n.bypasses.any? }
|
|
126
|
+
next if nodes_with_bypasses.empty?
|
|
127
|
+
|
|
128
|
+
has_bypasses = true
|
|
129
|
+
write_file_bypasses(bypass_buffer, file, nodes_with_bypasses)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
return unless has_bypasses
|
|
133
|
+
|
|
134
|
+
@buffer.puts "## Ignored Coverage Bypasses\n\n"
|
|
135
|
+
@buffer.puts bypass_buffer.string
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
sig { params(buffer: StringIO, file: SimpleCov::SourceFile, bypasses: T::Array[ASTResolver::SemanticNode]).void }
|
|
139
|
+
def write_file_bypasses(buffer, file, bypasses)
|
|
140
|
+
buffer.puts "### `#{file.project_filename}`"
|
|
141
|
+
bypasses.each do |node|
|
|
142
|
+
buffer.puts "- `#{node.name}`\n - **Bypass Present:** Contains `# :nocov:` directive artificially ignoring coverage."
|
|
143
|
+
end
|
|
144
|
+
buffer.puts ''
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
sig { void }
|
|
148
|
+
def write_truncation_warning
|
|
149
|
+
@buffer.puts '> **[WARNING] TRUNCATION NOTIFICATION:**'
|
|
150
|
+
@buffer.puts "> The total coverage deficit report exceeded the maximum token constraint (#{@config.max_file_size_kb} kB). The report was truncated. The deficits detailed above represent the lowest-coverage (most critical) files. Please resolve these deficits to reveal the remaining uncovered files in subsequent test runs."
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'sorbet-runtime'
|
|
5
|
+
|
|
6
|
+
module SimpleCov
|
|
7
|
+
module Formatter
|
|
8
|
+
class AIFormatter
|
|
9
|
+
# The semantic version identifier for the gem, used for dependency resolution
|
|
10
|
+
# and enforcing compatibility across upgrades.
|
|
11
|
+
VERSION = T.let('0.10.0', String)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
data/lib/simplecov-ai.rb
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'sorbet-runtime'
|
|
5
|
+
require 'simplecov'
|
|
6
|
+
require 'parser/current'
|
|
7
|
+
|
|
8
|
+
require_relative 'simplecov-ai/version'
|
|
9
|
+
require_relative 'simplecov-ai/configuration'
|
|
10
|
+
require_relative 'simplecov-ai/ast_resolver'
|
|
11
|
+
require_relative 'simplecov-ai/markdown_builder'
|
|
12
|
+
|
|
13
|
+
# The root namespace for the simplecov test coverage library.
|
|
14
|
+
module SimpleCov
|
|
15
|
+
# Namespace containing all reporting formatters.
|
|
16
|
+
module Formatter
|
|
17
|
+
# Transforms raw simplecov coverage results into an AI-optimized markdown digest.
|
|
18
|
+
# It addresses the context window limitations of LLMs by emitting token-efficient
|
|
19
|
+
# reports and relying on AST-based semantic mapping to pinpoint exact methods
|
|
20
|
+
# or classes with deficient coverage.
|
|
21
|
+
class AIFormatter
|
|
22
|
+
extend T::Sig
|
|
23
|
+
|
|
24
|
+
# Retrieves the global configuration for the AI formatter.
|
|
25
|
+
# The instantiation pattern ensures that defaults are securely lazily loaded
|
|
26
|
+
# before any coverage processing begins.
|
|
27
|
+
#
|
|
28
|
+
# @return [SimpleCov::Formatter::AIFormatter::Configuration] The active configuration state.
|
|
29
|
+
sig { returns(SimpleCov::Formatter::AIFormatter::Configuration) }
|
|
30
|
+
def self.configuration
|
|
31
|
+
@configuration ||= T.let(Configuration.new, T.nilable(Configuration))
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Yields the configuration object to a block, allowing consumers to override
|
|
35
|
+
# default formatting behaviors, output paths, and token constraints before execution.
|
|
36
|
+
#
|
|
37
|
+
# @yieldparam config [Configuration] The global formatter configuration.
|
|
38
|
+
# @return [void]
|
|
39
|
+
sig { params(blk: T.nilable(T.proc.params(config: Configuration).void)).void }
|
|
40
|
+
def self.configure(&blk)
|
|
41
|
+
blk&.call(configuration)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Orchestrates the formatting lifecycle to synthesize the markdown output.
|
|
45
|
+
# It converts the raw result data using the internal MarkdownBuilder, enforces
|
|
46
|
+
# directory structures, and securely persists the result to disk to guarantee
|
|
47
|
+
# an idempotent and strictly defined output location.
|
|
48
|
+
#
|
|
49
|
+
# @param result [SimpleCov::Result] The test coverage outcome generated by SimpleCov.
|
|
50
|
+
# @return [void]
|
|
51
|
+
sig { params(result: SimpleCov::Result).void }
|
|
52
|
+
def format(result)
|
|
53
|
+
config = self.class.configuration
|
|
54
|
+
builder = MarkdownBuilder.new(result, config)
|
|
55
|
+
digest = builder.build
|
|
56
|
+
|
|
57
|
+
FileUtils.mkdir_p(File.dirname(config.report_path))
|
|
58
|
+
File.write(config.report_path, digest)
|
|
59
|
+
|
|
60
|
+
puts "\n[SimpleCov AI Formatter] Digest written to #{config.report_path}" if config.output_to_console
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
data.tar.gz.sig
ADDED
|
Binary file
|
metadata
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: simplecov-ai
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.10.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Vitalii Lazebnyi
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain:
|
|
10
|
+
- |
|
|
11
|
+
-----BEGIN CERTIFICATE-----
|
|
12
|
+
MIIEOTCCAqGgAwIBAgIUBONmsFo7fxLGkUHsKe65onH+5ogwDQYJKoZIhvcNAQEL
|
|
13
|
+
BQAwLDEqMCgGA1UEAwwhdml0YWxpaS5sYXplYm55aS5naXRodWJAZ21haWwuY29t
|
|
14
|
+
MB4XDTI2MDQxNTEzNTMyOVoXDTM2MDQxMjEzNTMyOVowLDEqMCgGA1UEAwwhdml0
|
|
15
|
+
YWxpaS5sYXplYm55aS5naXRodWJAZ21haWwuY29tMIIBojANBgkqhkiG9w0BAQEF
|
|
16
|
+
AAOCAY8AMIIBigKCAYEA5zdezJE+Zrsk9j53/IxBfRoaqvLcPvrcfl+EaEwWhIkV
|
|
17
|
+
0+08GtgS9N7VpB8cgaH2rkLJPjHIetsN/g5GMkDRsbJNXMrPVhxe1e1lI/r6j0Tm
|
|
18
|
+
JD0PaU4r8VzitxkqY9BBmSI8GjDjAfrT1u5jSXH1iAtKUoq5F116uYrxbgiDpvqa
|
|
19
|
+
kUQYcTf+6cZaPlF4KKhULnhKqs8u/NxyH4vPZyxEfg/gA4bODvcjW1A6d59BTiLV
|
|
20
|
+
yrJPebwU+F+URb8aoQ4AGvPKFiG1Y1fxRHuPrOpyymFnBnjwgMyQkNHtzTeEriV9
|
|
21
|
+
z1BUb10Pb/pjLBCrOvnStTPmcm1GE8HL2psYvlLvBlYqq3gzpQPBBKE3Jefa7ilC
|
|
22
|
+
cYsBYOGpynpA9uu9cXKa4jtpPDGQ7Qrpnk9gHy/0xfbgLdAkRCoZJeR7wDL/1xmm
|
|
23
|
+
nXwcUOLSOBj1Y4P9M+uQSQUZFTAaLbwyaBfE1gvVjwbTv3+rNP1ck1hACt+numGG
|
|
24
|
+
m7R6MF+Hmh8pNnDBYpBNAgMBAAGjUzBRMB0GA1UdDgQWBBRbuaz1EhdG6T4KIeWr
|
|
25
|
+
ac8LULxO9zAfBgNVHSMEGDAWgBRbuaz1EhdG6T4KIeWrac8LULxO9zAPBgNVHRMB
|
|
26
|
+
Af8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBgQBgfGTDIMxlm6o8o7dzCR0HosRm
|
|
27
|
+
DSeUrx46EG1knTEqO05CooEHW98hrHa1/EwzkPaH1KhjjserQb6VtczMnySlfySu
|
|
28
|
+
HbKWAIaqzlpf8zaE5tCiAKgFKr77b2XB7xKt25p/Vf/Kn/RLm3+sYQ2izzzMimei
|
|
29
|
+
tBHo29cLV9bB/5HHFDwjrtdC5a0HJHiir0w4MCSDDGtnsKird4RKD2xESpoVjiNg
|
|
30
|
+
L9nEGk25YDeIfKn8UtxduMv53T86CiBSsDcEb6oVjNiMOA0HFucwFKX+Vy5u0/qx
|
|
31
|
+
ZRoLbZiCkTTGyNkBh4o6RCCTn37Lj98FBxYMbAHLNhEcKnAGxB7XP/CYsV4+QHOy
|
|
32
|
+
h0PctylhIvm24QeKgIWJUWamFPfqdvlP660T4umxl2wMqvNpWmGMmGTMCraoKwxl
|
|
33
|
+
zpp6uA15MXgTU7CxGivRgUKM64TqBMZKkOJcCtPkruSobxiR8cROrBNTqEbrmedM
|
|
34
|
+
26EUEoxwDzfSzHU2SKz5pMR+8DClMUKB1rctg68=
|
|
35
|
+
-----END CERTIFICATE-----
|
|
36
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
37
|
+
dependencies:
|
|
38
|
+
- !ruby/object:Gem::Dependency
|
|
39
|
+
name: parser
|
|
40
|
+
requirement: !ruby/object:Gem::Requirement
|
|
41
|
+
requirements:
|
|
42
|
+
- - ">="
|
|
43
|
+
- !ruby/object:Gem::Version
|
|
44
|
+
version: 3.1.0
|
|
45
|
+
type: :runtime
|
|
46
|
+
prerelease: false
|
|
47
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
48
|
+
requirements:
|
|
49
|
+
- - ">="
|
|
50
|
+
- !ruby/object:Gem::Version
|
|
51
|
+
version: 3.1.0
|
|
52
|
+
- !ruby/object:Gem::Dependency
|
|
53
|
+
name: sorbet-runtime
|
|
54
|
+
requirement: !ruby/object:Gem::Requirement
|
|
55
|
+
requirements:
|
|
56
|
+
- - "~>"
|
|
57
|
+
- !ruby/object:Gem::Version
|
|
58
|
+
version: '0.5'
|
|
59
|
+
type: :runtime
|
|
60
|
+
prerelease: false
|
|
61
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
62
|
+
requirements:
|
|
63
|
+
- - "~>"
|
|
64
|
+
- !ruby/object:Gem::Version
|
|
65
|
+
version: '0.5'
|
|
66
|
+
- !ruby/object:Gem::Dependency
|
|
67
|
+
name: simplecov
|
|
68
|
+
requirement: !ruby/object:Gem::Requirement
|
|
69
|
+
requirements:
|
|
70
|
+
- - ">="
|
|
71
|
+
- !ruby/object:Gem::Version
|
|
72
|
+
version: 0.18.0
|
|
73
|
+
type: :runtime
|
|
74
|
+
prerelease: false
|
|
75
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
76
|
+
requirements:
|
|
77
|
+
- - ">="
|
|
78
|
+
- !ruby/object:Gem::Version
|
|
79
|
+
version: 0.18.0
|
|
80
|
+
- !ruby/object:Gem::Dependency
|
|
81
|
+
name: base64
|
|
82
|
+
requirement: !ruby/object:Gem::Requirement
|
|
83
|
+
requirements:
|
|
84
|
+
- - ">="
|
|
85
|
+
- !ruby/object:Gem::Version
|
|
86
|
+
version: '0'
|
|
87
|
+
type: :development
|
|
88
|
+
prerelease: false
|
|
89
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
90
|
+
requirements:
|
|
91
|
+
- - ">="
|
|
92
|
+
- !ruby/object:Gem::Version
|
|
93
|
+
version: '0'
|
|
94
|
+
- !ruby/object:Gem::Dependency
|
|
95
|
+
name: benchmark
|
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
|
97
|
+
requirements:
|
|
98
|
+
- - ">="
|
|
99
|
+
- !ruby/object:Gem::Version
|
|
100
|
+
version: '0'
|
|
101
|
+
type: :development
|
|
102
|
+
prerelease: false
|
|
103
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
104
|
+
requirements:
|
|
105
|
+
- - ">="
|
|
106
|
+
- !ruby/object:Gem::Version
|
|
107
|
+
version: '0'
|
|
108
|
+
- !ruby/object:Gem::Dependency
|
|
109
|
+
name: logger
|
|
110
|
+
requirement: !ruby/object:Gem::Requirement
|
|
111
|
+
requirements:
|
|
112
|
+
- - ">="
|
|
113
|
+
- !ruby/object:Gem::Version
|
|
114
|
+
version: '0'
|
|
115
|
+
type: :development
|
|
116
|
+
prerelease: false
|
|
117
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
118
|
+
requirements:
|
|
119
|
+
- - ">="
|
|
120
|
+
- !ruby/object:Gem::Version
|
|
121
|
+
version: '0'
|
|
122
|
+
- !ruby/object:Gem::Dependency
|
|
123
|
+
name: ostruct
|
|
124
|
+
requirement: !ruby/object:Gem::Requirement
|
|
125
|
+
requirements:
|
|
126
|
+
- - ">="
|
|
127
|
+
- !ruby/object:Gem::Version
|
|
128
|
+
version: '0'
|
|
129
|
+
type: :development
|
|
130
|
+
prerelease: false
|
|
131
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
132
|
+
requirements:
|
|
133
|
+
- - ">="
|
|
134
|
+
- !ruby/object:Gem::Version
|
|
135
|
+
version: '0'
|
|
136
|
+
- !ruby/object:Gem::Dependency
|
|
137
|
+
name: rspec
|
|
138
|
+
requirement: !ruby/object:Gem::Requirement
|
|
139
|
+
requirements:
|
|
140
|
+
- - "~>"
|
|
141
|
+
- !ruby/object:Gem::Version
|
|
142
|
+
version: '3.12'
|
|
143
|
+
type: :development
|
|
144
|
+
prerelease: false
|
|
145
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
146
|
+
requirements:
|
|
147
|
+
- - "~>"
|
|
148
|
+
- !ruby/object:Gem::Version
|
|
149
|
+
version: '3.12'
|
|
150
|
+
- !ruby/object:Gem::Dependency
|
|
151
|
+
name: rubocop
|
|
152
|
+
requirement: !ruby/object:Gem::Requirement
|
|
153
|
+
requirements:
|
|
154
|
+
- - "~>"
|
|
155
|
+
- !ruby/object:Gem::Version
|
|
156
|
+
version: '1.28'
|
|
157
|
+
type: :development
|
|
158
|
+
prerelease: false
|
|
159
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
160
|
+
requirements:
|
|
161
|
+
- - "~>"
|
|
162
|
+
- !ruby/object:Gem::Version
|
|
163
|
+
version: '1.28'
|
|
164
|
+
- !ruby/object:Gem::Dependency
|
|
165
|
+
name: rubocop-performance
|
|
166
|
+
requirement: !ruby/object:Gem::Requirement
|
|
167
|
+
requirements:
|
|
168
|
+
- - "~>"
|
|
169
|
+
- !ruby/object:Gem::Version
|
|
170
|
+
version: '1.14'
|
|
171
|
+
type: :development
|
|
172
|
+
prerelease: false
|
|
173
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
174
|
+
requirements:
|
|
175
|
+
- - "~>"
|
|
176
|
+
- !ruby/object:Gem::Version
|
|
177
|
+
version: '1.14'
|
|
178
|
+
- !ruby/object:Gem::Dependency
|
|
179
|
+
name: rubocop-rspec
|
|
180
|
+
requirement: !ruby/object:Gem::Requirement
|
|
181
|
+
requirements:
|
|
182
|
+
- - "~>"
|
|
183
|
+
- !ruby/object:Gem::Version
|
|
184
|
+
version: '2.11'
|
|
185
|
+
type: :development
|
|
186
|
+
prerelease: false
|
|
187
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
188
|
+
requirements:
|
|
189
|
+
- - "~>"
|
|
190
|
+
- !ruby/object:Gem::Version
|
|
191
|
+
version: '2.11'
|
|
192
|
+
- !ruby/object:Gem::Dependency
|
|
193
|
+
name: rubocop-thread_safety
|
|
194
|
+
requirement: !ruby/object:Gem::Requirement
|
|
195
|
+
requirements:
|
|
196
|
+
- - ">="
|
|
197
|
+
- !ruby/object:Gem::Version
|
|
198
|
+
version: '0'
|
|
199
|
+
type: :development
|
|
200
|
+
prerelease: false
|
|
201
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
202
|
+
requirements:
|
|
203
|
+
- - ">="
|
|
204
|
+
- !ruby/object:Gem::Version
|
|
205
|
+
version: '0'
|
|
206
|
+
- !ruby/object:Gem::Dependency
|
|
207
|
+
name: sorbet
|
|
208
|
+
requirement: !ruby/object:Gem::Requirement
|
|
209
|
+
requirements:
|
|
210
|
+
- - "~>"
|
|
211
|
+
- !ruby/object:Gem::Version
|
|
212
|
+
version: '0.5'
|
|
213
|
+
type: :development
|
|
214
|
+
prerelease: false
|
|
215
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
216
|
+
requirements:
|
|
217
|
+
- - "~>"
|
|
218
|
+
- !ruby/object:Gem::Version
|
|
219
|
+
version: '0.5'
|
|
220
|
+
- !ruby/object:Gem::Dependency
|
|
221
|
+
name: tsort
|
|
222
|
+
requirement: !ruby/object:Gem::Requirement
|
|
223
|
+
requirements:
|
|
224
|
+
- - ">="
|
|
225
|
+
- !ruby/object:Gem::Version
|
|
226
|
+
version: '0'
|
|
227
|
+
type: :development
|
|
228
|
+
prerelease: false
|
|
229
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
230
|
+
requirements:
|
|
231
|
+
- - ">="
|
|
232
|
+
- !ruby/object:Gem::Version
|
|
233
|
+
version: '0'
|
|
234
|
+
- !ruby/object:Gem::Dependency
|
|
235
|
+
name: yard
|
|
236
|
+
requirement: !ruby/object:Gem::Requirement
|
|
237
|
+
requirements:
|
|
238
|
+
- - ">="
|
|
239
|
+
- !ruby/object:Gem::Version
|
|
240
|
+
version: '0'
|
|
241
|
+
type: :development
|
|
242
|
+
prerelease: false
|
|
243
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
244
|
+
requirements:
|
|
245
|
+
- - ">="
|
|
246
|
+
- !ruby/object:Gem::Version
|
|
247
|
+
version: '0'
|
|
248
|
+
- !ruby/object:Gem::Dependency
|
|
249
|
+
name: yard-sorbet
|
|
250
|
+
requirement: !ruby/object:Gem::Requirement
|
|
251
|
+
requirements:
|
|
252
|
+
- - ">="
|
|
253
|
+
- !ruby/object:Gem::Version
|
|
254
|
+
version: '0'
|
|
255
|
+
type: :development
|
|
256
|
+
prerelease: false
|
|
257
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
258
|
+
requirements:
|
|
259
|
+
- - ">="
|
|
260
|
+
- !ruby/object:Gem::Version
|
|
261
|
+
version: '0'
|
|
262
|
+
description: Generates highly concise, deterministic Markdown coverage digests tailored
|
|
263
|
+
for LLMs and autonomous agents by matching coverage deficits to their AST semantic
|
|
264
|
+
boundaries rather than line numbers.
|
|
265
|
+
email:
|
|
266
|
+
- vitalii.lazebnyi.github@gmail.com
|
|
267
|
+
executables: []
|
|
268
|
+
extensions: []
|
|
269
|
+
extra_rdoc_files: []
|
|
270
|
+
files:
|
|
271
|
+
- LICENSE.txt
|
|
272
|
+
- README.md
|
|
273
|
+
- certs/simplecov-ai-public_cert.pem
|
|
274
|
+
- lib/simplecov-ai.rb
|
|
275
|
+
- lib/simplecov-ai/ast_resolver.rb
|
|
276
|
+
- lib/simplecov-ai/configuration.rb
|
|
277
|
+
- lib/simplecov-ai/markdown_builder.rb
|
|
278
|
+
- lib/simplecov-ai/version.rb
|
|
279
|
+
homepage: https://github.com/VitaliiLazebnyi/simplecov-ai
|
|
280
|
+
licenses:
|
|
281
|
+
- MIT
|
|
282
|
+
metadata:
|
|
283
|
+
source_code_uri: https://github.com/VitaliiLazebnyi/simplecov-ai
|
|
284
|
+
allowed_push_host: https://rubygems.org
|
|
285
|
+
rubygems_mfa_required: 'true'
|
|
286
|
+
rdoc_options: []
|
|
287
|
+
require_paths:
|
|
288
|
+
- lib
|
|
289
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
290
|
+
requirements:
|
|
291
|
+
- - ">="
|
|
292
|
+
- !ruby/object:Gem::Version
|
|
293
|
+
version: 3.0.0
|
|
294
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
295
|
+
requirements:
|
|
296
|
+
- - ">="
|
|
297
|
+
- !ruby/object:Gem::Version
|
|
298
|
+
version: '0'
|
|
299
|
+
requirements: []
|
|
300
|
+
rubygems_version: 4.0.6
|
|
301
|
+
specification_version: 4
|
|
302
|
+
summary: An AI-optimized Markdown formatter for SimpleCov utilizing AST mapping.
|
|
303
|
+
test_files: []
|
metadata.gz.sig
ADDED
|
Binary file
|