codemonitor 0.3.3 → 0.4.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: 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