quality_gate 0.1.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b549f0b3f39f5a51bc4c1321a8b0771ae355f4ad1a8da733e6f5cf9c8209a54e
4
- data.tar.gz: a0adfdf9647271909cac928c6056ee59114a5eefee5c8173c4ecf8816bbbb5ba
3
+ metadata.gz: dc4fe18802dfc3fb409d11c8db6eb816df282e1914cd143807436998724eaf19
4
+ data.tar.gz: f1529c7f7cf1959a889fcc8a88691da70853ccb36e499718a5d56918e58add21
5
5
  SHA512:
6
- metadata.gz: 6f58b91553cbf70f2224831b4c21f8b2aa279a3e76dba5638445c2d11982363d4820a8d70b54b99fcc83d5b97e1d898292e472c0427b7f2e34c764b2979ab796
7
- data.tar.gz: eb7bbe5a088b832b60f28751f6dfd1009a4d6ed1ce291e42c27dcd2abbbb9ccc1310e4d45992bff589723aaf1dd37c47ff9763f1edb5599aace9fbc730621d0e
6
+ metadata.gz: f5d316d6426037588e9b895ed9f0d0a18d6f59e06b6666d8b37fd079af1bab0dddf991c1b756385ef90a6f5ae562da322f65a704f55d71b302adf5d9de90c63b
7
+ data.tar.gz: 2a21cb8513b65b1fb5fee6187bb303ae1ece9f575c9d1ae0c46c7cff5e2f27be7e12a88c6543cb9c29fb10fb636e02d0ebd4bab5b246c6d5ba6f99f51505ab39
data/CHANGELOG.md CHANGED
@@ -1,3 +1,14 @@
1
+ ## [0.2.0] - 2026-09-21
2
+
3
+ - Gate commands accept `--format markdown` and `format: markdown`, printing a heading with the tally, a checks table, and a findings list for agents and pull request comments.
4
+ - Undercover falls back to `origin/main` or `origin/master` when `origin/HEAD` and the local default branch are absent, so verify checks the diff on CI pull request checkouts instead of skipping. A missing default branch is now reported as missing rather than as a detached checkout.
5
+ - The repository's own `.undercover` keeps Undercover's default test exclusions next to the version file exclusion.
6
+
7
+ ## [0.1.1] - 2026-09-10
8
+
9
+ - Text gate output now lists each tool that ran with its status, scope, and duration, so a clean run is distinguishable from a run where nothing executed.
10
+ - The installer no longer injects a coverage block into a test helper that already calls `SimpleCov.start`; it reports the skip and names what to confirm.
11
+
1
12
  ## [0.1.0] - 2026-09-08
2
13
 
3
14
  - Layered fast, verify, and audit gates with structured findings for Ruby projects.
data/README.md CHANGED
@@ -45,19 +45,31 @@ The built-in fast path is meant for changed files:
45
45
  bundle exec quality_gate fast --files app/models/user.rb
46
46
  ```
47
47
 
48
- `verify` records coverage while it runs the full test suite, then checks changed code with Undercover. A clean `verify` or `audit` run prints this clean summary:
48
+ `verify` records coverage while it runs the full test suite, then checks changed code with Undercover. A clean `verify` or `audit` run prints this clean summary: one line per tool that ran, naming its status, scope, and duration, then the tally:
49
49
 
50
50
  ```text
51
+ reek clean project 312ms
52
+ test_suite clean test_suite 189137ms
53
+ undercover clean git_diff 619ms
51
54
  0 findings, 0 tool failures
52
55
  ```
53
56
 
57
+ (Durations above are illustrative, not measured figures.) These tool lines make a clean run visible: you see every tool that ran, not just an absence of findings.
58
+
54
59
  Use `--files` to select paths for RuboCop and Reek; the first path follows the option and further paths follow it as separate arguments:
55
60
 
56
61
  ```sh
57
62
  bundle exec quality_gate fast --files lib/quality_gate.rb test/test_quality_gate.rb
58
63
  ```
59
64
 
60
- Paths must exist, including paths from the configuration file. Any missing path returns exit `2` before tools run. A bare `fast` scans the project; it does not discover changed files automatically.
65
+ Paths must exist, including paths from the configuration file. Any missing path returns exit `2` before tools run. A bare `fast` scans the project; it does not discover changed files automatically. RuboCop is the only default fast adapter, so a clean bare `fast` run prints one tool line then the tally:
66
+
67
+ ```text
68
+ rubocop clean project 842ms
69
+ 0 findings, 0 tool failures
70
+ ```
71
+
72
+ (842ms above is illustrative, not a measured figure.)
61
73
 
62
74
  | Adapter | Scope with `--files` |
63
75
  | --- | --- |
@@ -70,25 +82,36 @@ Paths must exist, including paths from the configuration file. Any missing path
70
82
 
71
83
  `verify --files app/models/user.rb` therefore narrows Reek, but still runs the full suite and checks Git changes. JSON includes each adapter's scope and status. When stderr is a terminal, progress names each tool before it starts; redirected output and hook invocations remain quiet except for diagnostics.
72
84
 
73
- Choose text or JSON output with `--format text` or `--format json`:
85
+ Choose text, JSON, or Markdown output with `--format text`, `--format json`, or `--format markdown`:
74
86
 
75
87
  ```sh
76
88
  bundle exec quality_gate verify --format json
89
+ bundle exec quality_gate verify --format markdown
77
90
  ```
78
91
 
79
92
  The shorter form `quality_gate version` works when the installed executable is already on your `PATH`.
80
93
 
81
- The repository config must also use exactly `text` or `json` for `format`; any other configured value is rejected as a configuration error before a gate runs.
94
+ The repository config must also use exactly `text`, `json`, or `markdown` for `format`; any other configured value is rejected as a configuration error before a gate runs.
82
95
 
83
96
  ## Output contract
84
97
 
85
- Gate text output includes tool, severity, location when available, rule, and message:
98
+ Gate text output prints, in order: one line per tool that ran, then findings, then the tally.
99
+
100
+ When at least one tool ran, each gets a line naming the tool, its status, what it inspected, and how long it took:
101
+
102
+ ```text
103
+ <tool> <status> <scope> <duration>ms
104
+ ```
105
+
106
+ `status` is `clean`, `findings`, `tool_failure`, or `skipped`; `scope` describes what the tool inspected, such as `project`, `selected_files`, `git_diff`, `test_suite`, `coverage_summary`, or `lockfile`. These lines distinguish a clean run, which always names its tools, from a run where nothing executed. An empty adapter list runs no tools and prints none of these lines.
107
+
108
+ Findings come next, with tool, severity, location when available, rule, and message:
86
109
 
87
110
  ```text
88
111
  <tool> <severity> <file>:<line> <rule> <message>
89
112
  ```
90
113
 
91
- Multiline messages retain indented detail lines. Findings without a source location omit it rather than printing `:0`. The final gate output line is the summary:
114
+ Multiline messages retain indented detail lines. Findings without a source location omit it rather than printing `:0`. The final gate output line is unchanged, the summary:
92
115
 
93
116
  ```text
94
117
  <n> findings, <m> tool failures
@@ -149,6 +172,23 @@ When `--format json` is requested, input/configuration failures use the same rep
149
172
 
150
173
  Consumers should tolerate additive JSON fields. Existing finding and summary fields retain their meanings. An informational Undercover skip remains a finding and returns exit `1`; it is not proof that changed code was covered.
151
174
 
175
+ Markdown output is for pasting into agent notes, pull request comments, and chat. It prints a level-two heading with the tally, a checks table when at least one tool ran, and a findings list when there are findings:
176
+
177
+ ```markdown
178
+ ## Quality Gate: 1 findings, 0 tool failures
179
+
180
+ | Tool | Status | Scope | Duration |
181
+ | --- | --- | --- | --- |
182
+ | rubocop | findings | selected_files | 1240ms |
183
+
184
+ ### Findings
185
+
186
+ - **rubocop** warning `lib/a.rb:4` Layout/First: First message
187
+ Second message line
188
+ ```
189
+
190
+ Locations are code spans, extra message lines are indented under their finding, and pipe characters in table cells are escaped. Message punctuation is escaped so diagnostic HTML and Markdown markers display literally. Input and configuration errors keep the plain-text stderr behaviour; only JSON has a machine-readable error envelope.
191
+
152
192
  ## Configuration
153
193
 
154
194
  QualityGate looks for `.quality_gate.yml` in the working repository. Values in that file override the shipped defaults for known top-level keys:
@@ -352,7 +392,7 @@ The default verify adapters always run in order: Reek reports code smells, the t
352
392
 
353
393
  Reek uses an existing host `.reek.yml` when present and otherwise uses the shipped configuration. It reports smells as warnings and respects `--files`; without selected files it scans the project. Missing selected files are rejected by the CLI before any adapter runs.
354
394
 
355
- Undercover compares against the configured `compare_point` when present. Otherwise it uses the merge base of `HEAD` and the detected default branch: `refs/remotes/origin/HEAD` when available, then local `main`, then local `master`. If history is shallow, the default branch is missing, there is no earlier commit, or a detached checkout has no shared ancestor, verify reports an informational `undercover_skipped` finding with the reason instead of silently passing.
395
+ Undercover compares against the configured `compare_point` when present. Otherwise it uses the merge base of `HEAD` and the detected default branch: `refs/remotes/origin/HEAD` when available, then `main` (local, then `origin/main`), then `master` (local, then `origin/master`). The remote-tracking fallback covers CI pull request checkouts, which are detached and fetch the default branch only as `origin/main`. If history is shallow, the default branch is missing, there is no earlier commit, or a detached checkout has no shared ancestor, verify reports an informational `undercover_skipped` finding with the reason instead of silently passing.
356
396
 
357
397
  Each uncovered region is a warning that names its file, first line, full line range, node, uncovered lines, and uncovered branch context. If the coverage record is absent, verify reports a tool failure explaining that the SimpleCov wiring is missing. Install it with `quality_gate init --profile ruby` or the Rails generator. The test-suite and Undercover adapters ignore `--files`: the suite and the Git change define their scope.
358
398
 
@@ -1,4 +1,4 @@
1
- # # Output format: "text" or "json".
1
+ # # Output format: "text", "json", or "markdown".
2
2
  # format: text
3
3
  #
4
4
  # # Optional file paths passed to adapters; empty means no configured restriction.
@@ -1,4 +1,4 @@
1
- # # Output format: "text" or "json".
1
+ # # Output format: "text", "json", or "markdown".
2
2
  # format: text
3
3
  #
4
4
  # # Optional file paths passed to RuboCop and Reek; empty means the project scope.
@@ -10,7 +10,10 @@ module QualityGate
10
10
  COVERAGE_PATH = "coverage/coverage.json"
11
11
  SKIP_RULE = "undercover_skipped"
12
12
  CLI_FOOTER_PATTERN = /\nUndercover finished in \d+(?:\.\d+)?s\n\z/
13
- private_constant :CLI_FOOTER_PATTERN
13
+ NAMED_DEFAULT_BRANCH_REFS = %w[main master].flat_map do |branch|
14
+ ["refs/heads/#{branch}", "refs/remotes/origin/#{branch}"]
15
+ end.freeze
16
+ private_constant :CLI_FOOTER_PATTERN, :NAMED_DEFAULT_BRANCH_REFS
14
17
 
15
18
  def name = "undercover"
16
19
 
@@ -144,7 +147,7 @@ module QualityGate
144
147
 
145
148
  def skip_reason
146
149
  return "Undercover skipped: repository is shallow; set compare_point explicitly." if shallow_repository?
147
- return detached_reason if detached_head?
150
+ return detached_reason if detached_head? && default_branch_present?
148
151
  return first_commit_reason if first_commit?
149
152
  return missing_branch_reason unless default_branch_present?
150
153
 
@@ -201,7 +204,7 @@ module QualityGate
201
204
  def default_branch_ref
202
205
  return @default_branch_ref if defined?(@default_branch_ref)
203
206
 
204
- @default_branch_ref = remote_default_branch || local_default_branch
207
+ @default_branch_ref = remote_default_branch || named_default_branch
205
208
  end
206
209
 
207
210
  def remote_default_branch
@@ -217,19 +220,12 @@ module QualityGate
217
220
  branch
218
221
  end
219
222
 
220
- def local_default_branch
221
- %w[main master].each do |branch|
222
- ref = "refs/heads/#{branch}"
223
- next unless commit_exists?(ref)
224
-
225
- @default_branch_present = true
226
- @default_branch_name = branch
227
- return ref
228
- end
229
-
230
- @default_branch_present = false
231
- @default_branch_name = "main"
232
- "refs/heads/main"
223
+ # Local branches win, but a CI pull request checkout often has only the remote-tracking ref.
224
+ def named_default_branch
225
+ ref = NAMED_DEFAULT_BRANCH_REFS.find { commit_exists?(_1) }
226
+ @default_branch_present = !ref.nil?
227
+ @default_branch_name = ref ? ref.split("/").last : "main"
228
+ ref || "refs/heads/main"
233
229
  end
234
230
 
235
231
  def merge_base(branch)
@@ -166,7 +166,7 @@ module QualityGate
166
166
 
167
167
  overrides[:files] = [path]
168
168
  end
169
- parser.on("--format FORMAT", %w[text json]) { |format| overrides[:format] = format }
169
+ parser.on("--format FORMAT", Config::FORMATS) { |format| overrides[:format] = format }
170
170
  parser.on("-h", "--help") { overrides[:help] = true }
171
171
  end
172
172
  end
@@ -241,10 +241,9 @@ module QualityGate
241
241
 
242
242
  def reporter_for(format, stdout:)
243
243
  case format
244
- when "json"
245
- Reporters::Json.new(io: stdout)
246
- else
247
- Reporters::Text.new(io: stdout)
244
+ when "json" then Reporters::Json.new(io: stdout)
245
+ when "markdown" then Reporters::Markdown.new(io: stdout)
246
+ else Reporters::Text.new(io: stdout)
248
247
  end
249
248
  end
250
249
 
@@ -296,7 +295,7 @@ module QualityGate
296
295
 
297
296
  Options:
298
297
  --files PATH [PATH ...] Replace configured paths for this run; files and directories are allowed
299
- --format FORMAT Choose text or json output (also accepts --format=FORMAT)
298
+ --format FORMAT Choose text, json, or markdown output (also accepts --format=FORMAT)
300
299
  -h, --help Show this help
301
300
 
302
301
  Scope:
@@ -308,6 +307,7 @@ module QualityGate
308
307
  quality_gate fast
309
308
  quality_gate fast --files app/models/user.rb test/models
310
309
  quality_gate verify --format json
310
+ quality_gate verify --format markdown
311
311
 
312
312
  Exit status:
313
313
  0 Checks completed cleanly
@@ -326,7 +326,7 @@ module QualityGate
326
326
 
327
327
  Options:
328
328
  --files PATH [PATH ...] Replace configured paths for this run; files and directories are allowed
329
- --format FORMAT Choose text or json output (also accepts --format=FORMAT)
329
+ --format FORMAT Choose text, json, or markdown output (also accepts --format=FORMAT)
330
330
  -h, --help Show this help
331
331
 
332
332
  Scope:
@@ -362,7 +362,7 @@ module QualityGate
362
362
  --pretend Preview changes without writing them
363
363
  -h, --help Show this help
364
364
 
365
- Init writes a human-readable summary. Gate commands continue to support text and JSON output.
365
+ Init writes a human-readable summary. Gate commands continue to support text, JSON, and Markdown output.
366
366
 
367
367
  Examples:
368
368
  quality_gate init
@@ -396,7 +396,7 @@ module QualityGate
396
396
  contents = File.read(path)
397
397
  document = YAML.safe_load(contents)
398
398
  format = document.is_a?(Hash) && document["format"]
399
- return format if %w[text json].include?(format)
399
+ return format if Config::FORMATS.include?(format)
400
400
 
401
401
  nil
402
402
  rescue Psych::Exception, SystemCallError, SystemStackError
@@ -34,6 +34,7 @@ module QualityGate
34
34
  compare_point: nil,
35
35
  rubocop_config: nil
36
36
  }.freeze
37
+ FORMATS = %w[text json markdown].map!(&:freeze).freeze
37
38
  ADAPTER_LAYERS = %i[fast verify audit].freeze
38
39
  COVERAGE_KEYS = %i[minimum_line minimum_branch].freeze
39
40
  private_constant :ADAPTER_LAYERS, :COVERAGE_KEYS
@@ -95,9 +96,9 @@ module QualityGate
95
96
  end
96
97
 
97
98
  def validate_format(format, path)
98
- return if %w[text json].include?(format)
99
+ return if FORMATS.include?(format)
99
100
 
100
- fail ConfigError.new(path: path, cause_message: "format must be text or json") # rubocop:disable Style/SignalException
101
+ fail ConfigError.new(path: path, cause_message: "format must be text, json, or markdown") # rubocop:disable Style/SignalException
101
102
  end
102
103
 
103
104
  def validate_files(files, path)
@@ -22,6 +22,7 @@ module QualityGate
22
22
  ].freeze
23
23
  MARKER_START = "# quality_gate coverage — start"
24
24
  MARKER_END = "# quality_gate coverage — end"
25
+ EXISTING_SIMPLECOV_START = /SimpleCov\s*\.\s*start\b/
25
26
  AGENT_CONTRACT_START = "<!-- quality_gate agent contract — start -->"
26
27
  AGENT_CONTRACT_END = "<!-- quality_gate agent contract — end -->"
27
28
  AGENT_TEMPLATE_PATHS = {
@@ -424,6 +425,7 @@ module QualityGate
424
425
  def install_coverage(path, relative_path)
425
426
  original = File.binread(path)
426
427
  return record(:unchanged, relative_path) if original.include?(MARKER_START.b)
428
+ return skip_existing_simplecov(relative_path) if EXISTING_SIMPLECOV_START.match?(original)
427
429
  return record_pretend_skip(relative_path) if options[:pretend]
428
430
 
429
431
  content = content_with_coverage(original)
@@ -659,6 +661,12 @@ module QualityGate
659
661
  record(:skipped, "test/test_helper.rb (missing; Minitest coverage wiring skipped)")
660
662
  end
661
663
 
664
+ def skip_existing_simplecov(relative_path)
665
+ say "Warning: coverage wiring skipped; #{relative_path} already calls SimpleCov.start. " \
666
+ "Confirm it enables branch coverage and loads the Undercover formatter."
667
+ record(:skipped, "#{relative_path} (already starts SimpleCov; coverage wiring skipped)")
668
+ end
669
+
662
670
  def record_pretend_skip(relative_path)
663
671
  record(:skipped, "#{relative_path} (pretend)")
664
672
  end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module QualityGate
4
+ module Reporters
5
+ # Writes Markdown reports that agents and humans can paste into notes and reviews.
6
+ class Markdown
7
+ TABLE_HEADER = ["| Tool | Status | Scope | Duration |", "| --- | --- | --- | --- |"].freeze
8
+
9
+ def initialize(io:)
10
+ @io = io
11
+ end
12
+
13
+ def call(result)
14
+ io.puts(sections_for(result).join("\n\n"))
15
+ end
16
+
17
+ private
18
+
19
+ attr_reader :io
20
+
21
+ def sections_for(result)
22
+ [summary_heading(result), checks_table(result.checks), findings_section(result.findings)].compact
23
+ end
24
+
25
+ def summary_heading(result)
26
+ "## Quality Gate: #{result.findings.length} findings, #{result.failed_tools.length} tool failures"
27
+ end
28
+
29
+ def checks_table(checks)
30
+ return if checks.empty?
31
+
32
+ [*TABLE_HEADER, *checks.map { render_check(_1) }].join("\n")
33
+ end
34
+
35
+ def render_check(check)
36
+ cells = [check[:tool], check[:status], check[:scope]].map { table_cell(_1) }
37
+ "| #{cells.join(" | ")} | #{duration_ms_for(check)}ms |"
38
+ end
39
+
40
+ def duration_ms_for(check)
41
+ Integer(check[:duration_ms], exception: false) || 0
42
+ end
43
+
44
+ def table_cell(value)
45
+ FieldSanitizer.for_text(value).gsub("|", "\\|")
46
+ end
47
+
48
+ def findings_section(findings)
49
+ return if findings.empty?
50
+
51
+ ["### Findings", "", *findings.map { render_finding(_1) }].join("\n")
52
+ end
53
+
54
+ def render_finding(finding)
55
+ message = FieldSanitizer.for_text_message(finding.message).gsub(/[[:punct:]&&[:ascii:]]/) { "\\#{_1}" }
56
+ lines = message.split("\n", -1)
57
+ ["- #{finding_prefix(finding)}: #{lines.first}", *lines.drop(1).map { " #{_1}" }].join("\n")
58
+ end
59
+
60
+ def finding_prefix(finding)
61
+ tool = "**#{FieldSanitizer.for_text(finding.tool)}**"
62
+ severity = FieldSanitizer.for_text(finding.severity.to_s)
63
+ [tool, severity, finding_location(finding), FieldSanitizer.for_text(finding.rule)].compact.join(" ")
64
+ end
65
+
66
+ def finding_location(finding)
67
+ file = FieldSanitizer.for_text(finding.file)
68
+ return if file.empty?
69
+
70
+ code_span(finding.line.zero? ? file : "#{file}:#{finding.line}")
71
+ end
72
+
73
+ def code_span(text)
74
+ fence = "`" * (longest_backtick_run(text) + 1)
75
+ fence.length == 1 ? "`#{text}`" : "#{fence} #{text} #{fence}"
76
+ end
77
+
78
+ def longest_backtick_run(text)
79
+ text.scan(/`+/).map(&:length).max.to_i
80
+ end
81
+ end
82
+ end
83
+ end
@@ -9,6 +9,8 @@ module QualityGate
9
9
  end
10
10
 
11
11
  def call(result)
12
+ print_checks(result.checks)
13
+
12
14
  result.findings.each do |finding|
13
15
  io.puts(render_finding(finding))
14
16
  end
@@ -20,6 +22,26 @@ module QualityGate
20
22
 
21
23
  attr_reader :io
22
24
 
25
+ def print_checks(checks)
26
+ return if checks.empty?
27
+
28
+ checks.each { |check| io.puts(render_check(check)) }
29
+ end
30
+
31
+ def render_check(check)
32
+ format(
33
+ "%<tool>-12s %<status>-12s %<scope>-18s %<duration>8dms",
34
+ tool: FieldSanitizer.for_text(check[:tool]),
35
+ status: FieldSanitizer.for_text(check[:status]),
36
+ scope: FieldSanitizer.for_text(check[:scope]),
37
+ duration: duration_ms_for(check)
38
+ )
39
+ end
40
+
41
+ def duration_ms_for(check)
42
+ Integer(check[:duration_ms], exception: false) || 0
43
+ end
44
+
23
45
  def render_finding(finding)
24
46
  lines = FieldSanitizer.for_text_message(finding.message).split("\n", -1)
25
47
  ["#{finding_prefix(finding)} #{lines.first}", *lines.drop(1).map { " #{_1}" }].join("\n")
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module QualityGate
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
data/lib/quality_gate.rb CHANGED
@@ -5,6 +5,7 @@ require_relative "quality_gate/exit_code"
5
5
  require_relative "quality_gate/finding"
6
6
  require_relative "quality_gate/reporters/field_sanitizer"
7
7
  require_relative "quality_gate/reporters/json"
8
+ require_relative "quality_gate/reporters/markdown"
8
9
  require_relative "quality_gate/reporters/text"
9
10
  require_relative "quality_gate/hook_log"
10
11
 
data/sig/quality_gate.rbs CHANGED
@@ -189,6 +189,11 @@ module QualityGate
189
189
  def initialize: (io: _Output) -> void
190
190
  def call: (Runner::Result result) -> nil
191
191
  end
192
+
193
+ class Markdown
194
+ def initialize: (io: _Output) -> void
195
+ def call: (Runner::Result result) -> nil
196
+ end
192
197
  end
193
198
 
194
199
  type finding_severity = :error | :warning | :info
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: quality_gate
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lucian Ghinda
@@ -219,6 +219,7 @@ files:
219
219
  - lib/quality_gate/railtie.rb
220
220
  - lib/quality_gate/reporters/field_sanitizer.rb
221
221
  - lib/quality_gate/reporters/json.rb
222
+ - lib/quality_gate/reporters/markdown.rb
222
223
  - lib/quality_gate/reporters/text.rb
223
224
  - lib/quality_gate/rubocop.rb
224
225
  - lib/quality_gate/ruby_profile.rb