codemonitor 0.3.3 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b2b693d8b21e5c6041765bf54d568d21b7e8e54abeb1695d7937166a6bf9b2b7
4
- data.tar.gz: 3af8eb6ab76db485ddc34a793c04793238b0d92c4523440028dd2809d58c1f50
3
+ metadata.gz: 67bdedaed6063ea891d58825204d5dd31d60498325ee9b4b217d5981947282d7
4
+ data.tar.gz: 2cdf8289ed5be0af4dfbd1acbb0d7d6d77964b81427ae22edf3f4aff1b081d19
5
5
  SHA512:
6
- metadata.gz: 2c0a23989bded3388a21ac6c40c87ed4ed6d18a3f62d9ff804a13e595fb2152eb17dd8955224812ebfac2a71d37a569b1cadf1bf9f46195d87f7576351a6b39d
7
- data.tar.gz: 8ae23e524620fd956835b5bf85699153a579796701c6e605ab7ad1f670f30622b50ee3d1528b2a79f7ef7b3d698e0aae5d8b463a59b96c69b663ba4e77896d22
6
+ metadata.gz: 8fc80f0a516df374fa081ead8d3de1d3a9a58cd320b36ad867e106aa13c123fb9aed0bafa07f9958df4ce1a3a7c78c6451c49cd56fe9dcb225e1635eb61c5c86
7
+ data.tar.gz: 6bc644578c63a242697bea5d17949c610e2ca1b3d1ccda0fcf62999cd3dd77f6d15b44f4dc1dce0cbcd07620dde3bf49e3032f390bb47b91031291017f04b19b
data/Gemfile CHANGED
@@ -2,5 +2,5 @@
2
2
 
3
3
  source 'https://rubygems.org'
4
4
 
5
- # Specify your gem's dependencies in rubocop-changes.gemspec
5
+ # Specify your gem's dependencies in codemonitor.gemspec
6
6
  gemspec
data/Gemfile.lock CHANGED
@@ -1,25 +1,56 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- codemonitor (0.3.0)
4
+ codemonitor (0.4.0)
5
5
  dogapi (~> 1.45)
6
+ octokit (~> 4.0)
6
7
 
7
8
  GEM
8
9
  remote: https://rubygems.org/
9
10
  specs:
11
+ addressable (2.8.0)
12
+ public_suffix (>= 2.0.2, < 5.0)
10
13
  ast (2.4.2)
11
14
  coderay (1.1.3)
12
15
  diff-lcs (1.3)
13
16
  dogapi (1.45.0)
14
17
  multi_json
18
+ faraday (1.9.3)
19
+ faraday-em_http (~> 1.0)
20
+ faraday-em_synchrony (~> 1.0)
21
+ faraday-excon (~> 1.1)
22
+ faraday-httpclient (~> 1.0)
23
+ faraday-multipart (~> 1.0)
24
+ faraday-net_http (~> 1.0)
25
+ faraday-net_http_persistent (~> 1.0)
26
+ faraday-patron (~> 1.0)
27
+ faraday-rack (~> 1.0)
28
+ faraday-retry (~> 1.0)
29
+ ruby2_keywords (>= 0.0.4)
30
+ faraday-em_http (1.0.0)
31
+ faraday-em_synchrony (1.0.0)
32
+ faraday-excon (1.1.0)
33
+ faraday-httpclient (1.0.1)
34
+ faraday-multipart (1.0.3)
35
+ multipart-post (>= 1.2, < 3)
36
+ faraday-net_http (1.0.1)
37
+ faraday-net_http_persistent (1.2.0)
38
+ faraday-patron (1.0.0)
39
+ faraday-rack (1.0.0)
40
+ faraday-retry (1.0.3)
15
41
  method_source (1.0.0)
16
42
  multi_json (1.15.0)
43
+ multipart-post (2.1.1)
44
+ octokit (4.22.0)
45
+ faraday (>= 0.9)
46
+ sawyer (~> 0.8.0, >= 0.5.3)
17
47
  parallel (1.20.1)
18
48
  parser (3.0.1.1)
19
49
  ast (~> 2.4.1)
20
50
  pry (0.13.1)
21
51
  coderay (~> 1.1)
22
52
  method_source (~> 1.0)
53
+ public_suffix (4.0.6)
23
54
  rainbow (3.0.0)
24
55
  rake (13.0.3)
25
56
  regexp_parser (2.1.1)
@@ -49,6 +80,10 @@ GEM
49
80
  rubocop-ast (1.5.0)
50
81
  parser (>= 3.0.1.1)
51
82
  ruby-progressbar (1.11.0)
83
+ ruby2_keywords (0.0.5)
84
+ sawyer (0.8.2)
85
+ addressable (>= 2.3.5)
86
+ faraday (> 0.8, < 2.0)
52
87
  unicode-display_width (1.7.0)
53
88
 
54
89
  PLATFORMS
data/README.md CHANGED
@@ -1,9 +1,134 @@
1
- # CodeMonitor
1
+ # 🖥️ CodeMonitor
2
2
 
3
3
  A engine to collect multiple metrics from your repository and push them to a
4
4
  time series provider.
5
5
 
6
6
 
7
- # Inspiration
7
+ # Engines
8
8
 
9
- https://github.com/scribd/github-action-datadog-reporting
9
+ ## Git
10
+
11
+ Collect multiple metrics from the a Git repository.
12
+
13
+ **Requirements / Setup**:
14
+
15
+ You need a `.git` folder present in the current folder:
16
+
17
+ **Options**:
18
+
19
+ `CODEMONITOR_GIT_FILES_THRESHOLD`: Don't emit metrics about number of files, from those that are above of this threshold. (Default: `0`)
20
+
21
+ ## Eslint
22
+
23
+ Collect multiple metrics from the a project with [Eslint](https://eslint.org/) configured.
24
+
25
+ **Requirements / Setup**:
26
+
27
+ You need a `.eslintrc.js` and a `eslint.output.json` file present in the current folder.
28
+
29
+ You can generate the `eslint.output.json` file with this example command:
30
+
31
+ ```
32
+ eslint -f json -o eslint.output.json
33
+ ```
34
+ **Options**:
35
+
36
+ `CODEMONITOR_ESLINT_THRESHOLD`: Don't emit metrics about eslint rules that are above of this threshold. (Default: `10`)
37
+
38
+
39
+ ## Npm
40
+
41
+ Collect multiple metrics from the a NodeJS.
42
+
43
+ **Requirements / Setup**:
44
+
45
+ You need a `package.json` file present in the current folder.
46
+
47
+ ## Packwerk
48
+
49
+ Collect multiple metrics from the a Ruby project with [Packwerk](https://github.com/Shopify/packwerk) configured.
50
+
51
+ **Requirements / Setup**:
52
+
53
+ You need a `deprecated_references.yml` files present in the current project.
54
+
55
+ ## Rubocop
56
+
57
+ Collect multiple metrics from the a Ruby project with [Rubocop](https://github.com/rubocop/rubocop) configured.
58
+
59
+ **Requirements / Setup**:
60
+
61
+ You need a `.rubocop.yml` and a `rubocop.output.json` file present in the current folder.
62
+
63
+ You can generate the `rubocop.output.json` file with this example command:
64
+
65
+ ```
66
+ bundle exec srb tc --metrics-prefix 'codemetrics' --metrics-file sorbet.output.json
67
+ ```
68
+
69
+ **Options**:
70
+
71
+ `CODEMONITOR_RUBOCOP_THRESHOLD`: Don't emit metrics about rubocop cops that are above of this threshold. (Default: `50`)
72
+
73
+
74
+ ## Semgrep
75
+
76
+ Collect multiple metrics from the a with [Semgrep](https://semgrep.dev/) configured.
77
+
78
+ **Requirements / Setup**:
79
+
80
+ You need a `.semgrep.yml` and a `semgrep.output.json` file present in the current folder.
81
+
82
+ You can generate the `semgrep.output.json` file with this example command:
83
+
84
+ ```
85
+ semgrep --json -o semgrep.output.json
86
+ ```
87
+
88
+ **Options**:
89
+
90
+ `CODEMONITOR_SEMGREP_THRESHOLD`: Don't emit metrics about rubocop cops that are above of this threshold. (Default: `50`)
91
+
92
+ ## Sorbet
93
+
94
+ Collect multiple metrics from the a with [Sorbet](https://sorbet.org/) configured.
95
+
96
+ **Requirements / Setup**:
97
+
98
+ You need a `sorbet.output.json` file present in the current folder.
99
+
100
+ You can generate the `sorbet.output.json` file with this example command:
101
+
102
+ ```
103
+ bundle exec srb tc --metrics-prefix 'codemetrics' --metrics-file sorbet.output.json
104
+ ```
105
+
106
+ ## SCC
107
+
108
+ Collect multiple metrics from [SCC](https://github.com/boyter/scc) configured.
109
+
110
+ **Requirements / Setup**:
111
+
112
+ You need a `scc.output.json` file present in the current folder.
113
+
114
+ You can generate the `scc.output.json` file with this example command:
115
+
116
+ ```
117
+ scc -f json scc.output.json
118
+ ```
119
+
120
+ # Providers
121
+
122
+ ## Console
123
+
124
+ TODO
125
+
126
+ ## Datadog
127
+
128
+ TODO
129
+
130
+ # Contribute
131
+
132
+ This project started as a side project, so I'm sure that is full
133
+ of mistakes and areas to be improve. If you think you can tweak the code to
134
+ make it better, I'll really appreciate a pull request. ;)
data/codemonitor.gemspec CHANGED
@@ -37,6 +37,7 @@ Gem::Specification.new do |spec|
37
37
  spec.require_paths = ['lib']
38
38
 
39
39
  spec.add_runtime_dependency 'dogapi', '~> 1.45'
40
+ spec.add_runtime_dependency 'octokit', '~> 4.0'
40
41
 
41
42
  spec.add_development_dependency 'bundler', '~> 2.0'
42
43
  spec.add_development_dependency 'pry', '~> 0.13.1'
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Engines
4
+ module Custom
5
+ class Extractor
6
+ def call(provider)
7
+ provider.emit(metrics)
8
+ end
9
+
10
+ def requirements?
11
+ custom_files.length.positive?
12
+ end
13
+
14
+ private
15
+
16
+ def custom_files
17
+ Dir.glob('./.codemonitor/*.rb')
18
+ end
19
+
20
+ def metrics
21
+ custom_files.map do |file|
22
+ values = begin
23
+ eval File.read(file)
24
+ rescue SyntaxError => e
25
+ raise "Unable to execute the custom codemonitor script `#{file}` file"
26
+ end
27
+
28
+ raise "Malformed return value from `#{file}` file. It must be a hash of metrics" unless values.is_a?(Hash)
29
+
30
+ values
31
+ end.reduce({}, :merge)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -8,8 +8,6 @@ module Engines
8
8
  ].freeze
9
9
 
10
10
  def call(provider)
11
- return unless requirements?
12
-
13
11
  metrics = METRICS.map do |metric|
14
12
  [metric, send(metric) || 0]
15
13
  end.to_h
@@ -17,12 +15,12 @@ module Engines
17
15
  provider.emit(metrics)
18
16
  end
19
17
 
20
- private
21
-
22
18
  def requirements?
23
19
  true
24
20
  end
25
21
 
22
+ private
23
+
26
24
  def debug_random
27
25
  rand(0..100)
28
26
  end
@@ -16,8 +16,6 @@ module Engines
16
16
  end
17
17
 
18
18
  def call(provider)
19
- return unless requirements?
20
-
21
19
  metrics = METRICS.map do |metric|
22
20
  [metric, send(metric)]
23
21
  end.to_h
@@ -29,16 +27,14 @@ module Engines
29
27
  provider.emit(metrics)
30
28
  end
31
29
 
30
+ def requirements?
31
+ File.exist?('eslint.output.json')
32
+ end
33
+
32
34
  private
33
35
 
34
36
  attr_reader :threshold
35
37
 
36
- def requirements?
37
- # FIXME: Review if this is the only check we can do or there are more.
38
- File.exist?('.eslintrc.js')
39
- end
40
-
41
- # NOTE: This output file must be created by an external command
42
38
  def eslint
43
39
  @eslint ||= JSON.parse(File.read('eslint.output.json'))
44
40
  end
@@ -20,8 +20,6 @@ module Engines
20
20
  end
21
21
 
22
22
  def call(provider)
23
- return unless requirements?
24
-
25
23
  metrics = METRICS.map do |metric|
26
24
  [metric, send(metric)]
27
25
  end.to_h
@@ -32,14 +30,14 @@ module Engines
32
30
  provider.emit(metrics)
33
31
  end
34
32
 
35
- private
36
-
37
- attr_reader :threshold
38
-
39
33
  def requirements?
40
34
  File.exist?('.git')
41
35
  end
42
36
 
37
+ private
38
+
39
+ attr_reader :threshold
40
+
43
41
  def git_number_of_commits
44
42
  Shell.run("git log --format='%h'").lines.count
45
43
  end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'octokit'
4
+ require 'date'
5
+
6
+ Octokit.configure do |c|
7
+ c.auto_paginate = true
8
+ end
9
+
10
+ module Engines
11
+ module Github
12
+ class Extractor
13
+ METRICS = %i[
14
+ github_number_of_open_pull_requests
15
+ github_number_of_lead_time_in_days
16
+ ].freeze
17
+
18
+ def initialize
19
+ @access_token = ENV['GITHUB_TOKEN']
20
+ @repository = ENV['GITHUB_REPOSITORY']
21
+ @since_days = ENV['GITHUB_SINCE_DAYS'] || 30
22
+ end
23
+
24
+ def call(provider)
25
+ metrics = METRICS.map do |metric|
26
+ [metric, send(metric)]
27
+ end.to_h
28
+
29
+ provider.emit(metrics)
30
+ end
31
+
32
+ def requirements?
33
+ !access_token.nil? && !repository.nil?
34
+ end
35
+
36
+ private
37
+
38
+ attr_reader :access_token, :repository, :since_days
39
+
40
+ def github
41
+ @github ||= Octokit::Client.new(access_token: access_token)
42
+ end
43
+
44
+ def since
45
+ (Date.today - since_days).to_time.iso8601
46
+ end
47
+
48
+ def github_number_of_open_pull_requests
49
+ github.issues(repository, state: 'open').length
50
+ end
51
+
52
+ def github_number_of_lead_time_in_days
53
+ diffs = github
54
+ .issues(repository, since: since, state: 'closed')
55
+ .map do |issue|
56
+ next nil if issue[:pull_request][:merged_at].nil? || issue[:created_at].nil?
57
+
58
+ merged_at = Time.at(issue[:pull_request][:merged_at])
59
+ created_at = Time.at(issue[:created_at])
60
+
61
+ merged_at - created_at
62
+ end.reject do |diff|
63
+ diff.nil?
64
+ end
65
+
66
+ value = (diffs.reduce(:+) / diffs.size.to_f / (24 * 60 * 60))
67
+
68
+ value.round(2)
69
+ end
70
+ end
71
+ end
72
+ end
@@ -18,8 +18,6 @@ module Engines
18
18
  ].freeze
19
19
 
20
20
  def call(provider)
21
- return unless requirements?
22
-
23
21
  metrics = METRICS.map do |metric|
24
22
  [metric, send(metric) || 0]
25
23
  end.to_h
@@ -27,12 +25,12 @@ module Engines
27
25
  provider.emit(metrics)
28
26
  end
29
27
 
30
- private
31
-
32
28
  def requirements?
33
29
  File.exist?('package.json')
34
30
  end
35
31
 
32
+ private
33
+
36
34
  def npm_number_of_dependencies
37
35
  npm_package['dependencies'].keys.length
38
36
  end
@@ -13,8 +13,6 @@ module Engines
13
13
  def initialize; end
14
14
 
15
15
  def call(provider)
16
- return unless requirements?
17
-
18
16
  metrics = METRICS.map do |metric|
19
17
  [metric, send(metric)]
20
18
  end.to_h
@@ -22,12 +20,12 @@ module Engines
22
20
  provider.emit(metrics)
23
21
  end
24
22
 
25
- private
26
-
27
23
  def requirements?
28
24
  packwerk_files.length.positive?
29
25
  end
30
26
 
27
+ private
28
+
31
29
  # NOTE: This output file must be created by an external command
32
30
  def packwerk_files
33
31
  Dir.glob('./**/deprecated_references.yml')
@@ -15,8 +15,6 @@ module Engines
15
15
  end
16
16
 
17
17
  def call(provider)
18
- return unless requirements?
19
-
20
18
  metrics = METRICS.map do |metric|
21
19
  [metric, send(metric)]
22
20
  end.to_h
@@ -28,15 +26,14 @@ module Engines
28
26
  provider.emit(metrics)
29
27
  end
30
28
 
29
+ def requirements?
30
+ File.exist?('rubocop.output.json')
31
+ end
32
+
31
33
  private
32
34
 
33
35
  attr_reader :threshold
34
36
 
35
- def requirements?
36
- File.exist?('.rubocop.yml')
37
- end
38
-
39
- # NOTE: This output file must be created by an external command
40
37
  def rubocop
41
38
  @rubocop ||= JSON.parse(File.read('rubocop.output.json'))
42
39
  end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module Engines
6
+ module Scc
7
+ class Extractor
8
+ METRICS = %i[].freeze
9
+ FIELDS = %w[Bytes Lines Code Comment Blank Complexity Count WeightedComplexity]
10
+
11
+ def initialize; end
12
+
13
+ def call(provider)
14
+ metrics = METRICS.map do |metric|
15
+ [metric, send(metric)]
16
+ end.to_h
17
+
18
+ metrics
19
+ .merge!(scc_totals)
20
+ .merge!(scc_by_file_type)
21
+
22
+ provider.emit(metrics)
23
+ end
24
+
25
+ def requirements?
26
+ File.exist?('scc.output.json')
27
+ end
28
+
29
+ private
30
+
31
+ def scc
32
+ @scc ||= JSON.parse(File.read('scc.output.json'))
33
+ end
34
+
35
+ def scc_totals
36
+ scc.each_with_object({}) do |type, totals|
37
+ FIELDS.each do |field|
38
+ key = "scc_total_#{clean(field)}"
39
+ totals[key] = 0 unless totals.key?(key)
40
+
41
+ totals[key] += type[field]
42
+ end
43
+ end
44
+ end
45
+
46
+ def scc_by_file_type
47
+ scc.map do |type|
48
+ FIELDS.map do |field|
49
+ ["scc_type_#{clean(type['Name'])}_#{clean(field)}", type[field]]
50
+ end.to_h
51
+ end.inject(&:merge)
52
+ end
53
+
54
+ def clean(key)
55
+ key.gsub(%r{[-,./ ]}, '_').downcase
56
+ end
57
+ end
58
+ end
59
+ end
@@ -15,8 +15,6 @@ module Engines
15
15
  end
16
16
 
17
17
  def call(provider)
18
- return unless requirements?
19
-
20
18
  metrics = METRICS.map do |metric|
21
19
  [metric, send(metric)]
22
20
  end.to_h
@@ -26,15 +24,14 @@ module Engines
26
24
  provider.emit(metrics)
27
25
  end
28
26
 
27
+ def requirements?
28
+ File.exist?('semgrep.output.json')
29
+ end
30
+
29
31
  private
30
32
 
31
33
  attr_reader :threshold
32
34
 
33
- def requirements?
34
- File.exist?('.semgrep.yml')
35
- end
36
-
37
- # NOTE: This output file must be created by an external command
38
35
  def semgrep
39
36
  @semgrep ||= JSON.parse(File.read('semgrep.output.json'))
40
37
  end
@@ -24,8 +24,6 @@ module Engines
24
24
  def initialize; end
25
25
 
26
26
  def call(provider)
27
- return unless requirements?
28
-
29
27
  metrics = METRICS.map do |metric|
30
28
  [metric, send(metric)]
31
29
  end.to_h
@@ -33,13 +31,12 @@ module Engines
33
31
  provider.emit(metrics)
34
32
  end
35
33
 
36
- private
37
-
38
34
  def requirements?
39
35
  File.exist?('sorbet.output.json')
40
36
  end
41
37
 
42
- # NOTE: This output file must be created by an external command
38
+ private
39
+
43
40
  def sorbet
44
41
  @sorbet ||= JSON.parse(File.read('sorbet.output.json'))
45
42
  end
data/exe/codemonitor CHANGED
@@ -7,11 +7,14 @@ require_relative '../providers/datadog'
7
7
  require_relative '../engines/eslint/extractor'
8
8
  require_relative '../engines/debug/extractor'
9
9
  require_relative '../engines/git/extractor'
10
+ require_relative '../engines/github/extractor'
10
11
  require_relative '../engines/npm/extractor'
11
12
  require_relative '../engines/packwerk/extractor'
12
13
  require_relative '../engines/rubocop/extractor'
13
14
  require_relative '../engines/semgrep/extractor'
14
15
  require_relative '../engines/sorbet/extractor'
16
+ require_relative '../engines/scc/extractor'
17
+ require_relative '../engines/custom/extractor'
15
18
 
16
19
  PROVIDERS = {
17
20
  console: Providers::Console,
@@ -22,11 +25,14 @@ EXTRACTORS = {
22
25
  eslint: Engines::Eslint::Extractor,
23
26
  debug: Engines::Debug::Extractor,
24
27
  git: Engines::Git::Extractor,
28
+ github: Engines::Github::Extractor,
25
29
  npm: Engines::Npm::Extractor,
26
30
  packwerk: Engines::Packwerk::Extractor,
27
31
  rubocop: Engines::Rubocop::Extractor,
28
32
  semgrep: Engines::Semgrep::Extractor,
29
- sorbet: Engines::Sorbet::Extractor
33
+ sorbet: Engines::Sorbet::Extractor,
34
+ scc: Engines::Scc::Extractor,
35
+ custom: Engines::Custom::Extractor
30
36
  }.freeze
31
37
 
32
38
  config_provider = ENV['CODEMONITOR_PROVIDER'] || 'console'
@@ -45,6 +51,8 @@ puts '# process start'
45
51
  extractors
46
52
  .map(&:new)
47
53
  .map do |extractor|
54
+ raise "Requirements not fullfiled in #{extractor.class.name}" unless extractor.requirements?
55
+
48
56
  extractor.call(provider)
49
57
  end
50
58
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CodeMonitor
4
- VERSION = '0.3.3'
4
+ VERSION = '0.4.0'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: codemonitor
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.3
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ferran Basora
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-07-01 00:00:00.000000000 Z
11
+ date: 2022-02-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dogapi
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '1.45'
27
+ - !ruby/object:Gem::Dependency
28
+ name: octokit
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '4.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '4.0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: bundler
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -115,12 +129,15 @@ files:
115
129
  - bin/console
116
130
  - bin/setup
117
131
  - codemonitor.gemspec
132
+ - engines/custom/extractor.rb
118
133
  - engines/debug/extractor.rb
119
134
  - engines/eslint/extractor.rb
120
135
  - engines/git/extractor.rb
136
+ - engines/github/extractor.rb
121
137
  - engines/npm/extractor.rb
122
138
  - engines/packwerk/extractor.rb
123
139
  - engines/rubocop/extractor.rb
140
+ - engines/scc/extractor.rb
124
141
  - engines/semgrep/extractor.rb
125
142
  - engines/sorbet/extractor.rb
126
143
  - exe/codemonitor