quiet_quality 1.1.0 → 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/dogfood.yml +1 -1
  3. data/.github/workflows/rspec.yml +1 -1
  4. data/.quiet_quality.ci.yml +6 -0
  5. data/.quiet_quality.yml +2 -1
  6. data/CHANGELOG.md +63 -0
  7. data/README.md +43 -13
  8. data/lib/quiet_quality/cli/arg_parser.rb +23 -7
  9. data/lib/quiet_quality/cli/entrypoint.rb +21 -30
  10. data/lib/quiet_quality/cli/presenter.rb +77 -0
  11. data/lib/quiet_quality/config/builder.rb +5 -0
  12. data/lib/quiet_quality/config/finder.rb +0 -4
  13. data/lib/quiet_quality/config/logging.rb +23 -0
  14. data/lib/quiet_quality/config/options.rb +6 -0
  15. data/lib/quiet_quality/config/parsed_options.rb +36 -0
  16. data/lib/quiet_quality/config/parser.rb +5 -8
  17. data/lib/quiet_quality/executors/base_executor.rb +9 -11
  18. data/lib/quiet_quality/logger.rb +17 -0
  19. data/lib/quiet_quality/tools/base_runner.rb +49 -0
  20. data/lib/quiet_quality/tools/brakeman/runner.rb +7 -26
  21. data/lib/quiet_quality/tools/brakeman.rb +0 -2
  22. data/lib/quiet_quality/tools/haml_lint/runner.rb +15 -50
  23. data/lib/quiet_quality/tools/haml_lint.rb +0 -2
  24. data/lib/quiet_quality/tools/markdown_lint/parser.rb +34 -0
  25. data/lib/quiet_quality/tools/markdown_lint/runner.rb +28 -0
  26. data/lib/quiet_quality/tools/markdown_lint.rb +9 -0
  27. data/lib/quiet_quality/tools/relevant_runner.rb +55 -0
  28. data/lib/quiet_quality/tools/rspec/runner.rb +9 -46
  29. data/lib/quiet_quality/tools/rspec.rb +0 -2
  30. data/lib/quiet_quality/tools/rubocop/runner.rb +9 -56
  31. data/lib/quiet_quality/tools/rubocop.rb +0 -2
  32. data/lib/quiet_quality/tools/standardrb/runner.rb +15 -3
  33. data/lib/quiet_quality/tools/standardrb.rb +0 -2
  34. data/lib/quiet_quality/tools.rb +6 -0
  35. data/lib/quiet_quality/version.rb +1 -1
  36. data/lib/quiet_quality/version_control_systems/git.rb +2 -11
  37. metadata +12 -2
@@ -0,0 +1,49 @@
1
+ module QuietQuality
2
+ module Tools
3
+ class BaseRunner
4
+ # In general, we don't want to supply a huge number of arguments to a command-line tool.
5
+ MAX_FILES = 100
6
+
7
+ def initialize(changed_files: nil, file_filter: nil)
8
+ @changed_files = changed_files
9
+ @file_filter = file_filter
10
+ end
11
+
12
+ def invoke!
13
+ @_outcome ||= performed_outcome
14
+ end
15
+
16
+ def tool_name
17
+ fail(NoMethodError, "BaseRunner subclass must implement `tool_name`")
18
+ end
19
+
20
+ def command
21
+ fail(NoMethodError, "BaseRunner subclass must implement `command`")
22
+ end
23
+
24
+ def success_status?(stat)
25
+ stat.success?
26
+ end
27
+
28
+ # distinct from _error_ status - this is asking "does this status represent failures-found?"
29
+ def failure_status?(stat)
30
+ stat.exitstatus == 1
31
+ end
32
+
33
+ private
34
+
35
+ attr_reader :changed_files, :file_filter
36
+
37
+ def performed_outcome
38
+ out, err, stat = Open3.capture3(*command)
39
+ if success_status?(stat)
40
+ Outcome.new(tool: tool_name, output: out, logging: err)
41
+ elsif failure_status?(stat)
42
+ Outcome.new(tool: tool_name, output: out, logging: err, failure: true)
43
+ else
44
+ fail(ExecutionError, "Execution of #{tool_name} failed with #{stat.exitstatus}")
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -1,38 +1,19 @@
1
1
  module QuietQuality
2
2
  module Tools
3
3
  module Brakeman
4
- class Runner
5
- # These are specified in constants at the top of brakeman.rb:
6
- # https://github.com/presidentbeef/brakeman/blob/main/lib/brakeman.rb#L6-L25
7
- KNOWN_EXIT_STATUSES = [3, 4, 5, 6, 7, 8].to_set
8
-
9
- # brakeman does not support being run against a portion of the project, so neither
10
- # changed_files nor file_filter is actually used. But they are accepted here because
11
- # that is what Runner initializers are required to accept.
12
- def initialize(changed_files: nil, file_filter: nil)
13
- @changed_files = changed_files
14
- @file_filter = file_filter
15
- end
16
-
17
- def invoke!
18
- @_outcome ||= performed_outcome
4
+ class Runner < BaseRunner
5
+ def tool_name
6
+ :brakeman
19
7
  end
20
8
 
21
- private
22
-
23
9
  def command
24
10
  ["brakeman", "-f", "json"]
25
11
  end
26
12
 
27
- def performed_outcome
28
- out, err, stat = Open3.capture3(*command)
29
- if stat.success?
30
- Outcome.new(tool: :brakeman, output: out, logging: err)
31
- elsif KNOWN_EXIT_STATUSES.include?(stat.exitstatus)
32
- Outcome.new(tool: :brakeman, output: out, logging: err, failure: true)
33
- else
34
- fail(ExecutionError, "Execution of brakeman failed with #{stat.exitstatus}")
35
- end
13
+ # These are specified in constants at the top of brakeman.rb:
14
+ # https://github.com/presidentbeef/brakeman/blob/main/lib/brakeman.rb#L6-L25
15
+ def failure_status?(stat)
16
+ [3, 4, 5, 6, 7, 8].include?(stat.exitstatus)
36
17
  end
37
18
  end
38
19
  end
@@ -3,8 +3,6 @@ require_relative "./rubocop"
3
3
  module QuietQuality
4
4
  module Tools
5
5
  module Brakeman
6
- ExecutionError = Class.new(Tools::Error)
7
- ParsingError = Class.new(Tools::Error)
8
6
  end
9
7
  end
10
8
  end
@@ -1,64 +1,29 @@
1
1
  module QuietQuality
2
2
  module Tools
3
3
  module HamlLint
4
- class Runner
5
- MAX_FILES = 100
6
- NO_FILES_OUTPUT = %({"files": []})
7
-
8
- # haml-lint uses the `sysexits` gem, and exits with Sysexits::EX_DATAERR for the
9
- # failures case here in lib/haml_lint/cli.rb. That's mapped to status 65 - other
10
- # statuses have other failure meanings, which we don't want to interpret as "problems
11
- # encountered"
12
- FAILURE_STATUS = 65
13
-
14
- def initialize(changed_files: nil, file_filter: nil)
15
- @changed_files = changed_files
16
- @file_filter = file_filter
17
- end
18
-
19
- def invoke!
20
- @_outcome ||= skip_execution? ? skipped_outcome : performed_outcome
21
- end
22
-
23
- private
24
-
25
- attr_reader :changed_files, :file_filter
26
-
27
- def skip_execution?
28
- changed_files && relevant_files.empty?
29
- end
30
-
31
- def relevant_files
32
- return nil if changed_files.nil?
33
- changed_files.paths
34
- .select { |path| path.end_with?(".haml") }
35
- .select { |path| file_filter.nil? || file_filter.match?(path) }
4
+ class Runner < RelevantRunner
5
+ def tool_name
6
+ :haml_lint
36
7
  end
37
8
 
38
- def target_files
39
- return [] if changed_files.nil?
40
- return [] if relevant_files.length > MAX_FILES
41
- relevant_files
9
+ def no_files_output
10
+ %({"files": []})
42
11
  end
43
12
 
44
- def command
45
- return nil if skip_execution?
46
- ["haml-lint", "--reporter", "json"] + target_files.sort
13
+ def base_command
14
+ ["haml-lint", "--reporter", "json"]
47
15
  end
48
16
 
49
- def skipped_outcome
50
- Outcome.new(tool: :haml_lint, output: NO_FILES_OUTPUT)
17
+ def relevant_path?(path)
18
+ path.end_with?(".haml")
51
19
  end
52
20
 
53
- def performed_outcome
54
- out, err, stat = Open3.capture3(*command)
55
- if stat.success?
56
- Outcome.new(tool: :haml_lint, output: out, logging: err)
57
- elsif stat.exitstatus == FAILURE_STATUS
58
- Outcome.new(tool: :haml_lint, output: out, logging: err, failure: true)
59
- else
60
- fail(ExecutionError, "Execution of haml-lint failed with #{stat.exitstatus}")
61
- end
21
+ # haml-lint uses the `sysexits` gem, and exits with Sysexits::EX_DATAERR for the
22
+ # failures case here in lib/haml_lint/cli.rb. That's mapped to status 65 - other
23
+ # statuses have other failure meanings, which we don't want to interpret as "problems
24
+ # encountered"
25
+ def failure_status?(stat)
26
+ stat.exitstatus == 65
62
27
  end
63
28
  end
64
29
  end
@@ -1,8 +1,6 @@
1
1
  module QuietQuality
2
2
  module Tools
3
3
  module HamlLint
4
- ExecutionError = Class.new(Tools::Error)
5
- ParsingError = Class.new(Tools::Error)
6
4
  end
7
5
  end
8
6
  end
@@ -0,0 +1,34 @@
1
+ module QuietQuality
2
+ module Tools
3
+ module MarkdownLint
4
+ class Parser
5
+ def initialize(text)
6
+ @text = text
7
+ end
8
+
9
+ def messages
10
+ return @_messages if defined?(@_messages)
11
+ messages = content.map { |entry| message_for_entry(entry) }
12
+ @_messages = Messages.new(messages)
13
+ end
14
+
15
+ private
16
+
17
+ attr_reader :text
18
+
19
+ def content
20
+ @_content ||= JSON.parse(text, symbolize_names: true)
21
+ end
22
+
23
+ def message_for_entry(entry)
24
+ Message.new(
25
+ path: entry.fetch(:filename),
26
+ start_line: entry.fetch(:line),
27
+ rule: entry.fetch(:description),
28
+ body: entry.fetch(:docs)
29
+ )
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,28 @@
1
+ module QuietQuality
2
+ module Tools
3
+ module MarkdownLint
4
+ class Runner < RelevantRunner
5
+ def tool_name
6
+ :markdown_lint
7
+ end
8
+
9
+ def no_files_output
10
+ "[]"
11
+ end
12
+
13
+ def command
14
+ return nil if skip_execution?
15
+ if target_files.any?
16
+ ["mdl", "--json"] + target_files.sort
17
+ else
18
+ ["mdl", "--json", "."]
19
+ end
20
+ end
21
+
22
+ def relevant_path?(path)
23
+ path.end_with?(".md")
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,9 @@
1
+ module QuietQuality
2
+ module Tools
3
+ module MarkdownLint
4
+ end
5
+ end
6
+ end
7
+
8
+ glob = File.expand_path("../markdown_lint/*.rb", __FILE__)
9
+ Dir.glob(glob).sort.each { |f| require f }
@@ -0,0 +1,55 @@
1
+ require_relative "./base_runner"
2
+
3
+ module QuietQuality
4
+ module Tools
5
+ class RelevantRunner < BaseRunner
6
+ # In general, we don't want to supply a huge number of arguments to a command-line tool.
7
+ # This will probably become configurable later.
8
+ MAX_FILES = 100
9
+
10
+ def invoke!
11
+ @_outcome ||= skip_execution? ? skipped_outcome : performed_outcome
12
+ end
13
+
14
+ def command
15
+ return nil if skip_execution?
16
+ base_command + target_files.sort
17
+ end
18
+
19
+ def relevant_path?(path)
20
+ fail(NoMethodError, "RelevantRunner subclass must implement `relevant_path?`")
21
+ end
22
+
23
+ def base_command
24
+ fail(NoMethodError, "RelevantRunner subclass must implement either `command` or `base_command`")
25
+ end
26
+
27
+ def no_files_output
28
+ fail(NoMethodError, "RelevantRunner subclass must implement `no_files_output`")
29
+ end
30
+
31
+ private
32
+
33
+ def skip_execution?
34
+ changed_files && relevant_files.empty?
35
+ end
36
+
37
+ def relevant_files
38
+ return nil if changed_files.nil?
39
+ changed_files.paths
40
+ .select { |path| relevant_path?(path) }
41
+ .select { |path| file_filter.nil? || file_filter.match?(path) }
42
+ end
43
+
44
+ def target_files
45
+ return [] if changed_files.nil?
46
+ return [] if relevant_files.length > MAX_FILES
47
+ relevant_files
48
+ end
49
+
50
+ def skipped_outcome
51
+ Outcome.new(tool: tool_name, output: no_files_output)
52
+ end
53
+ end
54
+ end
55
+ end
@@ -1,58 +1,21 @@
1
1
  module QuietQuality
2
2
  module Tools
3
3
  module Rspec
4
- class Runner
5
- MAX_FILES = 100
6
- NO_FILES_OUTPUT = '{"examples": [], "summary": {"failure_count": 0}}'
7
-
8
- def initialize(changed_files: nil, file_filter: nil)
9
- @changed_files = changed_files
10
- @file_filter = file_filter
11
- end
12
-
13
- def invoke!
14
- @_outcome ||= skip_execution? ? skipped_outcome : performed_outcome
15
- end
16
-
17
- private
18
-
19
- attr_reader :changed_files, :file_filter
20
-
21
- def skip_execution?
22
- changed_files && relevant_files.empty?
23
- end
24
-
25
- def relevant_files
26
- return nil if changed_files.nil?
27
- changed_files.paths
28
- .select { |path| path.end_with?("_spec.rb") }
29
- .select { |path| file_filter.nil? || file_filter.match?(path) }
30
- end
31
-
32
- def target_files
33
- return [] if changed_files.nil?
34
- return [] if relevant_files.length > MAX_FILES
35
- relevant_files
4
+ class Runner < RelevantRunner
5
+ def tool_name
6
+ :rspec
36
7
  end
37
8
 
38
- def command
39
- return nil if skip_execution?
40
- ["rspec", "-f", "json"] + target_files.sort
9
+ def no_files_output
10
+ '{"examples": [], "summary": {"failure_count": 0}}'
41
11
  end
42
12
 
43
- def skipped_outcome
44
- Outcome.new(tool: :rspec, output: NO_FILES_OUTPUT)
13
+ def base_command
14
+ ["rspec", "-f", "json"]
45
15
  end
46
16
 
47
- def performed_outcome
48
- out, err, stat = Open3.capture3(*command)
49
- if stat.success?
50
- Outcome.new(tool: :rspec, output: out, logging: err)
51
- elsif stat.exitstatus == 1
52
- Outcome.new(tool: :rspec, output: out, logging: err, failure: true)
53
- else
54
- fail(ExecutionError, "Execution of rspec failed with #{stat.exitstatus}")
55
- end
17
+ def relevant_path?(path)
18
+ path.end_with?("_spec.rb")
56
19
  end
57
20
  end
58
21
  end
@@ -1,8 +1,6 @@
1
1
  module QuietQuality
2
2
  module Tools
3
3
  module Rspec
4
- ExecutionError = Class.new(Tools::Error)
5
- ParsingError = Class.new(Tools::Error)
6
4
  end
7
5
  end
8
6
  end
@@ -1,68 +1,21 @@
1
1
  module QuietQuality
2
2
  module Tools
3
3
  module Rubocop
4
- class Runner
5
- MAX_FILES = 100
6
- NO_FILES_OUTPUT = '{"files": [], "summary": {"offense_count": 0}}'
7
-
8
- def command_name
9
- "rubocop"
10
- end
11
-
12
- # Supplying changed_files: nil means "run against all files".
13
- def initialize(changed_files: nil, file_filter: nil)
14
- @changed_files = changed_files
15
- @file_filter = file_filter
16
- end
17
-
18
- def invoke!
19
- @_outcome ||= skip_execution? ? skipped_outcome : performed_outcome
20
- end
21
-
22
- private
23
-
24
- attr_reader :changed_files, :file_filter
25
-
26
- # If we were told that _no files changed_ (which is distinct from not being told that
27
- # any files changed - a [] instead of a nil), then we shouldn't run rubocop at all.
28
- def skip_execution?
29
- changed_files && relevant_files.empty?
30
- end
31
-
32
- # Note: if target_files goes over MAX_FILES, it's _empty_ instead - that means that
33
- # we run against the full repository instead of the specific files (rubocop's behavior
34
- # when no target files are specified)
35
- def command
36
- return nil if skip_execution?
37
- [command_name, "-f", "json"] + target_files.sort
38
- end
39
-
40
- def relevant_files
41
- return nil if changed_files.nil?
42
- changed_files.paths
43
- .select { |path| path.end_with?(".rb") }
44
- .select { |path| file_filter.nil? || file_filter.match?(path) }
4
+ class Runner < RelevantRunner
5
+ def tool_name
6
+ :rubocop
45
7
  end
46
8
 
47
- def target_files
48
- return [] if changed_files.nil?
49
- return [] if relevant_files.length > MAX_FILES
50
- relevant_files
9
+ def no_files_output
10
+ '{"files": [], "summary": {"offense_count": 0}}'
51
11
  end
52
12
 
53
- def skipped_outcome
54
- Outcome.new(tool: command_name.to_sym, output: NO_FILES_OUTPUT)
13
+ def base_command
14
+ ["rubocop", "-f", "json"]
55
15
  end
56
16
 
57
- def performed_outcome
58
- out, err, stat = Open3.capture3(*command)
59
- if stat.success?
60
- Outcome.new(tool: command_name.to_sym, output: out, logging: err)
61
- elsif stat.exitstatus == 1
62
- Outcome.new(tool: command_name.to_sym, output: out, logging: err, failure: true)
63
- else
64
- fail(ExecutionError, "Execution of #{command_name} failed with #{stat.exitstatus}")
65
- end
17
+ def relevant_path?(path)
18
+ path.end_with?(".rb")
66
19
  end
67
20
  end
68
21
  end
@@ -1,8 +1,6 @@
1
1
  module QuietQuality
2
2
  module Tools
3
3
  module Rubocop
4
- ExecutionError = Class.new(Tools::Error)
5
- ParsingError = Class.new(Tools::Error)
6
4
  end
7
5
  end
8
6
  end
@@ -1,9 +1,21 @@
1
1
  module QuietQuality
2
2
  module Tools
3
3
  module Standardrb
4
- class Runner < Rubocop::Runner
5
- def command_name
6
- "standardrb"
4
+ class Runner < RelevantRunner
5
+ def tool_name
6
+ :standardrb
7
+ end
8
+
9
+ def no_files_output
10
+ '{"files": [], "summary": {"offense_count": 0}}'
11
+ end
12
+
13
+ def base_command
14
+ ["standardrb", "-f", "json"]
15
+ end
16
+
17
+ def relevant_path?(path)
18
+ path.end_with?(".rb")
7
19
  end
8
20
  end
9
21
  end
@@ -3,8 +3,6 @@ require_relative "./rubocop"
3
3
  module QuietQuality
4
4
  module Tools
5
5
  module Standardrb
6
- ExecutionError = Class.new(Tools::Error)
7
- ParsingError = Class.new(Tools::Error)
8
6
  end
9
7
  end
10
8
  end
@@ -3,9 +3,14 @@ require "open3"
3
3
  module QuietQuality
4
4
  module Tools
5
5
  Error = Class.new(::QuietQuality::Error)
6
+ ExecutionError = Class.new(Error)
7
+ ParsingError = Class.new(Error)
6
8
  end
7
9
  end
8
10
 
11
+ require_relative "./tools/base_runner"
12
+ require_relative "./tools/relevant_runner"
13
+
9
14
  glob = File.expand_path("../tools/*.rb", __FILE__)
10
15
  Dir.glob(glob).sort.each { |f| require f }
11
16
 
@@ -15,6 +20,7 @@ module QuietQuality
15
20
  AVAILABLE = {
16
21
  brakeman: Brakeman,
17
22
  haml_lint: HamlLint,
23
+ markdown_lint: MarkdownLint,
18
24
  rspec: Rspec,
19
25
  rubocop: Rubocop,
20
26
  standardrb: Standardrb
@@ -1,3 +1,3 @@
1
1
  module QuietQuality
2
- VERSION = "1.1.0"
2
+ VERSION = "1.2.1"
3
3
  end
@@ -66,12 +66,6 @@ module QuietQuality
66
66
 
67
67
  private
68
68
 
69
- def changed_lines_for(diff)
70
- GitDiffParser.parse(diff).flat_map do |parsed_diff|
71
- parsed_diff.changed_line_numbers.to_set
72
- end
73
- end
74
-
75
69
  def committed_changed_files(base, sha)
76
70
  ChangedFiles.new(committed_changes(base, sha))
77
71
  end
@@ -105,11 +99,8 @@ module QuietQuality
105
99
  end
106
100
 
107
101
  def untracked_paths
108
- out, err, stat = Open3.capture3("git", "-C", path, "ls-files", "--others", "--exclude-standard")
109
- unless stat.success?
110
- warn err
111
- fail(Error, "git ls-files failed")
112
- end
102
+ out, _err, stat = Open3.capture3("git", "-C", path, "ls-files", "--others", "--exclude-standard")
103
+ fail(Error, "git ls-files failed") unless stat.success?
113
104
  out.split
114
105
  end
115
106
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: quiet_quality
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.0
4
+ version: 1.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Mueller
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-05-27 00:00:00.000000000 Z
11
+ date: 2023-06-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: git
@@ -166,9 +166,11 @@ files:
166
166
  - ".gitignore"
167
167
  - ".mdl_rules.rb"
168
168
  - ".mdlrc"
169
+ - ".quiet_quality.ci.yml"
169
170
  - ".quiet_quality.yml"
170
171
  - ".rspec"
171
172
  - ".rubocop.yml"
173
+ - CHANGELOG.md
172
174
  - Gemfile
173
175
  - LICENSE
174
176
  - README.md
@@ -183,9 +185,11 @@ files:
183
185
  - lib/quiet_quality/cli.rb
184
186
  - lib/quiet_quality/cli/arg_parser.rb
185
187
  - lib/quiet_quality/cli/entrypoint.rb
188
+ - lib/quiet_quality/cli/presenter.rb
186
189
  - lib/quiet_quality/config.rb
187
190
  - lib/quiet_quality/config/builder.rb
188
191
  - lib/quiet_quality/config/finder.rb
192
+ - lib/quiet_quality/config/logging.rb
189
193
  - lib/quiet_quality/config/options.rb
190
194
  - lib/quiet_quality/config/parsed_options.rb
191
195
  - lib/quiet_quality/config/parser.rb
@@ -195,17 +199,23 @@ files:
195
199
  - lib/quiet_quality/executors/concurrent_executor.rb
196
200
  - lib/quiet_quality/executors/pipeline.rb
197
201
  - lib/quiet_quality/executors/serial_executor.rb
202
+ - lib/quiet_quality/logger.rb
198
203
  - lib/quiet_quality/message.rb
199
204
  - lib/quiet_quality/message_filter.rb
200
205
  - lib/quiet_quality/messages.rb
201
206
  - lib/quiet_quality/tools.rb
207
+ - lib/quiet_quality/tools/base_runner.rb
202
208
  - lib/quiet_quality/tools/brakeman.rb
203
209
  - lib/quiet_quality/tools/brakeman/parser.rb
204
210
  - lib/quiet_quality/tools/brakeman/runner.rb
205
211
  - lib/quiet_quality/tools/haml_lint.rb
206
212
  - lib/quiet_quality/tools/haml_lint/parser.rb
207
213
  - lib/quiet_quality/tools/haml_lint/runner.rb
214
+ - lib/quiet_quality/tools/markdown_lint.rb
215
+ - lib/quiet_quality/tools/markdown_lint/parser.rb
216
+ - lib/quiet_quality/tools/markdown_lint/runner.rb
208
217
  - lib/quiet_quality/tools/outcome.rb
218
+ - lib/quiet_quality/tools/relevant_runner.rb
209
219
  - lib/quiet_quality/tools/rspec.rb
210
220
  - lib/quiet_quality/tools/rspec/parser.rb
211
221
  - lib/quiet_quality/tools/rspec/runner.rb