diff --git a/.gitlab/ci/global.gitlab-ci.yml b/.gitlab/ci/global.gitlab-ci.yml
index 274629f9c609e3461ed9d20b10a36b72e671afb0..de4b609098d429a05dd2ef233a0ddc5f10a633f0 100644
--- a/.gitlab/ci/global.gitlab-ci.yml
+++ b/.gitlab/ci/global.gitlab-ci.yml
@@ -49,6 +49,14 @@
       - vendor/ruby/
     policy: pull
 
+.danger-review-cache:
+  cache:
+    key: "danger-review-v1"
+    paths:
+      - vendor/ruby/
+      - node_modules/
+    policy: pull
+
 .qa-cache:
   cache:
     key: "qa-v2"
diff --git a/.gitlab/ci/review.gitlab-ci.yml b/.gitlab/ci/review.gitlab-ci.yml
index c18e898dc124588fc297adedd049ae3c319ae2d0..76191a923bf5d293007cc13261521f7e1985d2ea 100644
--- a/.gitlab/ci/review.gitlab-ci.yml
+++ b/.gitlab/ci/review.gitlab-ci.yml
@@ -225,12 +225,22 @@ parallel-spec-reports:
 danger-review:
   extends:
     - .default-retry
-    - .yarn-cache
+    - .danger-review-cache
     - .review:rules:danger
-  image: registry.gitlab.com/gitlab-org/gitlab-build-images:danger
   stage: test
   needs: []
-  script:
+  before_script:
     - source ./scripts/utils.sh
+    - run_timed_command "bundle install --jobs=$(nproc) --path=vendor --retry=3 --quiet --with danger"
     - run_timed_command "retry yarn install --frozen-lockfile"
-    - danger --fail-on-errors=true --verbose
+  script:
+    - run_timed_command "bundle exec danger --fail-on-errors=true --verbose"
+
+update-danger-review-cache:
+  extends:
+    - danger-review
+    - .shared:rules:update-cache
+  stage: prepare
+  script: echo 'Cache is fresh!'
+  cache:
+    policy: push  # We want to rebuild the cache from scratch to ensure stale dependencies are cleaned up.
diff --git a/Dangerfile b/Dangerfile
index 34e0efa027a3dc1a310c4e42a039717f5e8d240a..699be613f2d85ce54b9282e46c14e3c8be9c35d0 100644
--- a/Dangerfile
+++ b/Dangerfile
@@ -1,20 +1,18 @@
 # frozen_string_literal: true
 
-require_relative 'tooling/gitlab_danger'
-require_relative 'tooling/danger/request_helper'
+require 'gitlab-dangerfiles'
 
-Dir["danger/plugins/*.rb"].sort.each { |f| danger.import_plugin(f) }
+Gitlab::Dangerfiles.import_plugins(danger)
+danger.import_plugin('danger/plugins/*.rb')
 
 return if helper.release_automation?
 
-gitlab_danger = GitlabDanger.new(helper.gitlab_helper)
-
-gitlab_danger.rule_names.each do |file|
-  danger.import_dangerfile(path: File.join('danger', file))
+project_helper.rule_names.each do |rule|
+  danger.import_dangerfile(path: File.join('danger', rule))
 end
 
 anything_to_post = status_report.values.any? { |data| data.any? }
 
-if gitlab_danger.ci? && anything_to_post
+if helper.ci? && anything_to_post
   markdown("**If needed, you can retry the [`danger-review` job](#{ENV['CI_JOB_URL']}) that generated this comment.**")
 end
diff --git a/Gemfile b/Gemfile
index dd1054080ea9ee75ff6a15bbf651c6f5a7932eb1..3af41fd1dc51fea6593364fa5b00493e117a3139 100644
--- a/Gemfile
+++ b/Gemfile
@@ -344,7 +344,6 @@ end
 
 group :development do
   gem 'brakeman', '~> 4.2', require: false
-  gem 'danger', '~> 8.0.6', require: false
   gem 'lefthook', '~> 0.7', require: false
 
   gem 'letter_opener_web', '~> 1.3.4'
@@ -399,6 +398,11 @@ group :development, :test do
   gem 'rblineprof', '~> 0.3.6', platform: :mri, require: false
 end
 
+group :development, :test, :danger do
+  gem 'danger-gitlab', '~> 8.0', require: false
+  gem 'gitlab-dangerfiles', '~> 0.8.0', require: false
+end
+
 group :development, :test, :coverage do
   gem 'simplecov', '~> 0.18.5', require: false
   gem 'simplecov-cobertura', '~> 1.3.1', require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index 9a5c0bc10d4e78893c3abac5c5ab832c63576401..5b041f7db67237a3a2aa17c28e4e7c490f6d9c91 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -216,7 +216,7 @@ GEM
     css_parser (1.7.0)
       addressable
     daemons (1.3.1)
-    danger (8.0.6)
+    danger (8.2.3)
       claide (~> 1.0)
       claide-plugins (>= 0.9.2)
       colored2 (~> 3.1)
@@ -228,7 +228,10 @@ GEM
       kramdown-parser-gfm (~> 1.0)
       no_proxy_fix
       octokit (~> 4.7)
-      terminal-table (~> 1)
+      terminal-table (>= 1, < 4)
+    danger-gitlab (8.0.0)
+      danger
+      gitlab (~> 4.2, >= 4.2.0)
     database_cleaner (1.7.0)
     debugger-ruby_core_source (1.3.8)
     deckar01-task_list (2.3.1)
@@ -428,8 +431,13 @@ GEM
     gitaly (13.9.0.pre.rc1)
       grpc (~> 1.0)
     github-markup (1.7.0)
+    gitlab (4.16.1)
+      httparty (~> 0.14, >= 0.14.0)
+      terminal-table (~> 1.5, >= 1.5.1)
     gitlab-chronic (0.10.5)
       numerizer (~> 0.2)
+    gitlab-dangerfiles (0.8.0)
+      danger
     gitlab-experiment (0.5.0)
       activesupport (>= 3.0)
       scientist (~> 1.5, >= 1.5.0)
@@ -1367,7 +1375,7 @@ DEPENDENCIES
   countries (~> 3.0)
   creole (~> 0.5.0)
   crystalball (~> 0.7.0)
-  danger (~> 8.0.6)
+  danger-gitlab (~> 8.0)
   database_cleaner (~> 1.7.0)
   deckar01-task_list (= 2.3.1)
   default_value_for (~> 3.4.0)
@@ -1413,6 +1421,7 @@ DEPENDENCIES
   gitaly (~> 13.9.0.pre.rc1)
   github-markup (~> 1.7.0)
   gitlab-chronic (~> 0.10.5)
+  gitlab-dangerfiles (~> 0.8.0)
   gitlab-experiment (~> 0.5.0)
   gitlab-fog-azure-rm (~> 1.0.1)
   gitlab-fog-google (~> 1.13)
diff --git a/danger/changelog/Dangerfile b/danger/changelog/Dangerfile
index 377f3592d56227334d05474e0f509682e7817f91..444303d4b9ec159d87ea8b631565996b109a2bb6 100644
--- a/danger/changelog/Dangerfile
+++ b/danger/changelog/Dangerfile
@@ -17,8 +17,8 @@ def check_changelog_yaml(path)
   raw_file = File.read(path)
   yaml = YAML.safe_load(raw_file)
 
-  fail "`title` should be set, in #{gitlab.html_link(path)}! #{SEE_DOC}" if yaml["title"].nil?
-  fail "`type` should be set, in #{gitlab.html_link(path)}! #{SEE_DOC}" if yaml["type"].nil?
+  fail "`title` should be set, in #{helper.html_link(path)}! #{SEE_DOC}" if yaml["title"].nil?
+  fail "`type` should be set, in #{helper.html_link(path)}! #{SEE_DOC}" if yaml["type"].nil?
 
   return if helper.security_mr?
 
@@ -30,20 +30,20 @@ def check_changelog_yaml(path)
     if mr_line
       markdown(format(SUGGEST_MR_COMMENT, mr_iid: gitlab.mr_json["iid"]), file: path, line: mr_line.succ)
     else
-      message "Consider setting `merge_request` to #{gitlab.mr_json["iid"]} in #{gitlab.html_link(path)}. #{SEE_DOC}"
+      message "Consider setting `merge_request` to #{gitlab.mr_json["iid"]} in #{helper.html_link(path)}. #{SEE_DOC}"
     end
   elsif yaml["merge_request"] != gitlab.mr_json["iid"] && !cherry_pick_against_stable_branch
     fail "Merge request ID was not set to #{gitlab.mr_json["iid"]}! #{SEE_DOC}"
   end
 rescue Psych::Exception
   # YAML could not be parsed, fail the build.
-  fail "#{gitlab.html_link(path)} isn't valid YAML! #{SEE_DOC}"
+  fail "#{helper.html_link(path)} isn't valid YAML! #{SEE_DOC}"
 rescue StandardError => e
   warn "There was a problem trying to check the Changelog. Exception: #{e.class.name} - #{e.message}"
 end
 
 def check_changelog_path(path)
-  ee_changes = helper.all_ee_changes.dup
+  ee_changes = project_helper.all_ee_changes.dup
   ee_changes.delete(path)
 
   if ee_changes.any? && !changelog.ee_changelog? && !changelog.required?
diff --git a/danger/ci_templates/Dangerfile b/danger/ci_templates/Dangerfile
index 34b4bbff7a5e10c3a17e7d2a0b5c1274bec8e33a..fcd9080e7d1c6347a05253265b33a83eae1e1036 100644
--- a/danger/ci_templates/Dangerfile
+++ b/danger/ci_templates/Dangerfile
@@ -1,7 +1,5 @@
 # frozen_string_literal: true
 
-gitlab_danger = GitlabDanger.new(helper.gitlab_helper)
-
 TEMPLATE_MESSAGE = <<~MSG
 This merge request requires a CI/CD Template review. To make sure these
 changes are reviewed, take the following steps:
@@ -17,9 +15,9 @@ TEMPLATE_FILES_MESSAGE = <<~MSG
 The following files require a review from the CI/CD Templates maintainers:
 MSG
 
-return unless gitlab_danger.ci?
+return unless helper.ci?
 
-template_paths_to_review = helper.changes_by_category[:ci_template]
+template_paths_to_review = project_helper.changes_by_category[:ci_template]
 
 if gitlab.mr_labels.include?('ci::templates') || template_paths_to_review.any?
   message 'This merge request adds or changes files that require a ' \
diff --git a/danger/commit_messages/Dangerfile b/danger/commit_messages/Dangerfile
index 96a0c08c1840d0a18c0b148ca3b7932f5365a163..b4a0b6ad8cb320b5413930b9ada47a6cd3a48bf2 100644
--- a/danger/commit_messages/Dangerfile
+++ b/danger/commit_messages/Dangerfile
@@ -1,7 +1,7 @@
 # frozen_string_literal: true
 
-require_relative File.expand_path('../../tooling/danger/commit_linter', __dir__)
-require_relative File.expand_path('../../tooling/danger/merge_request_linter', __dir__)
+require 'gitlab/dangerfiles/commit_linter'
+require 'gitlab/dangerfiles/merge_request_linter'
 
 COMMIT_MESSAGE_GUIDELINES = "https://docs.gitlab.com/ee/development/contributing/merge_request_workflow.html#commit-messages-guidelines"
 MORE_INFO = "For more information, take a look at our [Commit message guidelines](#{COMMIT_MESSAGE_GUIDELINES})."
@@ -18,10 +18,6 @@ This merge request includes more than %<max_commits_count>d commits. Each commit
 If this merge request contains commits that do not meet this criteria and/or contains intermediate work, please rebase these commits into a smaller number of commits or split this merge request into multiple smaller merge requests.
 MSG
 
-def gitlab_danger
-  @gitlab_danger ||= GitlabDanger.new(helper.gitlab_helper)
-end
-
 def fail_commit(commit, message, more_info: true)
   self.fail(build_message(commit, message, more_info: more_info))
 end
@@ -39,22 +35,22 @@ end
 
 def squash_mr?
   # Locally, we assume the MR is set to be squashed so that the user only sees warnings instead of errors.
-  gitlab_danger.ci? ? gitlab.mr_json['squash'] : true
+  helper.ci? ? gitlab.mr_json['squash'] : true
 end
 
 def wip_mr?
-  gitlab_danger.ci? ? gitlab.mr_json['work_in_progress'] : false
+  helper.ci? ? gitlab.mr_json['work_in_progress'] : false
 end
 
 def danger_job_link
-  gitlab_danger.ci? ? "[#{THE_DANGER_JOB_TEXT}](#{ENV['CI_JOB_URL']})" : THE_DANGER_JOB_TEXT
+  helper.ci? ? "[#{THE_DANGER_JOB_TEXT}](#{ENV['CI_JOB_URL']})" : THE_DANGER_JOB_TEXT
 end
 
 # Perform various checks against commits. We're not using
 # https://github.com/jonallured/danger-commit_lint because its output is not
 # very helpful, and it doesn't offer the means of ignoring merge commits.
 def lint_commit(commit)
-  linter = Tooling::Danger::CommitLinter.new(commit)
+  linter = Gitlab::Dangerfiles::CommitLinter.new(commit)
 
   # For now we'll ignore merge commits, as getting rid of those is a problem
   # separate from enforcing good commit messages.
@@ -93,7 +89,7 @@ end
 def lint_mr_title(mr_title)
   commit = Struct.new(:message, :sha).new(mr_title)
 
-  Tooling::Danger::MergeRequestLinter.new(commit).lint
+  Gitlab::Dangerfiles::MergeRequestLinter.new(commit).lint
 end
 
 def count_non_fixup_commits(commit_linters)
@@ -109,7 +105,7 @@ def lint_commits(commits)
     if multi_line_commit_linter && multi_line_commit_linter.failed?
       warn_or_fail_commits(multi_line_commit_linter)
       commit_linters.delete(multi_line_commit_linter) # Don't show an error (here) and a warning (below)
-    elsif gitlab_danger.ci? # We don't have access to the MR title locally
+    elsif helper.ci? # We don't have access to the MR title locally
       title_linter = lint_mr_title(gitlab.mr_json['title'])
       if title_linter.failed?
         warn_or_fail_commits(title_linter)
diff --git a/danger/database/Dangerfile b/danger/database/Dangerfile
index 67a9b53fe3afa073e6a1b2cd09856265a063972b..af4d6ed513dc0dbdbbbb119cb070c1b7afbe886e 100644
--- a/danger/database/Dangerfile
+++ b/danger/database/Dangerfile
@@ -1,7 +1,5 @@
 # frozen_string_literal: true
 
-gitlab_danger = GitlabDanger.new(helper.gitlab_helper)
-
 SCHEMA_NOT_UPDATED_MESSAGE_SHORT = "New %<migrations>s added but %<schema>s wasn't updated"
 
 SCHEMA_NOT_UPDATED_MESSAGE_FULL = <<~MSG
@@ -35,20 +33,20 @@ geo_db_schema_updated = !git.modified_files.grep(%r{\Aee/db/geo/schema\.rb}).emp
 non_geo_migration_created = !git.added_files.grep(%r{\A(db/(post_)?migrate)/}).empty?
 geo_migration_created = !git.added_files.grep(%r{\Aee/db/geo/(post_)?migrate/}).empty?
 
-format_str = gitlab_danger.ci? ? SCHEMA_NOT_UPDATED_MESSAGE_FULL : SCHEMA_NOT_UPDATED_MESSAGE_SHORT
+format_str = helper.ci? ? SCHEMA_NOT_UPDATED_MESSAGE_FULL : SCHEMA_NOT_UPDATED_MESSAGE_SHORT
 
 if non_geo_migration_created && !non_geo_db_schema_updated
-  warn format(format_str, migrations: 'migrations', schema: gitlab_danger.html_link("db/structure.sql"))
+  warn format(format_str, migrations: 'migrations', schema: helper.html_link("db/structure.sql"))
 end
 
 if geo_migration_created && !geo_db_schema_updated
-  warn format(format_str, migrations: 'Geo migrations', schema: gitlab_danger.html_link("ee/db/geo/schema.rb"))
+  warn format(format_str, migrations: 'Geo migrations', schema: helper.html_link("ee/db/geo/schema.rb"))
 end
 
-return unless gitlab_danger.ci?
+return unless helper.ci?
 return if gitlab.mr_labels.include?(DATABASE_APPROVED_LABEL)
 
-db_paths_to_review = helper.changes_by_category[:database]
+db_paths_to_review = project_helper.changes_by_category[:database]
 
 if gitlab.mr_labels.include?('database') || db_paths_to_review.any?
   message 'This merge request adds or changes files that require a ' \
diff --git a/danger/documentation/Dangerfile b/danger/documentation/Dangerfile
index df7d0337e94f55155d0b74ad8622b17aafd419af..e6fe9fe1c0b7e52ec7f15b049bbe42287ec4ff5c 100644
--- a/danger/documentation/Dangerfile
+++ b/danger/documentation/Dangerfile
@@ -1,13 +1,7 @@
 # frozen_string_literal: true
 
-def gitlab_danger
-  @gitlab_danger ||= GitlabDanger.new(helper.gitlab_helper)
-end
-
 def feature_mr?
-  return false unless helper.gitlab_helper&.mr_labels
-
-  (helper.gitlab_helper.mr_labels & %w[feature::addition feature::enhancement]).any?
+  (helper.mr_labels & %w[feature::addition feature::enhancement]).any?
 end
 
 DOCUMENTATION_UPDATE_MISSING = <<~MSG
@@ -19,7 +13,7 @@ For more information, see:
 - The [definition of done](https://docs.gitlab.com/ee/development/contributing/merge_request_workflow.html#definition-of-done) documentation.
 MSG
 
-docs_paths_to_review = helper.changes_by_category[:docs]
+docs_paths_to_review = project_helper.changes_by_category[:docs]
 
 # Documentation should be updated for feature::addition and feature::enhancement
 if docs_paths_to_review.empty?
@@ -30,7 +24,7 @@ end
 
 message 'This merge request adds or changes documentation files. A review from the Technical Writing team before you merge is **recommended**. Reviews can happen after you merge.'
 
-return unless gitlab_danger.ci?
+return unless helper.ci?
 
 markdown(<<~MARKDOWN)
   ## Documentation review
diff --git a/danger/duplicate_yarn_dependencies/Dangerfile b/danger/duplicate_yarn_dependencies/Dangerfile
index 492b888d00e5535e0ece3fc8cd88d7e30f2963bf..7d4294ce959874e68db94f12eefe89aef2507a4d 100644
--- a/danger/duplicate_yarn_dependencies/Dangerfile
+++ b/danger/duplicate_yarn_dependencies/Dangerfile
@@ -11,7 +11,7 @@ return if duplicate.empty?
 
 warn 'This merge request has introduced duplicated yarn dependencies.'
 
-if GitlabDanger.new(helper.gitlab_helper).ci?
+if helper.ci?
   markdown(<<~MARKDOWN)
     ## Duplicate yarn dependencies
 
diff --git a/danger/eslint/Dangerfile b/danger/eslint/Dangerfile
index 92830bd7706987ea1ded7b9c68897694bba2164e..6564c83188ed642bf7de032bd869a4a044ecf41e 100644
--- a/danger/eslint/Dangerfile
+++ b/danger/eslint/Dangerfile
@@ -13,7 +13,7 @@ return if eslint_candidates.empty?
 
 warn 'This merge request changed files with disabled eslint rules. Please consider fixing them.'
 
-if GitlabDanger.new(helper.gitlab_helper).ci?
+if helper.ci?
   markdown(<<~MARKDOWN)
     ## Disabled eslint rules
 
diff --git a/danger/karma/Dangerfile b/danger/karma/Dangerfile
index cededff5f159a88b4b433855fcd40bd4235d95f6..e05bd86313fc5f80e0a075609aed4366f0d70428 100644
--- a/danger/karma/Dangerfile
+++ b/danger/karma/Dangerfile
@@ -13,7 +13,7 @@ new_karma_files = get_karma_files(git.added_files.to_a)
 
 unless new_karma_files.empty?
 
-  if GitlabDanger.new(helper.gitlab_helper).ci?
+  if helper.ci?
     markdown(<<~MARKDOWN)
     ## New karma spec file
 
@@ -36,7 +36,7 @@ return if changed_karma_files.empty?
 
 warn 'You have edited karma spec files. Please consider migrating them to jest.'
 
-if GitlabDanger.new(helper.gitlab_helper).ci?
+if helper.ci?
   markdown(<<~MARKDOWN)
     ## Edited karma files
 
diff --git a/danger/plugins/changelog.rb b/danger/plugins/changelog.rb
index fd2dad5932a0c61825badd2dde5f493fab9d6caa..02ff405c410a96fb1025555c0f77ad8615f451be 100644
--- a/danger/plugins/changelog.rb
+++ b/danger/plugins/changelog.rb
@@ -3,7 +3,7 @@
 require_relative '../../tooling/danger/changelog'
 
 module Danger
-  class Changelog < Plugin
+  class Changelog < ::Danger::Plugin
     # Put the helper code somewhere it can be tested
     include Tooling::Danger::Changelog
   end
diff --git a/danger/plugins/helper.rb b/danger/plugins/project_helper.rb
similarity index 54%
rename from danger/plugins/helper.rb
rename to danger/plugins/project_helper.rb
index 8602868d81783d96f0920db541faf417b12a4983..07a9de0894a0cd3b4ed75fefd081d0f24eb48a83 100644
--- a/danger/plugins/helper.rb
+++ b/danger/plugins/project_helper.rb
@@ -1,12 +1,12 @@
 # frozen_string_literal: true
 
-require_relative '../../tooling/danger/helper'
+require_relative '../../tooling/danger/project_helper'
 
 module Danger
-  # Common helper functions for our danger scripts. See Tooling::Danger::Helper
+  # Common helper functions for our danger scripts. See Tooling::Danger::ProjectHelper
   # for more details
-  class Helper < Plugin
+  class ProjectHelper < ::Danger::Plugin
     # Put the helper code somewhere it can be tested
-    include Tooling::Danger::Helper
+    include Tooling::Danger::ProjectHelper
   end
 end
diff --git a/danger/plugins/roulette.rb b/danger/plugins/roulette.rb
deleted file mode 100644
index 2aa0132852e355518bbe39accb17fbd6d1cf18f1..0000000000000000000000000000000000000000
--- a/danger/plugins/roulette.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-# frozen_string_literal: true
-
-require_relative '../../tooling/danger/roulette'
-
-module Danger
-  class Roulette < Plugin
-    # Put the helper code somewhere it can be tested
-    include Tooling::Danger::Roulette
-  end
-end
diff --git a/danger/plugins/sidekiq_queues.rb b/danger/plugins/sidekiq_queues.rb
index dd436e5cb2b534a78c8199d4750744c379156b20..d8d55f04e9921c68e1bfad74647b1ef19fd8be1b 100644
--- a/danger/plugins/sidekiq_queues.rb
+++ b/danger/plugins/sidekiq_queues.rb
@@ -3,7 +3,7 @@
 require_relative '../../tooling/danger/sidekiq_queues'
 
 module Danger
-  class SidekiqQueues < Plugin
+  class SidekiqQueues < ::Danger::Plugin
     # Put the helper code somewhere it can be tested
     include Tooling::Danger::SidekiqQueues
   end
diff --git a/danger/prettier/Dangerfile b/danger/prettier/Dangerfile
index ee27aaf9228ebcdcb3d49b39df9aa437c0bbaf97..29637986f1d9014b546fadcd9f8eabed051003ce 100644
--- a/danger/prettier/Dangerfile
+++ b/danger/prettier/Dangerfile
@@ -19,7 +19,7 @@ return if unpretty.empty?
 
 warn 'This merge request changed frontend files without pretty printing them.'
 
-if GitlabDanger.new(helper.gitlab_helper).ci?
+if helper.ci?
   markdown(<<~MARKDOWN)
     ## Pretty print Frontend files
 
diff --git a/danger/roulette/Dangerfile b/danger/roulette/Dangerfile
index 3096ef471dd9a575dbde7eb04a8ca24331eb5222..b46220d7886ee01959bc62d0433cf1f92373e9a2 100644
--- a/danger/roulette/Dangerfile
+++ b/danger/roulette/Dangerfile
@@ -35,7 +35,7 @@ UNKNOWN_FILES_MESSAGE = <<MARKDOWN
 
 These files couldn't be categorised, so Danger was unable to suggest a reviewer.
 Please consider creating a merge request to
-[add support](https://gitlab.com/gitlab-org/gitlab/blob/master/tooling/danger/helper.rb)
+[add support](https://gitlab.com/gitlab-org/gitlab/blob/master/tooling/danger/project_helper.rb)
 for them.
 MARKDOWN
 
@@ -67,7 +67,7 @@ def markdown_row_for_spins(category, spins_array)
   "| #{helper.label_for_category(category)} | #{reviewer_note} | #{maintainer_note} |"
 end
 
-changes = helper.changes_by_category
+changes = project_helper.changes_by_category
 
 # Ignore any files that are known but uncategorized. Prompt for any unknown files
 changes.delete(:none)
@@ -76,10 +76,10 @@ changes.delete(:docs)
 categories = changes.keys - [:unknown]
 
 # Ensure to spin for database reviewer/maintainer when ~database is applied (e.g. to review SQL queries)
-categories << :database if helper.gitlab_helper&.mr_labels&.include?('database') && !categories.include?(:database)
+categories << :database if helper.mr_labels.include?('database') && !categories.include?(:database)
 
 if changes.any?
-  project = helper.project_name
+  project = project_helper.project_name
 
   random_roulette_spins = roulette.spin(project, categories, timezone_experiment: false)
 
diff --git a/danger/specialization_labels/Dangerfile b/danger/specialization_labels/Dangerfile
index ac93eb4c3e1f6d4b3b436b8668710c3d0fbaa9e9..2261fe23e4eb6ed4adb1a0265b41678ece806132 100644
--- a/danger/specialization_labels/Dangerfile
+++ b/danger/specialization_labels/Dangerfile
@@ -1,8 +1,6 @@
 # frozen_string_literal: true
 
-gitlab_danger = GitlabDanger.new(helper.gitlab_helper)
-
-return unless gitlab_danger.ci?
+return unless helper.ci?
 
 SPECIALIZATIONS = {
   database: 'database',
@@ -14,7 +12,7 @@ SPECIALIZATIONS = {
   ci_template: 'ci::templates'
 }.freeze
 
-labels_to_add = helper.changes_by_category.each_with_object([]) do |(category, _changes), memo|
+labels_to_add = project_helper.changes_by_category.each_with_object([]) do |(category, _changes), memo|
   label = SPECIALIZATIONS[category]
 
   memo << label if label && !gitlab.mr_labels.include?(label)
diff --git a/lib/tasks/gitlab_danger.rake b/lib/tasks/gitlab_danger.rake
index 5df4a8ce4f11f687a5409712256c27b431e2c633..ff9464a588a3ce140e8b4435bd7c6d21c5e7e55c 100644
--- a/lib/tasks/gitlab_danger.rake
+++ b/lib/tasks/gitlab_danger.rake
@@ -2,16 +2,16 @@
 
 desc 'Run local Danger rules'
 task :danger_local do
-  require_relative '../../tooling/gitlab_danger'
+  require_relative '../../tooling/danger/project_helper'
   require 'gitlab/popen'
 
-  puts("#{GitlabDanger.local_warning_message}\n")
+  puts("#{Tooling::Danger::ProjectHelper.local_warning_message}\n")
 
   # _status will _always_ be 0, regardless of failure or success :(
   output, _status = Gitlab::Popen.popen(%w{danger dry_run})
 
   if output.empty?
-    puts(GitlabDanger.success_message)
+    puts(Tooling::Danger::ProjectHelper.success_message)
   else
     puts(output)
     exit(1)
diff --git a/spec/tooling/danger/base_linter_spec.rb b/spec/tooling/danger/base_linter_spec.rb
deleted file mode 100644
index 54d8f3dc1f729855c3577b8e57a7affec274927c..0000000000000000000000000000000000000000
--- a/spec/tooling/danger/base_linter_spec.rb
+++ /dev/null
@@ -1,192 +0,0 @@
-# frozen_string_literal: true
-
-require 'rspec-parameterized'
-require_relative 'danger_spec_helper'
-
-require_relative '../../../tooling/danger/base_linter'
-
-RSpec.describe Tooling::Danger::BaseLinter do
-  let(:commit_class) do
-    Struct.new(:message, :sha, :diff_parent)
-  end
-
-  let(:commit_message) { 'A commit message' }
-  let(:commit) { commit_class.new(commit_message, anything, anything) }
-
-  subject(:commit_linter) { described_class.new(commit) }
-
-  describe '#failed?' do
-    context 'with no failures' do
-      it { expect(commit_linter).not_to be_failed }
-    end
-
-    context 'with failures' do
-      before do
-        commit_linter.add_problem(:subject_too_long, described_class.subject_description)
-      end
-
-      it { expect(commit_linter).to be_failed }
-    end
-  end
-
-  describe '#add_problem' do
-    it 'stores messages in #failures' do
-      commit_linter.add_problem(:subject_too_long, '%s')
-
-      expect(commit_linter.problems).to eq({ subject_too_long: described_class.problems_mapping[:subject_too_long] })
-    end
-  end
-
-  shared_examples 'a valid commit' do
-    it 'does not have any problem' do
-      commit_linter.lint_subject
-
-      expect(commit_linter.problems).to be_empty
-    end
-  end
-
-  describe '#lint_subject' do
-    context 'when subject valid' do
-      it_behaves_like 'a valid commit'
-    end
-
-    context 'when subject is too short' do
-      let(:commit_message) { 'A B' }
-
-      it 'adds a problem' do
-        expect(commit_linter).to receive(:add_problem).with(:subject_too_short, described_class.subject_description)
-
-        commit_linter.lint_subject
-      end
-    end
-
-    context 'when subject is too long' do
-      let(:commit_message) { 'A B ' + 'C' * described_class::MAX_LINE_LENGTH }
-
-      it 'adds a problem' do
-        expect(commit_linter).to receive(:add_problem).with(:subject_too_long, described_class.subject_description)
-
-        commit_linter.lint_subject
-      end
-    end
-
-    context 'when ignoring length issues for subject having not-ready wording' do
-      using RSpec::Parameterized::TableSyntax
-
-      let(:final_message) { 'A B C' }
-
-      context 'when used as prefix' do
-        where(prefix: [
-          'WIP: ',
-          'WIP:',
-          'wIp:',
-          '[WIP] ',
-          '[WIP]',
-          '[draft]',
-          '[draft] ',
-          '(draft)',
-          '(draft) ',
-          'draft - ',
-          'draft: ',
-          'draft:',
-          'DRAFT:'
-        ])
-
-        with_them do
-          it 'does not have any problems' do
-            commit_message = prefix + final_message + 'D' * (described_class::MAX_LINE_LENGTH - final_message.size)
-            commit = commit_class.new(commit_message, anything, anything)
-
-            linter = described_class.new(commit).lint_subject
-
-            expect(linter.problems).to be_empty
-          end
-        end
-      end
-
-      context 'when used as suffix' do
-        where(suffix: %w[WIP draft])
-
-        with_them do
-          it 'does not have any problems' do
-            commit_message = final_message + 'D' * (described_class::MAX_LINE_LENGTH - final_message.size) + suffix
-            commit = commit_class.new(commit_message, anything, anything)
-
-            linter = described_class.new(commit).lint_subject
-
-            expect(linter.problems).to be_empty
-          end
-        end
-      end
-    end
-
-    context 'when subject does not have enough words and is too long' do
-      let(:commit_message) { 'A ' + 'B' * described_class::MAX_LINE_LENGTH }
-
-      it 'adds a problem' do
-        expect(commit_linter).to receive(:add_problem).with(:subject_too_short, described_class.subject_description)
-        expect(commit_linter).to receive(:add_problem).with(:subject_too_long, described_class.subject_description)
-
-        commit_linter.lint_subject
-      end
-    end
-
-    context 'when subject starts with lowercase' do
-      let(:commit_message) { 'a B C' }
-
-      it 'adds a problem' do
-        expect(commit_linter).to receive(:add_problem).with(:subject_starts_with_lowercase, described_class.subject_description)
-
-        commit_linter.lint_subject
-      end
-    end
-
-    [
-      '[ci skip] A commit message',
-      '[Ci skip] A commit message',
-      '[API] A commit message',
-      'api: A commit message',
-      'API: A commit message',
-      'API: a commit message',
-      'API: a commit message'
-    ].each do |message|
-      context "when subject is '#{message}'" do
-        let(:commit_message) { message }
-
-        it 'does not add a problem' do
-          expect(commit_linter).not_to receive(:add_problem)
-
-          commit_linter.lint_subject
-        end
-      end
-    end
-
-    [
-      '[ci skip]A commit message',
-      '[Ci skip]  A commit message',
-      '[ci skip] a commit message',
-      'api: a commit message',
-      '! A commit message'
-    ].each do |message|
-      context "when subject is '#{message}'" do
-        let(:commit_message) { message }
-
-        it 'adds a problem' do
-          expect(commit_linter).to receive(:add_problem).with(:subject_starts_with_lowercase, described_class.subject_description)
-
-          commit_linter.lint_subject
-        end
-      end
-    end
-
-    context 'when subject ends with a period' do
-      let(:commit_message) { 'A B C.' }
-
-      it 'adds a problem' do
-        expect(commit_linter).to receive(:add_problem).with(:subject_ends_with_a_period, described_class.subject_description)
-
-        commit_linter.lint_subject
-      end
-    end
-  end
-end
diff --git a/spec/tooling/danger/changelog_spec.rb b/spec/tooling/danger/changelog_spec.rb
index 8d056b8a78e6e0c9af775d3ea7f3aeda9348e660..b74039b3cd156e4ec1738c0b57da094cb060b326 100644
--- a/spec/tooling/danger/changelog_spec.rb
+++ b/spec/tooling/danger/changelog_spec.rb
@@ -1,26 +1,23 @@
 # frozen_string_literal: true
 
-require_relative 'danger_spec_helper'
+require 'gitlab-dangerfiles'
+require 'gitlab/dangerfiles/spec_helper'
 
-require_relative '../../../tooling/danger/helper'
 require_relative '../../../tooling/danger/changelog'
+require_relative '../../../tooling/danger/project_helper'
 
 RSpec.describe Tooling::Danger::Changelog do
-  include DangerSpecHelper
+  include_context "with dangerfile"
 
-  let(:change_class) { Tooling::Danger::Helper::Change }
-  let(:changes_class) { Tooling::Danger::Helper::Changes }
-  let(:changes) { changes_class.new([]) }
-
-  let(:mr_labels) { [] }
-  let(:sanitize_mr_title) { 'Fake Title' }
-
-  let(:fake_helper) { double('fake-helper', changes: changes, mr_iid: 1234, mr_title: sanitize_mr_title, mr_labels: mr_labels) }
-
-  let(:fake_danger) { new_fake_danger.include(described_class) }
+  let(:fake_danger) { DangerSpecHelper.fake_danger.include(described_class) }
+  let(:fake_project_helper) { double('fake-project-helper', helper: fake_helper).tap { |h| h.class.include(Tooling::Danger::ProjectHelper) } }
 
   subject(:changelog) { fake_danger.new(helper: fake_helper) }
 
+  before do
+    allow(changelog).to receive(:project_helper).and_return(fake_project_helper)
+  end
+
   describe '#required_reasons' do
     subject { changelog.required_reasons }
 
@@ -165,7 +162,7 @@
     subject { changelog.modified_text }
 
     context "when title is not changed from sanitization", :aggregate_failures do
-      let(:sanitize_mr_title) { 'Fake Title' }
+      let(:mr_title) { 'Fake Title' }
 
       specify do
         expect(subject).to include('CHANGELOG.md was edited')
@@ -175,7 +172,7 @@
     end
 
     context "when title needs sanitization", :aggregate_failures do
-      let(:sanitize_mr_title) { 'DRAFT: Fake Title' }
+      let(:mr_title) { 'DRAFT: Fake Title' }
 
       specify do
         expect(subject).to include('CHANGELOG.md was edited')
@@ -186,7 +183,7 @@
   end
 
   describe '#required_texts' do
-    let(:sanitize_mr_title) { 'Fake Title' }
+    let(:mr_title) { 'Fake Title' }
 
     subject { changelog.required_texts }
 
@@ -207,7 +204,7 @@
       end
 
       context "when title needs sanitization", :aggregate_failures do
-        let(:sanitize_mr_title) { 'DRAFT: Fake Title' }
+        let(:mr_title) { 'DRAFT: Fake Title' }
 
         it_behaves_like 'changelog required text', :db_changes
       end
@@ -224,7 +221,7 @@
     subject { changelog.optional_text }
 
     context "when title is not changed from sanitization", :aggregate_failures do
-      let(:sanitize_mr_title) { 'Fake Title' }
+      let(:mr_title) { 'Fake Title' }
 
       specify do
         expect(subject).to include('CHANGELOG missing')
@@ -234,7 +231,7 @@
     end
 
     context "when title needs sanitization", :aggregate_failures do
-      let(:sanitize_mr_title) { 'DRAFT: Fake Title' }
+      let(:mr_title) { 'DRAFT: Fake Title' }
 
       specify do
         expect(subject).to include('CHANGELOG missing')
diff --git a/spec/tooling/danger/commit_linter_spec.rb b/spec/tooling/danger/commit_linter_spec.rb
deleted file mode 100644
index 694e524af21c996431f81b7f865a6831341dfa1d..0000000000000000000000000000000000000000
--- a/spec/tooling/danger/commit_linter_spec.rb
+++ /dev/null
@@ -1,241 +0,0 @@
-# frozen_string_literal: true
-
-require 'rspec-parameterized'
-require_relative 'danger_spec_helper'
-
-require_relative '../../../tooling/danger/commit_linter'
-
-RSpec.describe Tooling::Danger::CommitLinter do
-  using RSpec::Parameterized::TableSyntax
-
-  let(:total_files_changed) { 2 }
-  let(:total_lines_changed) { 10 }
-  let(:stats) { { total: { files: total_files_changed, lines: total_lines_changed } } }
-  let(:diff_parent) { Struct.new(:stats).new(stats) }
-  let(:commit_class) do
-    Struct.new(:message, :sha, :diff_parent)
-  end
-
-  let(:commit_message) { 'A commit message' }
-  let(:commit_sha) { 'abcd1234' }
-  let(:commit) { commit_class.new(commit_message, commit_sha, diff_parent) }
-
-  subject(:commit_linter) { described_class.new(commit) }
-
-  describe '#fixup?' do
-    where(:commit_message, :is_fixup) do
-      'A commit message' | false
-      'fixup!' | true
-      'fixup! A commit message' | true
-      'squash!' | true
-      'squash! A commit message' | true
-    end
-
-    with_them do
-      it 'is true when commit message starts with "fixup!" or "squash!"' do
-        expect(commit_linter.fixup?).to be(is_fixup)
-      end
-    end
-  end
-
-  describe '#suggestion?' do
-    where(:commit_message, :is_suggestion) do
-      'A commit message' | false
-      'Apply suggestion to' | true
-      'Apply suggestion to "A commit message"' | true
-    end
-
-    with_them do
-      it 'is true when commit message starts with "Apply suggestion to"' do
-        expect(commit_linter.suggestion?).to be(is_suggestion)
-      end
-    end
-  end
-
-  describe '#merge?' do
-    where(:commit_message, :is_merge) do
-      'A commit message' | false
-      'Merge branch' | true
-      'Merge branch "A commit message"' | true
-    end
-
-    with_them do
-      it 'is true when commit message starts with "Merge branch"' do
-        expect(commit_linter.merge?).to be(is_merge)
-      end
-    end
-  end
-
-  describe '#revert?' do
-    where(:commit_message, :is_revert) do
-      'A commit message' | false
-      'Revert' | false
-      'Revert "' | true
-      'Revert "A commit message"' | true
-    end
-
-    with_them do
-      it 'is true when commit message starts with "Revert \""' do
-        expect(commit_linter.revert?).to be(is_revert)
-      end
-    end
-  end
-
-  describe '#multi_line?' do
-    where(:commit_message, :is_multi_line) do
-      "A commit message" | false
-      "A commit message\n" | false
-      "A commit message\n\n" | false
-      "A commit message\n\nSigned-off-by: User Name <user@name.me>" | false
-      "A commit message\n\nWith details" | true
-    end
-
-    with_them do
-      it 'is true when commit message contains details' do
-        expect(commit_linter.multi_line?).to be(is_multi_line)
-      end
-    end
-  end
-
-  shared_examples 'a valid commit' do
-    it 'does not have any problem' do
-      commit_linter.lint
-
-      expect(commit_linter.problems).to be_empty
-    end
-  end
-
-  describe '#lint' do
-    describe 'separator' do
-      context 'when separator is missing' do
-        let(:commit_message) { "A B C\n" }
-
-        it_behaves_like 'a valid commit'
-      end
-
-      context 'when separator is a blank line' do
-        let(:commit_message) { "A B C\n\nMore details." }
-
-        it_behaves_like 'a valid commit'
-      end
-
-      context 'when separator is missing' do
-        let(:commit_message) { "A B C\nMore details." }
-
-        it 'adds a problem' do
-          expect(commit_linter).to receive(:add_problem).with(:separator_missing)
-
-          commit_linter.lint
-        end
-      end
-    end
-
-    describe 'details' do
-      context 'when details are valid' do
-        let(:commit_message) { "A B C\n\nMore details." }
-
-        it_behaves_like 'a valid commit'
-      end
-
-      context 'when no details are given and many files are changed' do
-        let(:total_files_changed) { described_class::MAX_CHANGED_FILES_IN_COMMIT + 1 }
-
-        it_behaves_like 'a valid commit'
-      end
-
-      context 'when no details are given and many lines are changed' do
-        let(:total_lines_changed) { described_class::MAX_CHANGED_LINES_IN_COMMIT + 1 }
-
-        it_behaves_like 'a valid commit'
-      end
-
-      context 'when no details are given and many files and lines are changed' do
-        let(:total_files_changed) { described_class::MAX_CHANGED_FILES_IN_COMMIT + 1 }
-        let(:total_lines_changed) { described_class::MAX_CHANGED_LINES_IN_COMMIT + 1 }
-
-        it 'adds a problem' do
-          expect(commit_linter).to receive(:add_problem).with(:details_too_many_changes)
-
-          commit_linter.lint
-        end
-      end
-
-      context 'when details exceeds the max line length' do
-        let(:commit_message) { "A B C\n\n" + 'D' * (described_class::MAX_LINE_LENGTH + 1) }
-
-        it 'adds a problem' do
-          expect(commit_linter).to receive(:add_problem).with(:details_line_too_long)
-
-          commit_linter.lint
-        end
-      end
-
-      context 'when details exceeds the max line length including URLs' do
-        let(:commit_message) do
-          "A B C\n\nsome message with https://example.com and https://gitlab.com" + 'D' * described_class::MAX_LINE_LENGTH
-        end
-
-        it_behaves_like 'a valid commit'
-      end
-    end
-
-    describe 'message' do
-      context 'when message includes a text emoji' do
-        let(:commit_message) { "A commit message :+1:" }
-
-        it 'adds a problem' do
-          expect(commit_linter).to receive(:add_problem).with(:message_contains_text_emoji)
-
-          commit_linter.lint
-        end
-      end
-
-      context 'when message includes a unicode emoji' do
-        let(:commit_message) { "A commit message 🚀" }
-
-        it 'adds a problem' do
-          expect(commit_linter).to receive(:add_problem).with(:message_contains_unicode_emoji)
-
-          commit_linter.lint
-        end
-      end
-
-      context 'when message includes a value that is surrounded by backticks' do
-        let(:commit_message) { "A commit message `%20`" }
-
-        it 'does not add a problem' do
-          expect(commit_linter).not_to receive(:add_problem)
-
-          commit_linter.lint
-        end
-      end
-
-      context 'when message includes a short reference' do
-        [
-          'A commit message to fix #1234',
-          'A commit message to fix !1234',
-          'A commit message to fix &1234',
-          'A commit message to fix %1234',
-          'A commit message to fix gitlab#1234',
-          'A commit message to fix gitlab!1234',
-          'A commit message to fix gitlab&1234',
-          'A commit message to fix gitlab%1234',
-          'A commit message to fix gitlab-org/gitlab#1234',
-          'A commit message to fix gitlab-org/gitlab!1234',
-          'A commit message to fix gitlab-org/gitlab&1234',
-          'A commit message to fix gitlab-org/gitlab%1234',
-          'A commit message to fix "gitlab-org/gitlab%1234"',
-          'A commit message to fix `gitlab-org/gitlab%1234'
-        ].each do |message|
-          let(:commit_message) { message }
-
-          it 'adds a problem' do
-            expect(commit_linter).to receive(:add_problem).with(:message_contains_short_reference)
-
-            commit_linter.lint
-          end
-        end
-      end
-    end
-  end
-end
diff --git a/spec/tooling/danger/danger_spec_helper.rb b/spec/tooling/danger/danger_spec_helper.rb
deleted file mode 100644
index b1e84b3c13de8ad3cbac472a4b3f37bf1e087543..0000000000000000000000000000000000000000
--- a/spec/tooling/danger/danger_spec_helper.rb
+++ /dev/null
@@ -1,17 +0,0 @@
-# frozen_string_literal: true
-
-module DangerSpecHelper
-  def new_fake_danger
-    Class.new do
-      attr_reader :git, :gitlab, :helper
-
-      # rubocop:disable Gitlab/ModuleWithInstanceVariables
-      def initialize(git: nil, gitlab: nil, helper: nil)
-        @git = git
-        @gitlab = gitlab
-        @helper = helper
-      end
-      # rubocop:enable Gitlab/ModuleWithInstanceVariables
-    end
-  end
-end
diff --git a/spec/tooling/danger/emoji_checker_spec.rb b/spec/tooling/danger/emoji_checker_spec.rb
deleted file mode 100644
index bbd957b3d00d7192e2f98beab7348461bb4c53a1..0000000000000000000000000000000000000000
--- a/spec/tooling/danger/emoji_checker_spec.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-# frozen_string_literal: true
-
-require 'rspec-parameterized'
-
-require_relative '../../../tooling/danger/emoji_checker'
-
-RSpec.describe Tooling::Danger::EmojiChecker do
-  using RSpec::Parameterized::TableSyntax
-
-  describe '#includes_text_emoji?' do
-    where(:text, :includes_emoji) do
-      'Hello World!' | false
-      ':+1:' | true
-      'Hello World! :+1:' | true
-    end
-
-    with_them do
-      it 'is true when text includes a text emoji' do
-        expect(subject.includes_text_emoji?(text)).to be(includes_emoji)
-      end
-    end
-  end
-
-  describe '#includes_unicode_emoji?' do
-    where(:text, :includes_emoji) do
-      'Hello World!' | false
-      '🚀' | true
-      'Hello World! 🚀' | true
-    end
-
-    with_them do
-      it 'is true when text includes a text emoji' do
-        expect(subject.includes_unicode_emoji?(text)).to be(includes_emoji)
-      end
-    end
-  end
-end
diff --git a/spec/tooling/danger/feature_flag_spec.rb b/spec/tooling/danger/feature_flag_spec.rb
index db63116cc3795c7327cd4533092f226b8c1cfa8d..5e495cd43c68758d9f159d065df5b7c8d53a689d 100644
--- a/spec/tooling/danger/feature_flag_spec.rb
+++ b/spec/tooling/danger/feature_flag_spec.rb
@@ -1,29 +1,16 @@
 # frozen_string_literal: true
 
-require_relative 'danger_spec_helper'
+require 'gitlab-dangerfiles'
+require 'gitlab/dangerfiles/spec_helper'
 
 require_relative '../../../tooling/danger/feature_flag'
 
 RSpec.describe Tooling::Danger::FeatureFlag do
-  include DangerSpecHelper
+  include_context "with dangerfile"
 
-  let(:added_files) { nil }
-  let(:modified_files) { nil }
-  let(:deleted_files) { nil }
-  let(:fake_git) { double('fake-git', added_files: added_files, modified_files: modified_files, deleted_files: deleted_files) }
+  let(:fake_danger) { DangerSpecHelper.fake_danger.include(described_class) }
 
-  let(:mr_labels) { nil }
-  let(:mr_json) { nil }
-  let(:fake_gitlab) { double('fake-gitlab', mr_labels: mr_labels, mr_json: mr_json) }
-
-  let(:changes_by_category) { nil }
-  let(:sanitize_mr_title) { nil }
-  let(:ee?) { false }
-  let(:fake_helper) { double('fake-helper', changes_by_category: changes_by_category, sanitize_mr_title: sanitize_mr_title, ee?: ee?) }
-
-  let(:fake_danger) { new_fake_danger.include(described_class) }
-
-  subject(:feature_flag) { fake_danger.new(git: fake_git, gitlab: fake_gitlab, helper: fake_helper) }
+  subject(:feature_flag) { fake_danger.new(git: fake_git) }
 
   describe '#feature_flag_files' do
     let(:feature_flag_files) do
diff --git a/spec/tooling/danger/helper_spec.rb b/spec/tooling/danger/helper_spec.rb
deleted file mode 100644
index 9783705dca04891e534dd9807b995da1385d9949..0000000000000000000000000000000000000000
--- a/spec/tooling/danger/helper_spec.rb
+++ /dev/null
@@ -1,781 +0,0 @@
-# frozen_string_literal: true
-
-require 'fast_spec_helper'
-require 'rspec-parameterized'
-require_relative 'danger_spec_helper'
-
-require_relative '../../../tooling/danger/helper'
-
-RSpec.describe Tooling::Danger::Helper do
-  using RSpec::Parameterized::TableSyntax
-  include DangerSpecHelper
-
-  let(:mr_author) { nil }
-  let(:fake_gitlab) { double('fake-gitlab', mr_author: mr_author) }
-
-  let(:fake_danger) { new_fake_danger.include(described_class) }
-
-  let(:added_files) { %w[added1] }
-  let(:modified_files) { %w[modified1] }
-  let(:deleted_files) { %w[deleted1] }
-  let(:renamed_before_file) { 'renamed_before' }
-  let(:renamed_after_file) { 'renamed_after' }
-  let(:renamed_files) { [{ before: renamed_before_file, after: renamed_after_file }] }
-
-  let(:fake_git) { double('fake-git') }
-
-  before do
-    allow(fake_git).to receive(:added_files) { added_files }
-    allow(fake_git).to receive(:modified_files) { modified_files }
-    allow(fake_git).to receive(:deleted_files) { deleted_files }
-    allow(fake_git).to receive(:renamed_files).at_least(:twice) { renamed_files }
-  end
-
-  subject(:helper) { fake_danger.new(git: fake_git, gitlab: fake_gitlab) }
-
-  describe '#gitlab_helper' do
-    context 'when gitlab helper is not available' do
-      let(:fake_gitlab) { nil }
-
-      it 'returns nil' do
-        expect(helper.gitlab_helper).to be_nil
-      end
-    end
-
-    context 'when gitlab helper is available' do
-      it 'returns the gitlab helper' do
-        expect(helper.gitlab_helper).to eq(fake_gitlab)
-      end
-    end
-
-    context 'when danger gitlab plugin is not available' do
-      it 'returns nil' do
-        invalid_danger = Class.new do
-          include Tooling::Danger::Helper
-        end.new
-
-        expect(invalid_danger.gitlab_helper).to be_nil
-      end
-    end
-  end
-
-  describe '#release_automation?' do
-    context 'when gitlab helper is not available' do
-      it 'returns false' do
-        expect(helper.release_automation?).to be_falsey
-      end
-    end
-
-    context 'when gitlab helper is available' do
-      context "but the MR author isn't the RELEASE_TOOLS_BOT" do
-        let(:mr_author) { 'johnmarston' }
-
-        it 'returns false' do
-          expect(helper.release_automation?).to be_falsey
-        end
-      end
-
-      context 'and the MR author is the RELEASE_TOOLS_BOT' do
-        let(:mr_author) { described_class::RELEASE_TOOLS_BOT }
-
-        it 'returns true' do
-          expect(helper.release_automation?).to be_truthy
-        end
-      end
-    end
-  end
-
-  describe '#all_changed_files' do
-    subject { helper.all_changed_files }
-
-    it 'interprets a list of changes from the danger git plugin' do
-      expect(fake_git).to receive(:added_files) { %w[a b c.old] }
-      expect(fake_git).to receive(:modified_files) { %w[d e] }
-      expect(fake_git)
-        .to receive(:renamed_files)
-        .at_least(:once)
-        .and_return([{ before: 'c.old', after: 'c.new' }])
-
-      is_expected.to contain_exactly('a', 'b', 'c.new', 'd', 'e')
-    end
-  end
-
-  describe '#changed_lines' do
-    subject { helper.changed_lines('changed_file.rb') }
-
-    before do
-      allow(fake_git).to receive(:diff_for_file).with('changed_file.rb').and_return(diff)
-    end
-
-    context 'when file has diff' do
-      let(:diff) { double(:diff, patch: "+ # New change here\n+ # New change there") }
-
-      it 'returns file changes' do
-        is_expected.to eq(['+ # New change here', '+ # New change there'])
-      end
-    end
-
-    context 'when file has no diff (renamed without changes)' do
-      let(:diff) { nil }
-
-      it 'returns a blank array' do
-        is_expected.to eq([])
-      end
-    end
-  end
-
-  describe "changed_files" do
-    it 'returns list of changed files matching given regex' do
-      expect(helper).to receive(:all_changed_files).and_return(%w[migration.rb usage_data.rb])
-
-      expect(helper.changed_files(/usage_data/)).to contain_exactly('usage_data.rb')
-    end
-  end
-
-  describe '#all_ee_changes' do
-    subject { helper.all_ee_changes }
-
-    it 'returns all changed files starting with ee/' do
-      expect(helper).to receive(:all_changed_files).and_return(%w[fr/ee/beer.rb ee/wine.rb ee/lib/ido.rb ee.k])
-
-      is_expected.to match_array(%w[ee/wine.rb ee/lib/ido.rb])
-    end
-  end
-
-  describe '#ee?' do
-    subject { helper.ee? }
-
-    it 'returns true if CI_PROJECT_NAME if set to gitlab' do
-      stub_env('CI_PROJECT_NAME', 'gitlab')
-      expect(Dir).not_to receive(:exist?)
-
-      is_expected.to be_truthy
-    end
-
-    it 'delegates to CHANGELOG-EE.md existence if CI_PROJECT_NAME is set to something else' do
-      stub_env('CI_PROJECT_NAME', 'something else')
-      expect(Dir).to receive(:exist?).with(File.expand_path('../../../../ee', __dir__)) { true }
-
-      is_expected.to be_truthy
-    end
-
-    it 'returns true if ee exists' do
-      stub_env('CI_PROJECT_NAME', nil)
-      expect(Dir).to receive(:exist?).with(File.expand_path('../../../../ee', __dir__)) { true }
-
-      is_expected.to be_truthy
-    end
-
-    it "returns false if ee doesn't exist" do
-      stub_env('CI_PROJECT_NAME', nil)
-      expect(Dir).to receive(:exist?).with(File.expand_path('../../../../ee', __dir__)) { false }
-
-      is_expected.to be_falsy
-    end
-  end
-
-  describe '#project_name' do
-    subject { helper.project_name }
-
-    it 'returns gitlab if ee? returns true' do
-      expect(helper).to receive(:ee?) { true }
-
-      is_expected.to eq('gitlab')
-    end
-
-    it 'returns gitlab-ce if ee? returns false' do
-      expect(helper).to receive(:ee?) { false }
-
-      is_expected.to eq('gitlab-foss')
-    end
-  end
-
-  describe '#markdown_list' do
-    it 'creates a markdown list of items' do
-      items = %w[a b]
-
-      expect(helper.markdown_list(items)).to eq("* `a`\n* `b`")
-    end
-
-    it 'wraps items in <details> when there are more than 10 items' do
-      items = ('a'..'k').to_a
-
-      expect(helper.markdown_list(items)).to match(%r{<details>[^<]+</details>})
-    end
-  end
-
-  describe '#changes_by_category' do
-    let(:added_files) { %w[foo foo.md foo.rb foo.js] }
-    let(:modified_files) { %w[db/migrate/foo lib/gitlab/database/foo.rb] }
-    let(:renamed_files) { [{ before: '', after: 'qa/foo' }, { before: '', after: 'ee/changelogs/foo.yml' }] }
-
-    it 'categorizes changed files' do
-      expect(helper.changes_by_category).to eq(
-        backend: %w[foo.rb],
-        database: %w[db/migrate/foo lib/gitlab/database/foo.rb],
-        frontend: %w[foo.js],
-        migration: %w[db/migrate/foo],
-        none: %w[ee/changelogs/foo.yml foo.md],
-        qa: %w[qa/foo],
-        unknown: %w[foo]
-      )
-    end
-  end
-
-  describe 'Tooling::Danger::Helper::Changes', :aggregate_failures do
-    let(:added_files) { %w[db/migrate/foo ee/changelogs/unreleased/foo.yml] }
-
-    describe '#has_category?' do
-      it 'returns true when changes include given category, false otherwise' do
-        changes = helper.changes
-
-        expect(changes.has_category?(:migration)).to eq(true)
-        expect(changes.has_category?(:changelog)).to eq(true)
-        expect(changes.has_category?(:backend)).to eq(false)
-      end
-    end
-
-    describe '#by_category' do
-      it 'returns an array of Change objects' do
-        expect(helper.changes.by_category(:migration)).to all(be_an(described_class::Change))
-      end
-
-      it 'returns an array of Change objects with the given category' do
-        changes = helper.changes
-
-        expect(changes.by_category(:migration).files).to eq(['db/migrate/foo'])
-        expect(changes.by_category(:changelog).files).to eq(['ee/changelogs/unreleased/foo.yml'])
-        expect(changes.by_category(:backend)).to be_empty
-      end
-    end
-
-    describe '#categories' do
-      it 'returns an array of category symbols' do
-        expect(helper.changes.categories).to contain_exactly(:database, :migration, :changelog, :unknown)
-      end
-    end
-
-    describe '#files' do
-      it 'returns an array of files' do
-        expect(helper.changes.files).to include(*added_files)
-      end
-    end
-  end
-
-  describe '#changes' do
-    it 'returns an array of Change objects' do
-      expect(helper.changes).to all(be_an(described_class::Change))
-    end
-
-    it 'groups changes by change type' do
-      changes = helper.changes
-
-      expect(changes.added.files).to eq(added_files)
-      expect(changes.modified.files).to eq(modified_files)
-      expect(changes.deleted.files).to eq(deleted_files)
-      expect(changes.renamed_before.files).to eq([renamed_before_file])
-      expect(changes.renamed_after.files).to eq([renamed_after_file])
-    end
-  end
-
-  describe '#categories_for_file' do
-    before do
-      allow(fake_git).to receive(:diff_for_file).with('usage_data.rb') { double(:diff, patch: "+ count(User.active)") }
-    end
-
-    where(:path, :expected_categories) do
-      'usage_data.rb'   | [:database, :backend]
-      'doc/foo.md'      | [:docs]
-      'CONTRIBUTING.md' | [:docs]
-      'LICENSE'         | [:docs]
-      'MAINTENANCE.md'  | [:docs]
-      'PHILOSOPHY.md'   | [:docs]
-      'PROCESS.md'      | [:docs]
-      'README.md'       | [:docs]
-
-      'ee/doc/foo'      | [:unknown]
-      'ee/README'       | [:unknown]
-
-      'app/assets/foo'       | [:frontend]
-      'app/views/foo'        | [:frontend]
-      'public/foo'           | [:frontend]
-      'scripts/frontend/foo' | [:frontend]
-      'spec/javascripts/foo' | [:frontend]
-      'spec/frontend/bar'    | [:frontend]
-      'vendor/assets/foo'    | [:frontend]
-      'babel.config.js'      | [:frontend]
-      'jest.config.js'       | [:frontend]
-      'package.json'         | [:frontend]
-      'yarn.lock'            | [:frontend]
-      'config/foo.js'        | [:frontend]
-      'config/deep/foo.js'   | [:frontend]
-
-      'ee/app/assets/foo'       | [:frontend]
-      'ee/app/views/foo'        | [:frontend]
-      'ee/spec/javascripts/foo' | [:frontend]
-      'ee/spec/frontend/bar'    | [:frontend]
-
-      '.gitlab/ci/frontend.gitlab-ci.yml' | %i[frontend engineering_productivity]
-
-      'app/models/foo'             | [:backend]
-      'bin/foo'                    | [:backend]
-      'config/foo'                 | [:backend]
-      'lib/foo'                    | [:backend]
-      'rubocop/foo'                | [:backend]
-      '.rubocop.yml'               | [:backend]
-      '.rubocop_todo.yml'          | [:backend]
-      '.rubocop_manual_todo.yml'   | [:backend]
-      'spec/foo'                   | [:backend]
-      'spec/foo/bar'               | [:backend]
-
-      'ee/app/foo'      | [:backend]
-      'ee/bin/foo'      | [:backend]
-      'ee/spec/foo'     | [:backend]
-      'ee/spec/foo/bar' | [:backend]
-
-      'spec/features/foo'                            | [:test]
-      'ee/spec/features/foo'                         | [:test]
-      'spec/support/shared_examples/features/foo'    | [:test]
-      'ee/spec/support/shared_examples/features/foo' | [:test]
-      'spec/support/shared_contexts/features/foo'    | [:test]
-      'ee/spec/support/shared_contexts/features/foo' | [:test]
-      'spec/support/helpers/features/foo'            | [:test]
-      'ee/spec/support/helpers/features/foo'         | [:test]
-
-      'generator_templates/foo' | [:backend]
-      'vendor/languages.yml'    | [:backend]
-      'file_hooks/examples/'    | [:backend]
-
-      'Gemfile'        | [:backend]
-      'Gemfile.lock'   | [:backend]
-      'Rakefile'       | [:backend]
-      'FOO_VERSION'    | [:backend]
-
-      'Dangerfile'                                            | [:engineering_productivity]
-      'danger/commit_messages/Dangerfile'                     | [:engineering_productivity]
-      'ee/danger/commit_messages/Dangerfile'                  | [:engineering_productivity]
-      'danger/commit_messages/'                               | [:engineering_productivity]
-      'ee/danger/commit_messages/'                            | [:engineering_productivity]
-      '.gitlab-ci.yml'                                        | [:engineering_productivity]
-      '.gitlab/ci/cng.gitlab-ci.yml'                          | [:engineering_productivity]
-      '.gitlab/ci/ee-specific-checks.gitlab-ci.yml'           | [:engineering_productivity]
-      'scripts/foo'                                           | [:engineering_productivity]
-      'tooling/danger/foo'                                    | [:engineering_productivity]
-      'ee/tooling/danger/foo'                                 | [:engineering_productivity]
-      'lefthook.yml'                                          | [:engineering_productivity]
-      '.editorconfig'                                         | [:engineering_productivity]
-      'tooling/bin/find_foss_tests'                           | [:engineering_productivity]
-      '.codeclimate.yml'                                      | [:engineering_productivity]
-      '.gitlab/CODEOWNERS'                                    | [:engineering_productivity]
-
-      'lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml'   | [:ci_template]
-      'lib/gitlab/ci/templates/dotNET-Core.yml'               | [:ci_template]
-
-      'ee/FOO_VERSION' | [:unknown]
-
-      'db/schema.rb'                                              | [:database]
-      'db/structure.sql'                                          | [:database]
-      'db/migrate/foo'                                            | [:database, :migration]
-      'db/post_migrate/foo'                                       | [:database, :migration]
-      'ee/db/geo/migrate/foo'                                     | [:database, :migration]
-      'ee/db/geo/post_migrate/foo'                                | [:database, :migration]
-      'app/models/project_authorization.rb'                       | [:database]
-      'app/services/users/refresh_authorized_projects_service.rb' | [:database]
-      'lib/gitlab/background_migration.rb'                        | [:database]
-      'lib/gitlab/background_migration/foo'                       | [:database]
-      'ee/lib/gitlab/background_migration/foo'                    | [:database]
-      'lib/gitlab/database.rb'                                    | [:database]
-      'lib/gitlab/database/foo'                                   | [:database]
-      'ee/lib/gitlab/database/foo'                                | [:database]
-      'lib/gitlab/github_import.rb'                               | [:database]
-      'lib/gitlab/github_import/foo'                              | [:database]
-      'lib/gitlab/sql/foo'                                        | [:database]
-      'rubocop/cop/migration/foo'                                 | [:database]
-
-      'db/fixtures/foo.rb'                                 | [:backend]
-      'ee/db/fixtures/foo.rb'                              | [:backend]
-
-      'qa/foo' | [:qa]
-      'ee/qa/foo' | [:qa]
-
-      'changelogs/foo'    | [:none]
-      'ee/changelogs/foo' | [:none]
-      'locale/gitlab.pot' | [:none]
-
-      'FOO'          | [:unknown]
-      'foo'          | [:unknown]
-
-      'foo/bar.rb'  | [:backend]
-      'foo/bar.js'  | [:frontend]
-      'foo/bar.txt' | [:none]
-      'foo/bar.md'  | [:none]
-    end
-
-    with_them do
-      subject { helper.categories_for_file(path) }
-
-      it { is_expected.to eq(expected_categories) }
-    end
-
-    context 'having specific changes' do
-      where(:expected_categories, :patch, :changed_files) do
-        [:database, :backend] | '+ count(User.active)'                         | ['usage_data.rb', 'lib/gitlab/usage_data.rb', 'ee/lib/ee/gitlab/usage_data.rb']
-        [:database, :backend] | '+ estimate_batch_distinct_count(User.active)' | ['usage_data.rb']
-        [:backend]            | '+ alt_usage_data(User.active)'                | ['usage_data.rb']
-        [:backend]            | '+ count(User.active)'                         | ['user.rb']
-        [:backend]            | '+ count(User.active)'                         | ['usage_data/topology.rb']
-        [:backend]            | '+ foo_count(User.active)'                     | ['usage_data.rb']
-      end
-
-      with_them do
-        it 'has the correct categories' do
-          changed_files.each do |file|
-            allow(fake_git).to receive(:diff_for_file).with(file) { double(:diff, patch: patch) }
-
-            expect(helper.categories_for_file(file)).to eq(expected_categories)
-          end
-        end
-      end
-    end
-  end
-
-  describe '#label_for_category' do
-    where(:category, :expected_label) do
-      :backend   | '~backend'
-      :database  | '~database'
-      :docs      | '~documentation'
-      :foo       | '~foo'
-      :frontend  | '~frontend'
-      :none      | ''
-      :qa        | '~QA'
-      :engineering_productivity | '~"Engineering Productivity" for CI, Danger'
-      :ci_template | '~"ci::templates"'
-    end
-
-    with_them do
-      subject { helper.label_for_category(category) }
-
-      it { is_expected.to eq(expected_label) }
-    end
-  end
-
-  describe '#new_teammates' do
-    it 'returns an array of Teammate' do
-      usernames = %w[filipa iamphil]
-
-      teammates = helper.new_teammates(usernames)
-
-      expect(teammates.map(&:username)).to eq(usernames)
-    end
-  end
-
-  describe '#mr_iid' do
-    it 'returns "" when `gitlab_helper` is unavailable' do
-      expect(helper).to receive(:gitlab_helper).and_return(nil)
-
-      expect(helper.mr_iid).to eq('')
-    end
-
-    it 'returns the MR IID when `gitlab_helper` is available' do
-      mr_iid = '1234'
-      expect(fake_gitlab).to receive(:mr_json)
-        .and_return('iid' => mr_iid)
-
-      expect(helper.mr_iid).to eq(mr_iid)
-    end
-  end
-
-  describe '#mr_title' do
-    it 'returns "" when `gitlab_helper` is unavailable' do
-      expect(helper).to receive(:gitlab_helper).and_return(nil)
-
-      expect(helper.mr_title).to eq('')
-    end
-
-    it 'returns the MR title when `gitlab_helper` is available' do
-      mr_title = 'My MR title'
-      expect(fake_gitlab).to receive(:mr_json)
-        .and_return('title' => mr_title)
-
-      expect(helper.mr_title).to eq(mr_title)
-    end
-  end
-
-  describe '#mr_web_url' do
-    it 'returns "" when `gitlab_helper` is unavailable' do
-      expect(helper).to receive(:gitlab_helper).and_return(nil)
-
-      expect(helper.mr_web_url).to eq('')
-    end
-
-    it 'returns the MR web_url when `gitlab_helper` is available' do
-      mr_web_url = 'https://gitlab.com/gitlab-org/gitlab/-/merge_requests/1'
-      expect(fake_gitlab).to receive(:mr_json)
-        .and_return('web_url' => mr_web_url)
-
-      expect(helper.mr_web_url).to eq(mr_web_url)
-    end
-  end
-
-  describe '#mr_labels' do
-    it 'returns "" when `gitlab_helper` is unavailable' do
-      expect(helper).to receive(:gitlab_helper).and_return(nil)
-
-      expect(helper.mr_labels).to eq([])
-    end
-
-    it 'returns the MR labels when `gitlab_helper` is available' do
-      mr_labels = %w[foo bar baz]
-      expect(fake_gitlab).to receive(:mr_labels)
-        .and_return(mr_labels)
-
-      expect(helper.mr_labels).to eq(mr_labels)
-    end
-  end
-
-  describe '#mr_target_branch' do
-    it 'returns "" when `gitlab_helper` is unavailable' do
-      expect(helper).to receive(:gitlab_helper).and_return(nil)
-
-      expect(helper.mr_target_branch).to eq('')
-    end
-
-    it 'returns the MR web_url when `gitlab_helper` is available' do
-      mr_target_branch = 'main'
-      expect(fake_gitlab).to receive(:mr_json)
-        .and_return('target_branch' => mr_target_branch)
-
-      expect(helper.mr_target_branch).to eq(mr_target_branch)
-    end
-  end
-
-  describe '#security_mr?' do
-    it 'returns false when on a normal merge request' do
-      expect(fake_gitlab).to receive(:mr_json)
-        .and_return('web_url' => 'https://gitlab.com/gitlab-org/gitlab/-/merge_requests/1')
-
-      expect(helper).not_to be_security_mr
-    end
-
-    it 'returns true when on a security merge request' do
-      expect(fake_gitlab).to receive(:mr_json)
-        .and_return('web_url' => 'https://gitlab.com/gitlab-org/security/gitlab/-/merge_requests/1')
-
-      expect(helper).to be_security_mr
-    end
-  end
-
-  describe '#draft_mr?' do
-    it 'returns true for a draft MR' do
-      expect(fake_gitlab).to receive(:mr_json)
-        .and_return('title' => 'Draft: My MR title')
-
-      expect(helper).to be_draft_mr
-    end
-
-    it 'returns false for non draft MR' do
-      expect(fake_gitlab).to receive(:mr_json)
-        .and_return('title' => 'My MR title')
-
-      expect(helper).not_to be_draft_mr
-    end
-  end
-
-  describe '#cherry_pick_mr?' do
-    context 'when MR title does not mention a cherry-pick' do
-      it 'returns false' do
-        expect(fake_gitlab).to receive(:mr_json)
-          .and_return('title' => 'Add feature xyz')
-
-        expect(helper).not_to be_cherry_pick_mr
-      end
-    end
-
-    context 'when MR title mentions a cherry-pick' do
-      [
-        'Cherry Pick !1234',
-        'cherry-pick !1234',
-        'CherryPick !1234'
-      ].each do |mr_title|
-        it 'returns true' do
-          expect(fake_gitlab).to receive(:mr_json)
-            .and_return('title' => mr_title)
-
-          expect(helper).to be_cherry_pick_mr
-        end
-      end
-    end
-  end
-
-  describe '#run_all_rspec_mr?' do
-    context 'when MR title does not mention RUN ALL RSPEC' do
-      it 'returns false' do
-        expect(fake_gitlab).to receive(:mr_json)
-          .and_return('title' => 'Add feature xyz')
-
-        expect(helper).not_to be_run_all_rspec_mr
-      end
-    end
-
-    context 'when MR title mentions RUN ALL RSPEC' do
-      it 'returns true' do
-        expect(fake_gitlab).to receive(:mr_json)
-          .and_return('title' => 'Add feature xyz RUN ALL RSPEC')
-
-        expect(helper).to be_run_all_rspec_mr
-      end
-    end
-  end
-
-  describe '#run_as_if_foss_mr?' do
-    context 'when MR title does not mention RUN AS-IF-FOSS' do
-      it 'returns false' do
-        expect(fake_gitlab).to receive(:mr_json)
-          .and_return('title' => 'Add feature xyz')
-
-        expect(helper).not_to be_run_as_if_foss_mr
-      end
-    end
-
-    context 'when MR title mentions RUN AS-IF-FOSS' do
-      it 'returns true' do
-        expect(fake_gitlab).to receive(:mr_json)
-          .and_return('title' => 'Add feature xyz RUN AS-IF-FOSS')
-
-        expect(helper).to be_run_as_if_foss_mr
-      end
-    end
-  end
-
-  describe '#stable_branch?' do
-    it 'returns false when `gitlab_helper` is unavailable' do
-      expect(helper).to receive(:gitlab_helper).and_return(nil)
-
-      expect(helper).not_to be_stable_branch
-    end
-
-    context 'when MR target branch is not a stable branch' do
-      it 'returns false' do
-        expect(fake_gitlab).to receive(:mr_json)
-          .and_return('target_branch' => 'my-feature-branch')
-
-        expect(helper).not_to be_stable_branch
-      end
-    end
-
-    context 'when MR target branch is a stable branch' do
-      %w[
-        13-1-stable-ee
-        13-1-stable-ee-patch-1
-      ].each do |target_branch|
-        it 'returns true' do
-          expect(fake_gitlab).to receive(:mr_json)
-            .and_return('target_branch' => target_branch)
-
-          expect(helper).to be_stable_branch
-        end
-      end
-    end
-  end
-
-  describe '#mr_has_label?' do
-    it 'returns false when `gitlab_helper` is unavailable' do
-      expect(helper).to receive(:gitlab_helper).and_return(nil)
-
-      expect(helper.mr_has_labels?('telemetry')).to be_falsey
-    end
-
-    context 'when mr has labels' do
-      before do
-        mr_labels = ['telemetry', 'telemetry::reviewed']
-        expect(fake_gitlab).to receive(:mr_labels).and_return(mr_labels)
-      end
-
-      it 'returns true with a matched label' do
-        expect(helper.mr_has_labels?('telemetry')).to be_truthy
-      end
-
-      it 'returns false with unmatched label' do
-        expect(helper.mr_has_labels?('database')).to be_falsey
-      end
-
-      it 'returns true with an array of labels' do
-        expect(helper.mr_has_labels?(['telemetry', 'telemetry::reviewed'])).to be_truthy
-      end
-
-      it 'returns true with multi arguments with matched labels' do
-        expect(helper.mr_has_labels?('telemetry', 'telemetry::reviewed')).to be_truthy
-      end
-
-      it 'returns false with multi arguments with unmatched labels' do
-        expect(helper.mr_has_labels?('telemetry', 'telemetry::non existing')).to be_falsey
-      end
-    end
-  end
-
-  describe '#labels_list' do
-    let(:labels) { ['telemetry', 'telemetry::reviewed'] }
-
-    it 'composes the labels string' do
-      expect(helper.labels_list(labels)).to eq('~"telemetry", ~"telemetry::reviewed"')
-    end
-
-    context 'when passing a separator' do
-      it 'composes the labels string with the given separator' do
-        expect(helper.labels_list(labels, sep: ' ')).to eq('~"telemetry" ~"telemetry::reviewed"')
-      end
-    end
-
-    it 'returns empty string for empty array' do
-      expect(helper.labels_list([])).to eq('')
-    end
-  end
-
-  describe '#prepare_labels_for_mr' do
-    it 'composes the labels string' do
-      mr_labels = ['telemetry', 'telemetry::reviewed']
-
-      expect(helper.prepare_labels_for_mr(mr_labels)).to eq('/label ~"telemetry" ~"telemetry::reviewed"')
-    end
-
-    it 'returns empty string for empty array' do
-      expect(helper.prepare_labels_for_mr([])).to eq('')
-    end
-  end
-
-  describe '#has_ci_changes?' do
-    context 'when .gitlab/ci is changed' do
-      it 'returns true' do
-        expect(helper).to receive(:all_changed_files).and_return(%w[migration.rb .gitlab/ci/test.yml])
-
-        expect(helper.has_ci_changes?).to be_truthy
-      end
-    end
-
-    context 'when .gitlab-ci.yml is changed' do
-      it 'returns true' do
-        expect(helper).to receive(:all_changed_files).and_return(%w[migration.rb .gitlab-ci.yml])
-
-        expect(helper.has_ci_changes?).to be_truthy
-      end
-    end
-
-    context 'when neither .gitlab/ci/ or .gitlab-ci.yml is changed' do
-      it 'returns false' do
-        expect(helper).to receive(:all_changed_files).and_return(%w[migration.rb nested/.gitlab-ci.yml])
-
-        expect(helper.has_ci_changes?).to be_falsey
-      end
-    end
-  end
-
-  describe '#group_label' do
-    it 'returns nil when no group label is present' do
-      expect(helper.group_label(%w[foo bar])).to be_nil
-    end
-
-    it 'returns the group label when a group label is present' do
-      expect(helper.group_label(['foo', 'group::source code', 'bar'])).to eq('group::source code')
-    end
-  end
-end
diff --git a/spec/tooling/danger/merge_request_linter_spec.rb b/spec/tooling/danger/merge_request_linter_spec.rb
deleted file mode 100644
index 3273b6b3d070c97bc0a7ab30ab916a3d186a41ef..0000000000000000000000000000000000000000
--- a/spec/tooling/danger/merge_request_linter_spec.rb
+++ /dev/null
@@ -1,54 +0,0 @@
-# frozen_string_literal: true
-
-require 'rspec-parameterized'
-require_relative 'danger_spec_helper'
-
-require_relative '../../../tooling/danger/merge_request_linter'
-
-RSpec.describe Tooling::Danger::MergeRequestLinter do
-  using RSpec::Parameterized::TableSyntax
-
-  let(:mr_class) do
-    Struct.new(:message, :sha, :diff_parent)
-  end
-
-  let(:mr_title) { 'A B ' + 'C' }
-  let(:merge_request) { mr_class.new(mr_title, anything, anything) }
-
-  describe '#lint_subject' do
-    subject(:mr_linter) { described_class.new(merge_request) }
-
-    shared_examples 'a valid mr title' do
-      it 'does not have any problem' do
-        mr_linter.lint
-
-        expect(mr_linter.problems).to be_empty
-      end
-    end
-
-    context 'when subject valid' do
-      it_behaves_like 'a valid mr title'
-    end
-
-    context 'when it is too long' do
-      let(:mr_title) { 'A B ' + 'C' * described_class::MAX_LINE_LENGTH }
-
-      it 'adds a problem' do
-        expect(mr_linter).to receive(:add_problem).with(:subject_too_long, described_class.subject_description)
-
-        mr_linter.lint
-      end
-    end
-
-    describe 'using magic mr run options' do
-      where(run_option: described_class.mr_run_options_regex.split('|') +
-        described_class.mr_run_options_regex.split('|').map! { |x| "[#{x}]" })
-
-      with_them do
-        let(:mr_title) { run_option + ' A B ' + 'C' * (described_class::MAX_LINE_LENGTH - 5) }
-
-        it_behaves_like 'a valid mr title'
-      end
-    end
-  end
-end
diff --git a/spec/tooling/danger/project_helper_spec.rb b/spec/tooling/danger/project_helper_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ff4afcf68636664c634eaee5cd5b20fdfba2a42a
--- /dev/null
+++ b/spec/tooling/danger/project_helper_spec.rb
@@ -0,0 +1,260 @@
+# frozen_string_literal: true
+
+require 'rspec-parameterized'
+require 'gitlab-dangerfiles'
+require 'danger/helper'
+require 'gitlab/dangerfiles/spec_helper'
+
+require_relative '../../../danger/plugins/project_helper'
+
+RSpec.describe Tooling::Danger::ProjectHelper do
+  include_context "with dangerfile"
+
+  let(:fake_danger) { DangerSpecHelper.fake_danger.include(described_class) }
+  let(:fake_helper) { Danger::Helper.new(project_helper) }
+
+  subject(:project_helper) { fake_danger.new(git: fake_git) }
+
+  before do
+    allow(project_helper).to receive(:helper).and_return(fake_helper)
+  end
+
+  describe '#changes' do
+    it 'returns an array of Change objects' do
+      expect(project_helper.changes).to all(be_an(Gitlab::Dangerfiles::Change))
+    end
+
+    it 'groups changes by change type' do
+      changes = project_helper.changes
+
+      expect(changes.added.files).to eq(added_files)
+      expect(changes.modified.files).to eq(modified_files)
+      expect(changes.deleted.files).to eq(deleted_files)
+      expect(changes.renamed_before.files).to eq([renamed_before_file])
+      expect(changes.renamed_after.files).to eq([renamed_after_file])
+    end
+  end
+
+  describe '#categories_for_file' do
+    using RSpec::Parameterized::TableSyntax
+
+    before do
+      allow(fake_git).to receive(:diff_for_file).with('usage_data.rb') { double(:diff, patch: "+ count(User.active)") }
+    end
+
+    where(:path, :expected_categories) do
+      'usage_data.rb'   | [:database, :backend]
+      'doc/foo.md'      | [:docs]
+      'CONTRIBUTING.md' | [:docs]
+      'LICENSE'         | [:docs]
+      'MAINTENANCE.md'  | [:docs]
+      'PHILOSOPHY.md'   | [:docs]
+      'PROCESS.md'      | [:docs]
+      'README.md'       | [:docs]
+
+      'ee/doc/foo'      | [:unknown]
+      'ee/README'       | [:unknown]
+
+      'app/assets/foo'       | [:frontend]
+      'app/views/foo'        | [:frontend]
+      'public/foo'           | [:frontend]
+      'scripts/frontend/foo' | [:frontend]
+      'spec/javascripts/foo' | [:frontend]
+      'spec/frontend/bar'    | [:frontend]
+      'vendor/assets/foo'    | [:frontend]
+      'babel.config.js'      | [:frontend]
+      'jest.config.js'       | [:frontend]
+      'package.json'         | [:frontend]
+      'yarn.lock'            | [:frontend]
+      'config/foo.js'        | [:frontend]
+      'config/deep/foo.js'   | [:frontend]
+
+      'ee/app/assets/foo'       | [:frontend]
+      'ee/app/views/foo'        | [:frontend]
+      'ee/spec/javascripts/foo' | [:frontend]
+      'ee/spec/frontend/bar'    | [:frontend]
+
+      '.gitlab/ci/frontend.gitlab-ci.yml' | %i[frontend engineering_productivity]
+
+      'app/models/foo'             | [:backend]
+      'bin/foo'                    | [:backend]
+      'config/foo'                 | [:backend]
+      'lib/foo'                    | [:backend]
+      'rubocop/foo'                | [:backend]
+      '.rubocop.yml'               | [:backend]
+      '.rubocop_todo.yml'          | [:backend]
+      '.rubocop_manual_todo.yml'   | [:backend]
+      'spec/foo'                   | [:backend]
+      'spec/foo/bar'               | [:backend]
+
+      'ee/app/foo'      | [:backend]
+      'ee/bin/foo'      | [:backend]
+      'ee/spec/foo'     | [:backend]
+      'ee/spec/foo/bar' | [:backend]
+
+      'spec/features/foo'                            | [:test]
+      'ee/spec/features/foo'                         | [:test]
+      'spec/support/shared_examples/features/foo'    | [:test]
+      'ee/spec/support/shared_examples/features/foo' | [:test]
+      'spec/support/shared_contexts/features/foo'    | [:test]
+      'ee/spec/support/shared_contexts/features/foo' | [:test]
+      'spec/support/helpers/features/foo'            | [:test]
+      'ee/spec/support/helpers/features/foo'         | [:test]
+
+      'generator_templates/foo' | [:backend]
+      'vendor/languages.yml'    | [:backend]
+      'file_hooks/examples/'    | [:backend]
+
+      'Gemfile'        | [:backend]
+      'Gemfile.lock'   | [:backend]
+      'Rakefile'       | [:backend]
+      'FOO_VERSION'    | [:backend]
+
+      'Dangerfile'                                            | [:engineering_productivity]
+      'danger/commit_messages/Dangerfile'                     | [:engineering_productivity]
+      'ee/danger/commit_messages/Dangerfile'                  | [:engineering_productivity]
+      'danger/commit_messages/'                               | [:engineering_productivity]
+      'ee/danger/commit_messages/'                            | [:engineering_productivity]
+      '.gitlab-ci.yml'                                        | [:engineering_productivity]
+      '.gitlab/ci/cng.gitlab-ci.yml'                          | [:engineering_productivity]
+      '.gitlab/ci/ee-specific-checks.gitlab-ci.yml'           | [:engineering_productivity]
+      'scripts/foo'                                           | [:engineering_productivity]
+      'tooling/danger/foo'                                    | [:engineering_productivity]
+      'ee/tooling/danger/foo'                                 | [:engineering_productivity]
+      'lefthook.yml'                                          | [:engineering_productivity]
+      '.editorconfig'                                         | [:engineering_productivity]
+      'tooling/bin/find_foss_tests'                           | [:engineering_productivity]
+      '.codeclimate.yml'                                      | [:engineering_productivity]
+      '.gitlab/CODEOWNERS'                                    | [:engineering_productivity]
+
+      'lib/gitlab/ci/templates/Security/SAST.gitlab-ci.yml'   | [:ci_template]
+      'lib/gitlab/ci/templates/dotNET-Core.yml'               | [:ci_template]
+
+      'ee/FOO_VERSION' | [:unknown]
+
+      'db/schema.rb'                                              | [:database]
+      'db/structure.sql'                                          | [:database]
+      'db/migrate/foo'                                            | [:database, :migration]
+      'db/post_migrate/foo'                                       | [:database, :migration]
+      'ee/db/geo/migrate/foo'                                     | [:database, :migration]
+      'ee/db/geo/post_migrate/foo'                                | [:database, :migration]
+      'app/models/project_authorization.rb'                       | [:database]
+      'app/services/users/refresh_authorized_projects_service.rb' | [:database]
+      'lib/gitlab/background_migration.rb'                        | [:database]
+      'lib/gitlab/background_migration/foo'                       | [:database]
+      'ee/lib/gitlab/background_migration/foo'                    | [:database]
+      'lib/gitlab/database.rb'                                    | [:database]
+      'lib/gitlab/database/foo'                                   | [:database]
+      'ee/lib/gitlab/database/foo'                                | [:database]
+      'lib/gitlab/github_import.rb'                               | [:database]
+      'lib/gitlab/github_import/foo'                              | [:database]
+      'lib/gitlab/sql/foo'                                        | [:database]
+      'rubocop/cop/migration/foo'                                 | [:database]
+
+      'db/fixtures/foo.rb'                                 | [:backend]
+      'ee/db/fixtures/foo.rb'                              | [:backend]
+
+      'qa/foo' | [:qa]
+      'ee/qa/foo' | [:qa]
+
+      'changelogs/foo'    | [:none]
+      'ee/changelogs/foo' | [:none]
+      'locale/gitlab.pot' | [:none]
+
+      'FOO'          | [:unknown]
+      'foo'          | [:unknown]
+
+      'foo/bar.rb'  | [:backend]
+      'foo/bar.js'  | [:frontend]
+      'foo/bar.txt' | [:none]
+      'foo/bar.md'  | [:none]
+    end
+
+    with_them do
+      subject { project_helper.categories_for_file(path) }
+
+      it { is_expected.to eq(expected_categories) }
+    end
+
+    context 'having specific changes' do
+      where(:expected_categories, :patch, :changed_files) do
+        [:database, :backend] | '+ count(User.active)'                         | ['usage_data.rb', 'lib/gitlab/usage_data.rb', 'ee/lib/ee/gitlab/usage_data.rb']
+        [:database, :backend] | '+ estimate_batch_distinct_count(User.active)' | ['usage_data.rb']
+        [:backend]            | '+ alt_usage_data(User.active)'                | ['usage_data.rb']
+        [:backend]            | '+ count(User.active)'                         | ['user.rb']
+        [:backend]            | '+ count(User.active)'                         | ['usage_data/topology.rb']
+        [:backend]            | '+ foo_count(User.active)'                     | ['usage_data.rb']
+      end
+
+      with_them do
+        it 'has the correct categories' do
+          changed_files.each do |file|
+            allow(fake_git).to receive(:diff_for_file).with(file) { double(:diff, patch: patch) }
+
+            expect(project_helper.categories_for_file(file)).to eq(expected_categories)
+          end
+        end
+      end
+    end
+  end
+
+  describe '.local_warning_message' do
+    it 'returns an informational message with rules that can run' do
+      expect(described_class.local_warning_message).to eq('==> Only the following Danger rules can be run locally: changes_size, commit_messages, database, documentation, duplicate_yarn_dependencies, eslint, karma, pajamas, pipeline, prettier, product_intelligence, utility_css')
+    end
+  end
+
+  describe '.success_message' do
+    it 'returns an informational success message' do
+      expect(described_class.success_message).to eq('==> No Danger rule violations!')
+    end
+  end
+
+  describe '#rule_names' do
+    context 'when running locally' do
+      before do
+        expect(fake_helper).to receive(:ci?).and_return(false)
+      end
+
+      it 'returns local only rules' do
+        expect(project_helper.rule_names).to match_array(described_class::LOCAL_RULES)
+      end
+    end
+
+    context 'when running under CI' do
+      before do
+        expect(fake_helper).to receive(:ci?).and_return(true)
+      end
+
+      it 'returns all rules' do
+        expect(project_helper.rule_names).to eq(described_class::LOCAL_RULES | described_class::CI_ONLY_RULES)
+      end
+    end
+  end
+
+  describe '#all_ee_changes' do
+    subject { project_helper.all_ee_changes }
+
+    it 'returns all changed files starting with ee/' do
+      expect(project_helper).to receive(:all_changed_files).and_return(%w[fr/ee/beer.rb ee/wine.rb ee/lib/ido.rb ee.k])
+
+      is_expected.to match_array(%w[ee/wine.rb ee/lib/ido.rb])
+    end
+  end
+
+  describe '#project_name' do
+    subject { project_helper.project_name }
+
+    it 'returns gitlab if ee? returns true' do
+      expect(project_helper).to receive(:ee?) { true }
+
+      is_expected.to eq('gitlab')
+    end
+
+    it 'returns gitlab-ce if ee? returns false' do
+      expect(project_helper).to receive(:ee?) { false }
+
+      is_expected.to eq('gitlab-foss')
+    end
+  end
+end
diff --git a/spec/tooling/danger/roulette_spec.rb b/spec/tooling/danger/roulette_spec.rb
deleted file mode 100644
index 1e500a1ed08d29e82f14fdbeafda2ea0d68af828..0000000000000000000000000000000000000000
--- a/spec/tooling/danger/roulette_spec.rb
+++ /dev/null
@@ -1,429 +0,0 @@
-# frozen_string_literal: true
-
-require 'webmock/rspec'
-require 'timecop'
-
-require_relative '../../../tooling/danger/roulette'
-require 'active_support/testing/time_helpers'
-
-RSpec.describe Tooling::Danger::Roulette do
-  include ActiveSupport::Testing::TimeHelpers
-
-  around do |example|
-    travel_to(Time.utc(2020, 06, 22, 10)) { example.run }
-  end
-
-  let(:backend_available) { true }
-  let(:backend_tz_offset_hours) { 2.0 }
-  let(:backend_maintainer) do
-    Tooling::Danger::Teammate.new(
-      'username' => 'backend-maintainer',
-      'name' => 'Backend maintainer',
-      'role' => 'Backend engineer',
-      'projects' => { 'gitlab' => 'maintainer backend' },
-      'available' => backend_available,
-      'tz_offset_hours' => backend_tz_offset_hours
-    )
-  end
-
-  let(:frontend_reviewer) do
-    Tooling::Danger::Teammate.new(
-      'username' => 'frontend-reviewer',
-      'name' => 'Frontend reviewer',
-      'role' => 'Frontend engineer',
-      'projects' => { 'gitlab' => 'reviewer frontend' },
-      'available' => true,
-      'tz_offset_hours' => 2.0
-    )
-  end
-
-  let(:frontend_maintainer) do
-    Tooling::Danger::Teammate.new(
-      'username' => 'frontend-maintainer',
-      'name' => 'Frontend maintainer',
-      'role' => 'Frontend engineer',
-      'projects' => { 'gitlab' => "maintainer frontend" },
-      'available' => true,
-      'tz_offset_hours' => 2.0
-    )
-  end
-
-  let(:software_engineer_in_test) do
-    Tooling::Danger::Teammate.new(
-      'username' => 'software-engineer-in-test',
-      'name' => 'Software Engineer in Test',
-      'role' => 'Software Engineer in Test, Create:Source Code',
-      'projects' => { 'gitlab' => 'maintainer qa', 'gitlab-qa' => 'maintainer' },
-      'available' => true,
-      'tz_offset_hours' => 2.0
-    )
-  end
-
-  let(:engineering_productivity_reviewer) do
-    Tooling::Danger::Teammate.new(
-      'username' => 'eng-prod-reviewer',
-      'name' => 'EP engineer',
-      'role' => 'Engineering Productivity',
-      'projects' => { 'gitlab' => 'reviewer backend' },
-      'available' => true,
-      'tz_offset_hours' => 2.0
-    )
-  end
-
-  let(:ci_template_reviewer) do
-    Tooling::Danger::Teammate.new(
-      'username' => 'ci-template-maintainer',
-      'name' => 'CI Template engineer',
-      'role' => '~"ci::templates"',
-      'projects' => { 'gitlab' => 'reviewer ci_template' },
-      'available' => true,
-      'tz_offset_hours' => 2.0
-    )
-  end
-
-  let(:teammates) do
-    [
-      backend_maintainer.to_h,
-      frontend_maintainer.to_h,
-      frontend_reviewer.to_h,
-      software_engineer_in_test.to_h,
-      engineering_productivity_reviewer.to_h,
-      ci_template_reviewer.to_h
-    ]
-  end
-
-  let(:teammate_json) do
-    teammates.to_json
-  end
-
-  subject(:roulette) { Object.new.extend(described_class) }
-
-  describe 'Spin#==' do
-    it 'compares Spin attributes' do
-      spin1 = described_class::Spin.new(:backend, frontend_reviewer, frontend_maintainer, false, false)
-      spin2 = described_class::Spin.new(:backend, frontend_reviewer, frontend_maintainer, false, false)
-      spin3 = described_class::Spin.new(:backend, frontend_reviewer, frontend_maintainer, false, true)
-      spin4 = described_class::Spin.new(:backend, frontend_reviewer, frontend_maintainer, true, false)
-      spin5 = described_class::Spin.new(:backend, frontend_reviewer, backend_maintainer, false, false)
-      spin6 = described_class::Spin.new(:backend, backend_maintainer, frontend_maintainer, false, false)
-      spin7 = described_class::Spin.new(:frontend, frontend_reviewer, frontend_maintainer, false, false)
-
-      expect(spin1).to eq(spin2)
-      expect(spin1).not_to eq(spin3)
-      expect(spin1).not_to eq(spin4)
-      expect(spin1).not_to eq(spin5)
-      expect(spin1).not_to eq(spin6)
-      expect(spin1).not_to eq(spin7)
-    end
-  end
-
-  describe '#spin' do
-    let!(:project) { 'gitlab' }
-    let!(:mr_source_branch) { 'a-branch' }
-    let!(:mr_labels) { ['backend', 'devops::create'] }
-    let!(:author) { Tooling::Danger::Teammate.new('username' => 'johndoe') }
-    let(:timezone_experiment) { false }
-    let(:spins) do
-      # Stub the request at the latest time so that we can modify the raw data, e.g. available fields.
-      WebMock
-        .stub_request(:get, described_class::ROULETTE_DATA_URL)
-        .to_return(body: teammate_json)
-
-      subject.spin(project, categories, timezone_experiment: timezone_experiment)
-    end
-
-    before do
-      allow(subject).to receive(:mr_author_username).and_return(author.username)
-      allow(subject).to receive(:mr_labels).and_return(mr_labels)
-      allow(subject).to receive(:mr_source_branch).and_return(mr_source_branch)
-    end
-
-    context 'when timezone_experiment == false' do
-      context 'when change contains backend category' do
-        let(:categories) { [:backend] }
-
-        it 'assigns backend reviewer and maintainer' do
-          expect(spins[0].reviewer).to eq(engineering_productivity_reviewer)
-          expect(spins[0].maintainer).to eq(backend_maintainer)
-          expect(spins).to eq([described_class::Spin.new(:backend, engineering_productivity_reviewer, backend_maintainer, false, false)])
-        end
-
-        context 'when teammate is not available' do
-          let(:backend_available) { false }
-
-          it 'assigns backend reviewer and no maintainer' do
-            expect(spins).to eq([described_class::Spin.new(:backend, engineering_productivity_reviewer, nil, false, false)])
-          end
-        end
-      end
-
-      context 'when change contains frontend category' do
-        let(:categories) { [:frontend] }
-
-        it 'assigns frontend reviewer and maintainer' do
-          expect(spins).to eq([described_class::Spin.new(:frontend, frontend_reviewer, frontend_maintainer, false, false)])
-        end
-      end
-
-      context 'when change contains many categories' do
-        let(:categories) { [:frontend, :test, :qa, :engineering_productivity, :ci_template, :backend] }
-
-        it 'has a deterministic sorting order' do
-          expect(spins.map(&:category)).to eq categories.sort
-        end
-      end
-
-      context 'when change contains QA category' do
-        let(:categories) { [:qa] }
-
-        it 'assigns QA maintainer' do
-          expect(spins).to eq([described_class::Spin.new(:qa, nil, software_engineer_in_test, false, false)])
-        end
-      end
-
-      context 'when change contains QA category and another category' do
-        let(:categories) { [:backend, :qa] }
-
-        it 'assigns QA maintainer' do
-          expect(spins).to eq([described_class::Spin.new(:backend, engineering_productivity_reviewer, backend_maintainer, false, false), described_class::Spin.new(:qa, nil, software_engineer_in_test, :maintainer, false)])
-        end
-
-        context 'and author is an SET' do
-          let!(:author) { Tooling::Danger::Teammate.new('username' => software_engineer_in_test.username) }
-
-          it 'assigns QA reviewer' do
-            expect(spins).to eq([described_class::Spin.new(:backend, engineering_productivity_reviewer, backend_maintainer, false, false), described_class::Spin.new(:qa, nil, nil, false, false)])
-          end
-        end
-      end
-
-      context 'when change contains Engineering Productivity category' do
-        let(:categories) { [:engineering_productivity] }
-
-        it 'assigns Engineering Productivity reviewer and fallback to backend maintainer' do
-          expect(spins).to eq([described_class::Spin.new(:engineering_productivity, engineering_productivity_reviewer, backend_maintainer, false, false)])
-        end
-      end
-
-      context 'when change contains CI/CD Template category' do
-        let(:categories) { [:ci_template] }
-
-        it 'assigns CI/CD Template reviewer and fallback to backend maintainer' do
-          expect(spins).to eq([described_class::Spin.new(:ci_template, ci_template_reviewer, backend_maintainer, false, false)])
-        end
-      end
-
-      context 'when change contains test category' do
-        let(:categories) { [:test] }
-
-        it 'assigns corresponding SET' do
-          expect(spins).to eq([described_class::Spin.new(:test, software_engineer_in_test, nil, :maintainer, false)])
-        end
-      end
-    end
-
-    context 'when timezone_experiment == true' do
-      let(:timezone_experiment) { true }
-
-      context 'when change contains backend category' do
-        let(:categories) { [:backend] }
-
-        it 'assigns backend reviewer and maintainer' do
-          expect(spins).to eq([described_class::Spin.new(:backend, engineering_productivity_reviewer, backend_maintainer, false, true)])
-        end
-
-        context 'when teammate is not in a good timezone' do
-          let(:backend_tz_offset_hours) { 5.0 }
-
-          it 'assigns backend reviewer and no maintainer' do
-            expect(spins).to eq([described_class::Spin.new(:backend, engineering_productivity_reviewer, nil, false, true)])
-          end
-        end
-      end
-
-      context 'when change includes a category with timezone disabled' do
-        let(:categories) { [:backend] }
-
-        before do
-          stub_const("#{described_class}::INCLUDE_TIMEZONE_FOR_CATEGORY", backend: false)
-        end
-
-        it 'assigns backend reviewer and maintainer' do
-          expect(spins).to eq([described_class::Spin.new(:backend, engineering_productivity_reviewer, backend_maintainer, false, false)])
-        end
-
-        context 'when teammate is not in a good timezone' do
-          let(:backend_tz_offset_hours) { 5.0 }
-
-          it 'assigns backend reviewer and maintainer' do
-            expect(spins).to eq([described_class::Spin.new(:backend, engineering_productivity_reviewer, backend_maintainer, false, false)])
-          end
-        end
-      end
-    end
-  end
-
-  RSpec::Matchers.define :match_teammates do |expected|
-    match do |actual|
-      expected.each do |expected_person|
-        actual_person_found = actual.find { |actual_person| actual_person.name == expected_person.username }
-
-        actual_person_found &&
-        actual_person_found.name == expected_person.name &&
-        actual_person_found.role == expected_person.role &&
-        actual_person_found.projects == expected_person.projects
-      end
-    end
-  end
-
-  describe '#team' do
-    subject(:team) { roulette.team }
-
-    context 'HTTP failure' do
-      before do
-        WebMock
-          .stub_request(:get, described_class::ROULETTE_DATA_URL)
-          .to_return(status: 404)
-      end
-
-      it 'raises a pretty error' do
-        expect { team }.to raise_error(/Failed to read/)
-      end
-    end
-
-    context 'JSON failure' do
-      before do
-        WebMock
-          .stub_request(:get, described_class::ROULETTE_DATA_URL)
-          .to_return(body: 'INVALID JSON')
-      end
-
-      it 'raises a pretty error' do
-        expect { team }.to raise_error(/Failed to parse/)
-      end
-    end
-
-    context 'success' do
-      before do
-        WebMock
-          .stub_request(:get, described_class::ROULETTE_DATA_URL)
-          .to_return(body: teammate_json)
-      end
-
-      it 'returns an array of teammates' do
-        is_expected.to match_teammates([
-          backend_maintainer,
-          frontend_reviewer,
-          frontend_maintainer,
-          software_engineer_in_test,
-          engineering_productivity_reviewer,
-          ci_template_reviewer
-        ])
-      end
-
-      it 'memoizes the result' do
-        expect(team.object_id).to eq(roulette.team.object_id)
-      end
-    end
-  end
-
-  describe '#project_team' do
-    subject { roulette.project_team('gitlab-qa') }
-
-    before do
-      WebMock
-        .stub_request(:get, described_class::ROULETTE_DATA_URL)
-        .to_return(body: teammate_json)
-    end
-
-    it 'filters team by project_name' do
-      is_expected.to match_teammates([
-        software_engineer_in_test
-      ])
-    end
-  end
-
-  describe '#spin_for_person' do
-    let(:person_tz_offset_hours) { 0.0 }
-    let(:person1) do
-      Tooling::Danger::Teammate.new(
-        'username' => 'user1',
-        'available' => true,
-        'tz_offset_hours' => person_tz_offset_hours
-      )
-    end
-
-    let(:person2) do
-      Tooling::Danger::Teammate.new(
-        'username' => 'user2',
-        'available' => true,
-        'tz_offset_hours' => person_tz_offset_hours)
-    end
-
-    let(:author) do
-      Tooling::Danger::Teammate.new(
-        'username' => 'johndoe',
-        'available' => true,
-        'tz_offset_hours' => 0.0)
-    end
-
-    let(:unavailable) do
-      Tooling::Danger::Teammate.new(
-        'username' => 'janedoe',
-        'available' => false,
-        'tz_offset_hours' => 0.0)
-    end
-
-    before do
-      allow(subject).to receive(:mr_author_username).and_return(author.username)
-    end
-
-    (-4..4).each do |utc_offset|
-      context "when local hour for person is #{10 + utc_offset} (offset: #{utc_offset})" do
-        let(:person_tz_offset_hours) { utc_offset }
-
-        [false, true].each do |timezone_experiment|
-          context "with timezone_experiment == #{timezone_experiment}" do
-            it 'returns a random person' do
-              persons = [person1, person2]
-
-              selected = subject.spin_for_person(persons, random: Random.new, timezone_experiment: timezone_experiment)
-
-              expect(persons.map(&:username)).to include(selected.username)
-            end
-          end
-        end
-      end
-    end
-
-    ((-12..-5).to_a + (5..12).to_a).each do |utc_offset|
-      context "when local hour for person is #{10 + utc_offset} (offset: #{utc_offset})" do
-        let(:person_tz_offset_hours) { utc_offset }
-
-        [false, true].each do |timezone_experiment|
-          context "with timezone_experiment == #{timezone_experiment}" do
-            it 'returns a random person or nil' do
-              persons = [person1, person2]
-
-              selected = subject.spin_for_person(persons, random: Random.new, timezone_experiment: timezone_experiment)
-
-              if timezone_experiment
-                expect(selected).to be_nil
-              else
-                expect(persons.map(&:username)).to include(selected.username)
-              end
-            end
-          end
-        end
-      end
-    end
-
-    it 'excludes unavailable persons' do
-      expect(subject.spin_for_person([unavailable], random: Random.new)).to be_nil
-    end
-
-    it 'excludes mr.author' do
-      expect(subject.spin_for_person([author], random: Random.new)).to be_nil
-    end
-  end
-end
diff --git a/spec/tooling/danger/sidekiq_queues_spec.rb b/spec/tooling/danger/sidekiq_queues_spec.rb
index c5fc85926216eb665ccb29590d649d495796bea3..9bffc7ee93d8ad2c98e9995fb36a61eba3a41bf6 100644
--- a/spec/tooling/danger/sidekiq_queues_spec.rb
+++ b/spec/tooling/danger/sidekiq_queues_spec.rb
@@ -1,20 +1,21 @@
 # frozen_string_literal: true
 
 require 'rspec-parameterized'
-require_relative 'danger_spec_helper'
+require 'gitlab-dangerfiles'
+require 'gitlab/dangerfiles/spec_helper'
 
 require_relative '../../../tooling/danger/sidekiq_queues'
 
 RSpec.describe Tooling::Danger::SidekiqQueues do
-  using RSpec::Parameterized::TableSyntax
-  include DangerSpecHelper
+  include_context "with dangerfile"
 
-  let(:fake_git) { double('fake-git') }
-  let(:fake_danger) { new_fake_danger.include(described_class) }
+  let(:fake_danger) { DangerSpecHelper.fake_danger.include(described_class) }
 
   subject(:sidekiq_queues) { fake_danger.new(git: fake_git) }
 
   describe '#changed_queue_files' do
+    using RSpec::Parameterized::TableSyntax
+
     where(:modified_files, :changed_queue_files) do
       %w(app/workers/all_queues.yml ee/app/workers/all_queues.yml foo) | %w(app/workers/all_queues.yml ee/app/workers/all_queues.yml)
       %w(app/workers/all_queues.yml ee/app/workers/all_queues.yml) | %w(app/workers/all_queues.yml ee/app/workers/all_queues.yml)
diff --git a/spec/tooling/danger/teammate_spec.rb b/spec/tooling/danger/teammate_spec.rb
deleted file mode 100644
index f3afdc6e9120ec9d9bf942ede4fa48d82ff520f8..0000000000000000000000000000000000000000
--- a/spec/tooling/danger/teammate_spec.rb
+++ /dev/null
@@ -1,225 +0,0 @@
-# frozen_string_literal: true
-
-require_relative '../../../tooling/danger/teammate'
-require 'active_support/testing/time_helpers'
-require 'rspec-parameterized'
-
-RSpec.describe Tooling::Danger::Teammate do
-  using RSpec::Parameterized::TableSyntax
-
-  subject { described_class.new(options) }
-
-  let(:tz_offset_hours) { 2.0 }
-  let(:options) do
-    {
-      'username' => 'luigi',
-      'projects' => projects,
-      'role' => role,
-      'markdown_name' => '[Luigi](https://gitlab.com/luigi) (`@luigi`)',
-      'tz_offset_hours' => tz_offset_hours
-    }
-  end
-
-  let(:capabilities) { ['reviewer backend'] }
-  let(:projects) { { project => capabilities } }
-  let(:role) { 'Engineer, Manage' }
-  let(:labels) { [] }
-  let(:project) { double }
-
-  describe '#==' do
-    it 'compares Teammate username' do
-      joe1 = described_class.new('username' => 'joe', 'projects' => projects)
-      joe2 = described_class.new('username' => 'joe', 'projects' => [])
-      jane1 = described_class.new('username' => 'jane', 'projects' => projects)
-      jane2 = described_class.new('username' => 'jane', 'projects' => [])
-
-      expect(joe1).to eq(joe2)
-      expect(jane1).to eq(jane2)
-      expect(jane1).not_to eq(nil)
-      expect(described_class.new('username' => nil)).not_to eq(nil)
-    end
-  end
-
-  describe '#to_h' do
-    it 'returns the given options' do
-      expect(subject.to_h).to eq(options)
-    end
-  end
-
-  context 'when having multiple capabilities' do
-    let(:capabilities) { ['reviewer backend', 'maintainer frontend', 'trainee_maintainer qa'] }
-
-    it '#any_capability? returns true if the person has any capability for the category in the given project' do
-      expect(subject.any_capability?(project, :backend)).to be_truthy
-      expect(subject.any_capability?(project, :frontend)).to be_truthy
-      expect(subject.any_capability?(project, :qa)).to be_truthy
-      expect(subject.any_capability?(project, :engineering_productivity)).to be_falsey
-    end
-
-    it '#reviewer? supports multiple roles per project' do
-      expect(subject.reviewer?(project, :backend, labels)).to be_truthy
-    end
-
-    it '#traintainer? supports multiple roles per project' do
-      expect(subject.traintainer?(project, :qa, labels)).to be_truthy
-    end
-
-    it '#maintainer? supports multiple roles per project' do
-      expect(subject.maintainer?(project, :frontend, labels)).to be_truthy
-    end
-
-    context 'when labels contain devops::create and the category is test' do
-      let(:labels) { ['devops::create'] }
-
-      context 'when role is Software Engineer in Test, Create' do
-        let(:role) { 'Software Engineer in Test, Create' }
-
-        it '#reviewer? returns true' do
-          expect(subject.reviewer?(project, :test, labels)).to be_truthy
-        end
-
-        it '#maintainer? returns false' do
-          expect(subject.maintainer?(project, :test, labels)).to be_falsey
-        end
-
-        context 'when hyperlink is mangled in the role' do
-          let(:role) { '<a href="#">Software Engineer in Test</a>, Create' }
-
-          it '#reviewer? returns true' do
-            expect(subject.reviewer?(project, :test, labels)).to be_truthy
-          end
-        end
-      end
-
-      context 'when role is Software Engineer in Test' do
-        let(:role) { 'Software Engineer in Test' }
-
-        it '#reviewer? returns false' do
-          expect(subject.reviewer?(project, :test, labels)).to be_falsey
-        end
-      end
-
-      context 'when role is Software Engineer in Test, Manage' do
-        let(:role) { 'Software Engineer in Test, Manage' }
-
-        it '#reviewer? returns false' do
-          expect(subject.reviewer?(project, :test, labels)).to be_falsey
-        end
-      end
-
-      context 'when role is Backend Engineer, Engineering Productivity' do
-        let(:role) { 'Backend Engineer, Engineering Productivity' }
-
-        it '#reviewer? returns true' do
-          expect(subject.reviewer?(project, :engineering_productivity, labels)).to be_truthy
-        end
-
-        it '#maintainer? returns false' do
-          expect(subject.maintainer?(project, :engineering_productivity, labels)).to be_falsey
-        end
-
-        context 'when capabilities include maintainer backend' do
-          let(:capabilities) { ['maintainer backend'] }
-
-          it '#maintainer? returns true' do
-            expect(subject.maintainer?(project, :engineering_productivity, labels)).to be_truthy
-          end
-        end
-
-        context 'when capabilities include maintainer engineering productivity' do
-          let(:capabilities) { ['maintainer engineering_productivity'] }
-
-          it '#maintainer? returns true' do
-            expect(subject.maintainer?(project, :engineering_productivity, labels)).to be_truthy
-          end
-        end
-
-        context 'when capabilities include trainee_maintainer backend' do
-          let(:capabilities) { ['trainee_maintainer backend'] }
-
-          it '#traintainer? returns true' do
-            expect(subject.traintainer?(project, :engineering_productivity, labels)).to be_truthy
-          end
-        end
-      end
-    end
-  end
-
-  context 'when having single capability' do
-    let(:capabilities) { 'reviewer backend' }
-
-    it '#reviewer? supports one role per project' do
-      expect(subject.reviewer?(project, :backend, labels)).to be_truthy
-    end
-
-    it '#traintainer? supports one role per project' do
-      expect(subject.traintainer?(project, :database, labels)).to be_falsey
-    end
-
-    it '#maintainer? supports one role per project' do
-      expect(subject.maintainer?(project, :frontend, labels)).to be_falsey
-    end
-  end
-
-  describe '#local_hour' do
-    include ActiveSupport::Testing::TimeHelpers
-
-    around do |example|
-      travel_to(Time.utc(2020, 6, 23, 10)) { example.run }
-    end
-
-    context 'when author is given' do
-      where(:tz_offset_hours, :expected_local_hour) do
-        -12 | 22
-        -10 | 0
-        2 | 12
-        4 | 14
-        12 | 22
-      end
-
-      with_them do
-        it 'returns the correct local_hour' do
-          expect(subject.local_hour).to eq(expected_local_hour)
-        end
-      end
-    end
-  end
-
-  describe '#markdown_name' do
-    it 'returns markdown name with timezone info' do
-      expect(subject.markdown_name).to eq("#{options['markdown_name']} (UTC+2)")
-    end
-
-    context 'when offset is 1.5' do
-      let(:tz_offset_hours) { 1.5 }
-
-      it 'returns markdown name with timezone info, not truncated' do
-        expect(subject.markdown_name).to eq("#{options['markdown_name']} (UTC+1.5)")
-      end
-    end
-
-    context 'when author is given' do
-      where(:tz_offset_hours, :author_offset, :diff_text) do
-        -12 | -10 | "2 hours behind `@mario`"
-        -10 | -12 | "2 hours ahead of `@mario`"
-        -10 | 2 | "12 hours behind `@mario`"
-        2 | 4 | "2 hours behind `@mario`"
-        4 | 2 | "2 hours ahead of `@mario`"
-        2 | 3 | "1 hour behind `@mario`"
-        3 | 2 | "1 hour ahead of `@mario`"
-        2 | 2 | "same timezone as `@mario`"
-      end
-
-      with_them do
-        it 'returns markdown name with timezone info' do
-          author = described_class.new(options.merge('username' => 'mario', 'tz_offset_hours' => author_offset))
-
-          floored_offset_hours = subject.__send__(:floored_offset_hours)
-          utc_offset = floored_offset_hours >= 0 ? "+#{floored_offset_hours}" : floored_offset_hours
-
-          expect(subject.markdown_name(author: author)).to eq("#{options['markdown_name']} (UTC#{utc_offset}, #{diff_text})")
-        end
-      end
-    end
-  end
-end
diff --git a/spec/tooling/danger/title_linting_spec.rb b/spec/tooling/danger/title_linting_spec.rb
deleted file mode 100644
index 7bc1684cd87d5cc2148d1576a713c8355bfb0ec2..0000000000000000000000000000000000000000
--- a/spec/tooling/danger/title_linting_spec.rb
+++ /dev/null
@@ -1,91 +0,0 @@
-# frozen_string_literal: true
-
-require 'rspec-parameterized'
-
-require_relative '../../../tooling/danger/title_linting'
-
-RSpec.describe Tooling::Danger::TitleLinting do
-  using RSpec::Parameterized::TableSyntax
-
-  describe '#sanitize_mr_title' do
-    where(:mr_title, :expected_mr_title) do
-      '`My MR title`' | "\\`My MR title\\`"
-      'WIP: My MR title' | 'My MR title'
-      'Draft: My MR title' | 'My MR title'
-      '(Draft) My MR title' | 'My MR title'
-      '[Draft] My MR title' | 'My MR title'
-      '[DRAFT] My MR title' | 'My MR title'
-      'DRAFT: My MR title' | 'My MR title'
-      'DRAFT: `My MR title`' | "\\`My MR title\\`"
-    end
-
-    with_them do
-      subject { described_class.sanitize_mr_title(mr_title) }
-
-      it { is_expected.to eq(expected_mr_title) }
-    end
-  end
-
-  describe '#remove_draft_flag' do
-    where(:mr_title, :expected_mr_title) do
-      'WIP: My MR title' | 'My MR title'
-      'Draft: My MR title' | 'My MR title'
-      '(Draft) My MR title' | 'My MR title'
-      '[Draft] My MR title' | 'My MR title'
-      '[DRAFT] My MR title' | 'My MR title'
-      'DRAFT: My MR title' | 'My MR title'
-    end
-
-    with_them do
-      subject { described_class.remove_draft_flag(mr_title) }
-
-      it { is_expected.to eq(expected_mr_title) }
-    end
-  end
-
-  describe '#has_draft_flag?' do
-    it 'returns true for a draft title' do
-      expect(described_class.has_draft_flag?('Draft: My MR title')).to be true
-    end
-
-    it 'returns false for non draft title' do
-      expect(described_class.has_draft_flag?('My MR title')).to be false
-    end
-  end
-
-  describe '#has_cherry_pick_flag?' do
-    [
-      'Cherry Pick !1234',
-      'cherry-pick !1234',
-      'CherryPick !1234'
-    ].each do |mr_title|
-      it 'returns true for cherry-pick title' do
-        expect(described_class.has_cherry_pick_flag?(mr_title)).to be true
-      end
-    end
-
-    it 'returns false for non cherry-pick title' do
-      expect(described_class.has_cherry_pick_flag?('My MR title')).to be false
-    end
-  end
-
-  describe '#has_run_all_rspec_flag?' do
-    it 'returns true for a title that includes RUN ALL RSPEC' do
-      expect(described_class.has_run_all_rspec_flag?('My MR title RUN ALL RSPEC')).to be true
-    end
-
-    it 'returns true for a title that does not include RUN ALL RSPEC' do
-      expect(described_class.has_run_all_rspec_flag?('My MR title')).to be false
-    end
-  end
-
-  describe '#has_run_as_if_foss_flag?' do
-    it 'returns true for a title that includes RUN AS-IF-FOSS' do
-      expect(described_class.has_run_as_if_foss_flag?('My MR title RUN AS-IF-FOSS')).to be true
-    end
-
-    it 'returns true for a title that does not include RUN AS-IF-FOSS' do
-      expect(described_class.has_run_as_if_foss_flag?('My MR title')).to be false
-    end
-  end
-end
diff --git a/spec/tooling/danger/weightage/maintainers_spec.rb b/spec/tooling/danger/weightage/maintainers_spec.rb
deleted file mode 100644
index b99ffe706a477a6813cee88a60ff9577de47d0df..0000000000000000000000000000000000000000
--- a/spec/tooling/danger/weightage/maintainers_spec.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-# frozen_string_literal: true
-
-require_relative '../../../../tooling/danger/weightage/maintainers'
-
-RSpec.describe Tooling::Danger::Weightage::Maintainers do
-  let(:multiplier) { Tooling::Danger::Weightage::CAPACITY_MULTIPLIER }
-  let(:regular_maintainer) { double('Teammate', reduced_capacity: false) }
-  let(:reduced_capacity_maintainer) { double('Teammate', reduced_capacity: true) }
-  let(:maintainers) do
-    [
-      regular_maintainer,
-      reduced_capacity_maintainer
-    ]
-  end
-
-  let(:maintainer_count) { Tooling::Danger::Weightage::BASE_REVIEWER_WEIGHT * multiplier }
-  let(:reduced_capacity_maintainer_count) { Tooling::Danger::Weightage::BASE_REVIEWER_WEIGHT }
-
-  subject(:weighted_maintainers) { described_class.new(maintainers).execute }
-
-  describe '#execute' do
-    it 'weights the maintainers overall' do
-      expect(weighted_maintainers.count).to eq maintainer_count + reduced_capacity_maintainer_count
-    end
-
-    it 'has total count of regular maintainers' do
-      expect(weighted_maintainers.count { |r| r.object_id == regular_maintainer.object_id }).to eq maintainer_count
-    end
-
-    it 'has count of reduced capacity maintainers' do
-      expect(weighted_maintainers.count { |r| r.object_id == reduced_capacity_maintainer.object_id }).to eq reduced_capacity_maintainer_count
-    end
-  end
-end
diff --git a/spec/tooling/danger/weightage/reviewers_spec.rb b/spec/tooling/danger/weightage/reviewers_spec.rb
deleted file mode 100644
index 5693ce7a10cc794b67f9c5f2bb172c0b89cb5a65..0000000000000000000000000000000000000000
--- a/spec/tooling/danger/weightage/reviewers_spec.rb
+++ /dev/null
@@ -1,63 +0,0 @@
-# frozen_string_literal: true
-
-require_relative '../../../../tooling/danger/weightage/reviewers'
-
-RSpec.describe Tooling::Danger::Weightage::Reviewers do
-  let(:multiplier) { Tooling::Danger::Weightage::CAPACITY_MULTIPLIER }
-  let(:regular_reviewer) { double('Teammate', hungry: false, reduced_capacity: false) }
-  let(:hungry_reviewer) { double('Teammate', hungry: true, reduced_capacity: false) }
-  let(:reduced_capacity_reviewer) { double('Teammate', hungry: false, reduced_capacity: true) }
-  let(:reviewers) do
-    [
-      hungry_reviewer,
-      regular_reviewer,
-      reduced_capacity_reviewer
-    ]
-  end
-
-  let(:regular_traintainer) { double('Teammate', hungry: false, reduced_capacity: false) }
-  let(:hungry_traintainer) { double('Teammate', hungry: true, reduced_capacity: false) }
-  let(:reduced_capacity_traintainer) { double('Teammate', hungry: false, reduced_capacity: true) }
-  let(:traintainers) do
-    [
-      hungry_traintainer,
-      regular_traintainer,
-      reduced_capacity_traintainer
-    ]
-  end
-
-  let(:hungry_reviewer_count) { Tooling::Danger::Weightage::BASE_REVIEWER_WEIGHT * multiplier + described_class::DEFAULT_REVIEWER_WEIGHT }
-  let(:hungry_traintainer_count) { described_class::TRAINTAINER_WEIGHT * multiplier + described_class::DEFAULT_REVIEWER_WEIGHT }
-  let(:reviewer_count) { Tooling::Danger::Weightage::BASE_REVIEWER_WEIGHT * multiplier }
-  let(:traintainer_count) { Tooling::Danger::Weightage::BASE_REVIEWER_WEIGHT * described_class::TRAINTAINER_WEIGHT * multiplier }
-  let(:reduced_capacity_reviewer_count) { Tooling::Danger::Weightage::BASE_REVIEWER_WEIGHT }
-  let(:reduced_capacity_traintainer_count) { described_class::TRAINTAINER_WEIGHT }
-
-  subject(:weighted_reviewers) { described_class.new(reviewers, traintainers).execute }
-
-  describe '#execute', :aggregate_failures do
-    it 'weights the reviewers overall' do
-      reviewers_count = hungry_reviewer_count + reviewer_count + reduced_capacity_reviewer_count
-      traintainers_count = hungry_traintainer_count + traintainer_count + reduced_capacity_traintainer_count
-
-      expect(weighted_reviewers.count).to eq reviewers_count + traintainers_count
-    end
-
-    it 'has total count of hungry reviewers and traintainers' do
-      expect(weighted_reviewers.count(&:hungry)).to eq hungry_reviewer_count + hungry_traintainer_count
-      expect(weighted_reviewers.count { |r| r.object_id == hungry_reviewer.object_id }).to eq hungry_reviewer_count
-      expect(weighted_reviewers.count { |r| r.object_id == hungry_traintainer.object_id }).to eq hungry_traintainer_count
-    end
-
-    it 'has total count of regular reviewers and traintainers' do
-      expect(weighted_reviewers.count { |r| r.object_id == regular_reviewer.object_id }).to eq reviewer_count
-      expect(weighted_reviewers.count { |r| r.object_id == regular_traintainer.object_id }).to eq traintainer_count
-    end
-
-    it 'has count of reduced capacity reviewers' do
-      expect(weighted_reviewers.count(&:reduced_capacity)).to eq reduced_capacity_reviewer_count + reduced_capacity_traintainer_count
-      expect(weighted_reviewers.count { |r| r.object_id == reduced_capacity_reviewer.object_id }).to eq reduced_capacity_reviewer_count
-      expect(weighted_reviewers.count { |r| r.object_id == reduced_capacity_traintainer.object_id }).to eq reduced_capacity_traintainer_count
-    end
-  end
-end
diff --git a/spec/tooling/gitlab_danger_spec.rb b/spec/tooling/gitlab_danger_spec.rb
deleted file mode 100644
index 20ac40d1d2a89bcaf65e49022a455026a2d0d888..0000000000000000000000000000000000000000
--- a/spec/tooling/gitlab_danger_spec.rb
+++ /dev/null
@@ -1,76 +0,0 @@
-# frozen_string_literal: true
-
-require_relative '../../tooling/gitlab_danger'
-
-RSpec.describe GitlabDanger do
-  let(:gitlab_danger_helper) { nil }
-
-  subject { described_class.new(gitlab_danger_helper) }
-
-  describe '.local_warning_message' do
-    it 'returns an informational message with rules that can run' do
-      expect(described_class.local_warning_message).to eq("==> Only the following Danger rules can be run locally: #{described_class::LOCAL_RULES.join(', ')}")
-    end
-  end
-
-  describe '.success_message' do
-    it 'returns an informational success message' do
-      expect(described_class.success_message).to eq('==> No Danger rule violations!')
-    end
-  end
-
-  describe '#rule_names' do
-    context 'when running locally' do
-      it 'returns local only rules' do
-        expect(subject.rule_names).to eq(described_class::LOCAL_RULES)
-      end
-    end
-
-    context 'when running under CI' do
-      let(:gitlab_danger_helper) { double('danger_gitlab_helper') }
-
-      it 'returns all rules' do
-        expect(subject.rule_names).to eq(described_class::LOCAL_RULES | described_class::CI_ONLY_RULES)
-      end
-    end
-  end
-
-  describe '#html_link' do
-    context 'when running locally' do
-      it 'returns the same string' do
-        str = 'something'
-
-        expect(subject.html_link(str)).to eq(str)
-      end
-    end
-
-    context 'when running under CI' do
-      let(:gitlab_danger_helper) { double('danger_gitlab_helper') }
-
-      it 'returns a HTML link formatted version of the string' do
-        str = 'something'
-        html_formatted_str = %Q{<a href="#{str}">#{str}</a>}
-
-        expect(gitlab_danger_helper).to receive(:html_link).with(str).and_return(html_formatted_str)
-
-        expect(subject.html_link(str)).to eq(html_formatted_str)
-      end
-    end
-  end
-
-  describe '#ci?' do
-    context 'when gitlab_danger_helper is not available' do
-      it 'returns false' do
-        expect(subject.ci?).to be_falsey
-      end
-    end
-
-    context 'when gitlab_danger_helper is available' do
-      let(:gitlab_danger_helper) { double('danger_gitlab_helper') }
-
-      it 'returns true' do
-        expect(subject.ci?).to be_truthy
-      end
-    end
-  end
-end
diff --git a/tooling/danger/base_linter.rb b/tooling/danger/base_linter.rb
deleted file mode 100644
index c58f2d84dc80102d2648fbc71d711f63a8a6ea1b..0000000000000000000000000000000000000000
--- a/tooling/danger/base_linter.rb
+++ /dev/null
@@ -1,96 +0,0 @@
-# frozen_string_literal: true
-
-require_relative 'title_linting'
-
-module Tooling
-  module Danger
-    class BaseLinter
-      MIN_SUBJECT_WORDS_COUNT = 3
-      MAX_LINE_LENGTH = 72
-
-      attr_reader :commit, :problems
-
-      def self.problems_mapping
-        {
-          subject_too_short: "The %s must contain at least #{MIN_SUBJECT_WORDS_COUNT} words",
-          subject_too_long: "The %s may not be longer than #{MAX_LINE_LENGTH} characters",
-          subject_starts_with_lowercase: "The %s must start with a capital letter",
-          subject_ends_with_a_period: "The %s must not end with a period"
-        }
-      end
-
-      def self.subject_description
-        'commit subject'
-      end
-
-      def initialize(commit)
-        @commit = commit
-        @problems = {}
-      end
-
-      def failed?
-        problems.any?
-      end
-
-      def add_problem(problem_key, *args)
-        @problems[problem_key] = sprintf(self.class.problems_mapping[problem_key], *args)
-      end
-
-      def lint_subject
-        if subject_too_short?
-          add_problem(:subject_too_short, self.class.subject_description)
-        end
-
-        if subject_too_long?
-          add_problem(:subject_too_long, self.class.subject_description)
-        end
-
-        if subject_starts_with_lowercase?
-          add_problem(:subject_starts_with_lowercase, self.class.subject_description)
-        end
-
-        if subject_ends_with_a_period?
-          add_problem(:subject_ends_with_a_period, self.class.subject_description)
-        end
-
-        self
-      end
-
-      private
-
-      def subject
-        TitleLinting.remove_draft_flag(message_parts[0])
-      end
-
-      def subject_too_short?
-        subject.split(' ').length < MIN_SUBJECT_WORDS_COUNT
-      end
-
-      def subject_too_long?
-        line_too_long?(subject)
-      end
-
-      def line_too_long?(line)
-        line.length > MAX_LINE_LENGTH
-      end
-
-      def subject_starts_with_lowercase?
-        return false if ('A'..'Z').cover?(subject[0])
-
-        first_char = subject.sub(/\A(\[.+\]|\w+:)\s/, '')[0]
-        first_char_downcased = first_char.downcase
-        return true unless ('a'..'z').cover?(first_char_downcased)
-
-        first_char.downcase == first_char
-      end
-
-      def subject_ends_with_a_period?
-        subject.end_with?('.')
-      end
-
-      def message_parts
-        @message_parts ||= commit.message.split("\n", 3)
-      end
-    end
-  end
-end
diff --git a/tooling/danger/changelog.rb b/tooling/danger/changelog.rb
index 86184b38459bc992a51bf9d21f790e306085cace..672d23d58e4113c9fefe17d3247cf590e8814847 100644
--- a/tooling/danger/changelog.rb
+++ b/tooling/danger/changelog.rb
@@ -1,6 +1,6 @@
 # frozen_string_literal: true
 
-require_relative 'title_linting'
+require 'gitlab/dangerfiles/title_linting'
 
 module Tooling
   module Danger
@@ -44,8 +44,8 @@ module Changelog
 
       def required_reasons
         [].tap do |reasons|
-          reasons << :db_changes if helper.changes.added.has_category?(:migration)
-          reasons << :feature_flag_removed if helper.changes.deleted.has_category?(:feature_flag)
+          reasons << :db_changes if project_helper.changes.added.has_category?(:migration)
+          reasons << :feature_flag_removed if project_helper.changes.deleted.has_category?(:feature_flag)
         end
       end
 
@@ -58,7 +58,7 @@ def optional?
       end
 
       def found
-        @found ||= helper.changes.added.by_category(:changelog).files.first
+        @found ||= project_helper.changes.added.by_category(:changelog).files.first
       end
 
       def ee_changelog?
@@ -86,11 +86,11 @@ def optional_text
       private
 
       def sanitized_mr_title
-        TitleLinting.sanitize_mr_title(helper.mr_title)
+        Gitlab::Dangerfiles::TitleLinting.sanitize_mr_title(helper.mr_title)
       end
 
       def categories_need_changelog?
-        (helper.changes.categories - NO_CHANGELOG_CATEGORIES).any?
+        (project_helper.changes.categories - NO_CHANGELOG_CATEGORIES).any?
       end
 
       def without_no_changelog_label?
diff --git a/tooling/danger/commit_linter.rb b/tooling/danger/commit_linter.rb
deleted file mode 100644
index 905031ec881be3cc960ecc13241e08eb12a27c20..0000000000000000000000000000000000000000
--- a/tooling/danger/commit_linter.rb
+++ /dev/null
@@ -1,150 +0,0 @@
-# frozen_string_literal: true
-
-require_relative 'base_linter'
-require_relative 'emoji_checker'
-
-module Tooling
-  module Danger
-    class CommitLinter < BaseLinter
-      MAX_CHANGED_FILES_IN_COMMIT = 3
-      MAX_CHANGED_LINES_IN_COMMIT = 30
-      SHORT_REFERENCE_REGEX = %r{([\w\-\/]+)?(?<!`)(#|!|&|%)\d+(?<!`)}.freeze
-
-      def self.problems_mapping
-        super.merge(
-          {
-            separator_missing: "The commit subject and body must be separated by a blank line",
-            details_too_many_changes: "Commits that change #{MAX_CHANGED_LINES_IN_COMMIT} or more lines across " \
-          "at least #{MAX_CHANGED_FILES_IN_COMMIT} files must describe these changes in the commit body",
-            details_line_too_long: "The commit body should not contain more than #{MAX_LINE_LENGTH} characters per line",
-            message_contains_text_emoji: "Avoid the use of Markdown Emoji such as `:+1:`. These add limited value " \
-          "to the commit message, and are displayed as plain text outside of GitLab",
-            message_contains_unicode_emoji: "Avoid the use of Unicode Emoji. These add no value to the commit " \
-          "message, and may not be displayed properly everywhere",
-            message_contains_short_reference: "Use full URLs instead of short references (`gitlab-org/gitlab#123` or " \
-          "`!123`), as short references are displayed as plain text outside of GitLab"
-          }
-        )
-      end
-
-      def initialize(commit)
-        super
-
-        @linted = false
-      end
-
-      def fixup?
-        commit.message.start_with?('fixup!', 'squash!')
-      end
-
-      def suggestion?
-        commit.message.start_with?('Apply suggestion to')
-      end
-
-      def merge?
-        commit.message.start_with?('Merge branch')
-      end
-
-      def revert?
-        commit.message.start_with?('Revert "')
-      end
-
-      def multi_line?
-        !details.nil? && !details.empty?
-      end
-
-      def lint
-        return self if @linted
-
-        @linted = true
-        lint_subject
-        lint_separator
-        lint_details
-        lint_message
-
-        self
-      end
-
-      private
-
-      def lint_separator
-        return self unless separator && !separator.empty?
-
-        add_problem(:separator_missing)
-
-        self
-      end
-
-      def lint_details
-        if !multi_line? && many_changes?
-          add_problem(:details_too_many_changes)
-        end
-
-        details&.each_line do |line|
-          line_without_urls = line.strip.gsub(%r{https?://\S+}, '')
-
-          # If the line includes a URL, we'll allow it to exceed MAX_LINE_LENGTH characters, but
-          # only if the line _without_ the URL does not exceed this limit.
-          next unless line_too_long?(line_without_urls)
-
-          add_problem(:details_line_too_long)
-          break
-        end
-
-        self
-      end
-
-      def lint_message
-        if message_contains_text_emoji?
-          add_problem(:message_contains_text_emoji)
-        end
-
-        if message_contains_unicode_emoji?
-          add_problem(:message_contains_unicode_emoji)
-        end
-
-        if message_contains_short_reference?
-          add_problem(:message_contains_short_reference)
-        end
-
-        self
-      end
-
-      def files_changed
-        commit.diff_parent.stats[:total][:files]
-      end
-
-      def lines_changed
-        commit.diff_parent.stats[:total][:lines]
-      end
-
-      def many_changes?
-        files_changed > MAX_CHANGED_FILES_IN_COMMIT && lines_changed > MAX_CHANGED_LINES_IN_COMMIT
-      end
-
-      def separator
-        message_parts[1]
-      end
-
-      def details
-        message_parts[2]&.gsub(/^Signed-off-by.*$/, '')
-      end
-
-      def message_contains_text_emoji?
-        emoji_checker.includes_text_emoji?(commit.message)
-      end
-
-      def message_contains_unicode_emoji?
-        emoji_checker.includes_unicode_emoji?(commit.message)
-      end
-
-      def message_contains_short_reference?
-        commit.message.match?(SHORT_REFERENCE_REGEX)
-      end
-
-      def emoji_checker
-        @emoji_checker ||= Tooling::Danger::EmojiChecker.new
-      end
-    end
-  end
-end
diff --git a/tooling/danger/emoji_checker.rb b/tooling/danger/emoji_checker.rb
deleted file mode 100644
index 9d8ff93037ccfbe3079c5a055b7f1bd7b9c5d98f..0000000000000000000000000000000000000000
--- a/tooling/danger/emoji_checker.rb
+++ /dev/null
@@ -1,45 +0,0 @@
-# frozen_string_literal: true
-
-require 'json'
-
-module Tooling
-  module Danger
-    class EmojiChecker
-      DIGESTS = File.expand_path('../../fixtures/emojis/digests.json', __dir__)
-      ALIASES = File.expand_path('../../fixtures/emojis/aliases.json', __dir__)
-
-      # A regex that indicates a piece of text _might_ include an Emoji. The regex
-      # alone is not enough, as we'd match `:foo:bar:baz`. Instead, we use this
-      # regex to save us from having to check for all possible emoji names when we
-      # know one definitely is not included.
-      LIKELY_EMOJI = /:[\+a-z0-9_\-]+:/.freeze
-
-      UNICODE_EMOJI_REGEX = %r{(
-        [\u{1F300}-\u{1F5FF}] |
-        [\u{1F1E6}-\u{1F1FF}] |
-        [\u{2700}-\u{27BF}] |
-        [\u{1F900}-\u{1F9FF}] |
-        [\u{1F600}-\u{1F64F}] |
-        [\u{1F680}-\u{1F6FF}] |
-        [\u{2600}-\u{26FF}]
-      )}x.freeze
-
-      def initialize
-        names = JSON.parse(File.read(DIGESTS)).keys +
-          JSON.parse(File.read(ALIASES)).keys
-
-        @emoji = names.map { |name| ":#{name}:" }
-      end
-
-      def includes_text_emoji?(text)
-        return false unless text.match?(LIKELY_EMOJI)
-
-        @emoji.any? { |emoji| text.include?(emoji) }
-      end
-
-      def includes_unicode_emoji?(text)
-        text.match?(UNICODE_EMOJI_REGEX)
-      end
-    end
-  end
-end
diff --git a/tooling/danger/helper.rb b/tooling/danger/helper.rb
deleted file mode 100644
index ef5b2e16bb028d37e35ce96e82da4c3198835f4d..0000000000000000000000000000000000000000
--- a/tooling/danger/helper.rb
+++ /dev/null
@@ -1,383 +0,0 @@
-# frozen_string_literal: true
-
-require 'delegate'
-
-require_relative 'teammate'
-require_relative 'title_linting'
-
-module Tooling
-  module Danger
-    module Helper
-      RELEASE_TOOLS_BOT = 'gitlab-release-tools-bot'
-
-      # Returns a list of all files that have been added, modified or renamed.
-      # `git.modified_files` might contain paths that already have been renamed,
-      # so we need to remove them from the list.
-      #
-      # Considering these changes:
-      #
-      # - A new_file.rb
-      # - D deleted_file.rb
-      # - M modified_file.rb
-      # - R renamed_file_before.rb -> renamed_file_after.rb
-      #
-      # it will return
-      # ```
-      # [ 'new_file.rb', 'modified_file.rb', 'renamed_file_after.rb' ]
-      # ```
-      #
-      # @return [Array<String>]
-      def all_changed_files
-        Set.new
-          .merge(git.added_files.to_a)
-          .merge(git.modified_files.to_a)
-          .merge(git.renamed_files.map { |x| x[:after] })
-          .subtract(git.renamed_files.map { |x| x[:before] })
-          .to_a
-          .sort
-      end
-
-      # Returns a string containing changed lines as git diff
-      #
-      # Considering changing a line in lib/gitlab/usage_data.rb it will return:
-      #
-      # [ "--- a/lib/gitlab/usage_data.rb",
-      #   "+++ b/lib/gitlab/usage_data.rb",
-      #   "+      # Test change",
-      #   "-      # Old change" ]
-      def changed_lines(changed_file)
-        diff = git.diff_for_file(changed_file)
-        return [] unless diff
-
-        diff.patch.split("\n").select { |line| %r{^[+-]}.match?(line) }
-      end
-
-      def all_ee_changes
-        all_changed_files.grep(%r{\Aee/})
-      end
-
-      def ee?
-        # Support former project name for `dev` and support local Danger run
-        %w[gitlab gitlab-ee].include?(ENV['CI_PROJECT_NAME']) || Dir.exist?(File.expand_path('../../../ee', __dir__))
-      end
-
-      def gitlab_helper
-        # Unfortunately the following does not work:
-        # - respond_to?(:gitlab)
-        # - respond_to?(:gitlab, true)
-        gitlab
-      rescue NameError
-        nil
-      end
-
-      def release_automation?
-        gitlab_helper&.mr_author == RELEASE_TOOLS_BOT
-      end
-
-      def project_name
-        ee? ? 'gitlab' : 'gitlab-foss'
-      end
-
-      def markdown_list(items)
-        list = items.map { |item| "* `#{item}`" }.join("\n")
-
-        if items.size > 10
-          "\n<details>\n\n#{list}\n\n</details>\n"
-        else
-          list
-        end
-      end
-
-      Change = Struct.new(:file, :change_type, :category)
-
-      class Changes < ::SimpleDelegator
-        def added
-          select_by_change_type(:added)
-        end
-
-        def modified
-          select_by_change_type(:modified)
-        end
-
-        def deleted
-          select_by_change_type(:deleted)
-        end
-
-        def renamed_before
-          select_by_change_type(:renamed_before)
-        end
-
-        def renamed_after
-          select_by_change_type(:renamed_after)
-        end
-
-        def has_category?(category)
-          any? { |change| change.category == category }
-        end
-
-        def by_category(category)
-          Changes.new(select { |change| change.category == category })
-        end
-
-        def categories
-          map(&:category).uniq
-        end
-
-        def files
-          map(&:file)
-        end
-
-        private
-
-        def select_by_change_type(change_type)
-          Changes.new(select { |change| change.change_type == change_type })
-        end
-      end
-
-      # @return [Hash<Symbol,Array<String>>]
-      def changes_by_category
-        all_changed_files.each_with_object(Hash.new { |h, k| h[k] = [] }) do |file, hash|
-          categories_for_file(file).each { |category| hash[category] << file }
-        end
-      end
-
-      # @return [Changes]
-      def changes
-        Changes.new([]).tap do |changes|
-          git.added_files.each do |file|
-            categories_for_file(file).each { |category| changes << Change.new(file, :added, category) }
-          end
-
-          git.modified_files.each do |file|
-            categories_for_file(file).each { |category| changes << Change.new(file, :modified, category) }
-          end
-
-          git.deleted_files.each do |file|
-            categories_for_file(file).each { |category| changes << Change.new(file, :deleted, category) }
-          end
-
-          git.renamed_files.map { |x| x[:before] }.each do |file|
-            categories_for_file(file).each { |category| changes << Change.new(file, :renamed_before, category) }
-          end
-
-          git.renamed_files.map { |x| x[:after] }.each do |file|
-            categories_for_file(file).each { |category| changes << Change.new(file, :renamed_after, category) }
-          end
-        end
-      end
-
-      # Determines the categories a file is in, e.g., `[:frontend]`, `[:backend]`, or  `%i[frontend engineering_productivity]`
-      # using filename regex and specific change regex if given.
-      #
-      # @return Array<Symbol>
-      def categories_for_file(file)
-        _, categories = CATEGORIES.find do |key, _|
-          filename_regex, changes_regex = Array(key)
-
-          found = filename_regex.match?(file)
-          found &&= changed_lines(file).any? { |changed_line| changes_regex.match?(changed_line) } if changes_regex
-
-          found
-        end
-
-        Array(categories || :unknown)
-      end
-
-      # Returns the GFM for a category label, making its best guess if it's not
-      # a category we know about.
-      #
-      # @return[String]
-      def label_for_category(category)
-        CATEGORY_LABELS.fetch(category, "~#{category}")
-      end
-
-      CATEGORY_LABELS = {
-        docs: "~documentation", # Docs are reviewed along DevOps stages, so don't need roulette for now.
-        none: "",
-        qa: "~QA",
-        test: "~test ~Quality for `spec/features/*`",
-        engineering_productivity: '~"Engineering Productivity" for CI, Danger',
-        ci_template: '~"ci::templates"'
-      }.freeze
-      # First-match win, so be sure to put more specific regex at the top...
-      CATEGORIES = {
-        [%r{usage_data\.rb}, %r{^(\+|-).*\s+(count|distinct_count|estimate_batch_distinct_count)\(.*\)(.*)$}] => [:database, :backend],
-
-        %r{\A(ee/)?config/feature_flags/} => :feature_flag,
-
-        %r{\A(ee/)?(changelogs/unreleased)(-ee)?/} => :changelog,
-
-        %r{\Adoc/.*(\.(md|png|gif|jpg))\z} => :docs,
-        %r{\A(CONTRIBUTING|LICENSE|MAINTENANCE|PHILOSOPHY|PROCESS|README)(\.md)?\z} => :docs,
-        %r{\Adata/whats_new/} => :docs,
-
-        %r{\A(ee/)?app/(assets|views)/} => :frontend,
-        %r{\A(ee/)?public/} => :frontend,
-        %r{\A(ee/)?spec/(javascripts|frontend)/} => :frontend,
-        %r{\A(ee/)?vendor/assets/} => :frontend,
-        %r{\A(ee/)?scripts/frontend/} => :frontend,
-        %r{(\A|/)(
-          \.babelrc |
-          \.eslintignore |
-          \.eslintrc(\.yml)? |
-          \.nvmrc |
-          \.prettierignore |
-          \.prettierrc |
-          \.stylelintrc |
-          \.haml-lint.yml |
-          \.haml-lint_todo.yml |
-          babel\.config\.js |
-          jest\.config\.js |
-          package\.json |
-          yarn\.lock |
-          config/.+\.js
-        )\z}x => :frontend,
-
-        %r{(\A|/)(
-          \.gitlab/ci/frontend\.gitlab-ci\.yml
-        )\z}x => %i[frontend engineering_productivity],
-
-        %r{\A(ee/)?db/(geo/)?(migrate|post_migrate)/} => [:database, :migration],
-        %r{\A(ee/)?db/(?!fixtures)[^/]+} => :database,
-        %r{\A(ee/)?lib/gitlab/(database|background_migration|sql|github_import)(/|\.rb)} => :database,
-        %r{\A(app/models/project_authorization|app/services/users/refresh_authorized_projects_service)(/|\.rb)} => :database,
-        %r{\A(ee/)?app/finders/} => :database,
-        %r{\Arubocop/cop/migration(/|\.rb)} => :database,
-
-        %r{\A(\.gitlab-ci\.yml\z|\.gitlab\/ci)} => :engineering_productivity,
-        %r{\A\.codeclimate\.yml\z} => :engineering_productivity,
-        %r{\Alefthook.yml\z} => :engineering_productivity,
-        %r{\A\.editorconfig\z} => :engineering_productivity,
-        %r{Dangerfile\z} => :engineering_productivity,
-        %r{\A(ee/)?(danger/|tooling/danger/)} => :engineering_productivity,
-        %r{\A(ee/)?scripts/} => :engineering_productivity,
-        %r{\Atooling/} => :engineering_productivity,
-        %r{(CODEOWNERS)} => :engineering_productivity,
-        %r{(tests.yml)} => :engineering_productivity,
-
-        %r{\Alib/gitlab/ci/templates} => :ci_template,
-
-        %r{\A(ee/)?spec/features/} => :test,
-        %r{\A(ee/)?spec/support/shared_examples/features/} => :test,
-        %r{\A(ee/)?spec/support/shared_contexts/features/} => :test,
-        %r{\A(ee/)?spec/support/helpers/features/} => :test,
-
-        %r{\A(ee/)?app/(?!assets|views)[^/]+} => :backend,
-        %r{\A(ee/)?(bin|config|generator_templates|lib|rubocop)/} => :backend,
-        %r{\A(ee/)?spec/} => :backend,
-        %r{\A(ee/)?vendor/} => :backend,
-        %r{\A(Gemfile|Gemfile.lock|Rakefile)\z} => :backend,
-        %r{\A[A-Z_]+_VERSION\z} => :backend,
-        %r{\A\.rubocop((_manual)?_todo)?\.yml\z} => :backend,
-        %r{\Afile_hooks/} => :backend,
-
-        %r{\A(ee/)?qa/} => :qa,
-
-        # Files that don't fit into any category are marked with :none
-        %r{\A(ee/)?changelogs/} => :none,
-        %r{\Alocale/gitlab\.pot\z} => :none,
-
-        # GraphQL auto generated doc files and schema
-        %r{\Adoc/api/graphql/reference/} => :backend,
-
-        # Fallbacks in case the above patterns miss anything
-        %r{\.rb\z} => :backend,
-        %r{(
-          \.(md|txt)\z |
-          \.markdownlint\.json
-        )}x => :none, # To reinstate roulette for documentation, set to `:docs`.
-        %r{\.js\z} => :frontend
-      }.freeze
-
-      def new_teammates(usernames)
-        usernames.map { |u| Tooling::Danger::Teammate.new('username' => u) }
-      end
-
-      def mr_iid
-        return '' unless gitlab_helper
-
-        gitlab_helper.mr_json['iid']
-      end
-
-      def mr_title
-        return '' unless gitlab_helper
-
-        gitlab_helper.mr_json['title']
-      end
-
-      def mr_web_url
-        return '' unless gitlab_helper
-
-        gitlab_helper.mr_json['web_url']
-      end
-
-      def mr_labels
-        return [] unless gitlab_helper
-
-        gitlab_helper.mr_labels
-      end
-
-      def mr_target_branch
-        return '' unless gitlab_helper
-
-        gitlab_helper.mr_json['target_branch']
-      end
-
-      def draft_mr?
-        TitleLinting.has_draft_flag?(mr_title)
-      end
-
-      def security_mr?
-        mr_web_url.include?('/gitlab-org/security/')
-      end
-
-      def cherry_pick_mr?
-        TitleLinting.has_cherry_pick_flag?(mr_title)
-      end
-
-      def run_all_rspec_mr?
-        TitleLinting.has_run_all_rspec_flag?(mr_title)
-      end
-
-      def run_as_if_foss_mr?
-        TitleLinting.has_run_as_if_foss_flag?(mr_title)
-      end
-
-      def stable_branch?
-        /\A\d+-\d+-stable-ee/i.match?(mr_target_branch)
-      end
-
-      def mr_has_labels?(*labels)
-        labels = labels.flatten.uniq
-
-        (labels & mr_labels) == labels
-      end
-
-      def labels_list(labels, sep: ', ')
-        labels.map { |label| %Q{~"#{label}"} }.join(sep)
-      end
-
-      def prepare_labels_for_mr(labels)
-        return '' unless labels.any?
-
-        "/label #{labels_list(labels, sep: ' ')}"
-      end
-
-      def changed_files(regex)
-        all_changed_files.grep(regex)
-      end
-
-      def has_database_scoped_labels?(labels)
-        labels.any? { |label| label.start_with?('database::') }
-      end
-
-      def has_ci_changes?
-        changed_files(%r{\A(\.gitlab-ci\.yml|\.gitlab/ci/)}).any?
-      end
-
-      def group_label(labels)
-        labels.find { |label| label.start_with?('group::') }
-      end
-    end
-  end
-end
diff --git a/tooling/danger/merge_request_linter.rb b/tooling/danger/merge_request_linter.rb
deleted file mode 100644
index ddeb9cc2981b9334fd59efd057d83a3313c2fc3f..0000000000000000000000000000000000000000
--- a/tooling/danger/merge_request_linter.rb
+++ /dev/null
@@ -1,30 +0,0 @@
-# frozen_string_literal: true
-
-require_relative 'base_linter'
-
-module Tooling
-  module Danger
-    class MergeRequestLinter < BaseLinter
-      alias_method :lint, :lint_subject
-
-      def self.subject_description
-        'merge request title'
-      end
-
-      def self.mr_run_options_regex
-        [
-          'RUN AS-IF-FOSS',
-          'UPDATE CACHE',
-          'RUN ALL RSPEC',
-          'SKIP RSPEC FAIL-FAST'
-        ].join('|')
-      end
-
-      private
-
-      def subject
-        super.gsub(/\[?(#{self.class.mr_run_options_regex})\]?/, '').strip
-      end
-    end
-  end
-end
diff --git a/tooling/danger/project_helper.rb b/tooling/danger/project_helper.rb
new file mode 100644
index 0000000000000000000000000000000000000000..342bd8422ad5af8db8b69d18e8db445c2ff938dd
--- /dev/null
+++ b/tooling/danger/project_helper.rb
@@ -0,0 +1,181 @@
+# frozen_string_literal: true
+
+module Tooling
+  module Danger
+    module ProjectHelper
+      LOCAL_RULES ||= %w[
+        changes_size
+        commit_messages
+        database
+        documentation
+        duplicate_yarn_dependencies
+        eslint
+        karma
+        pajamas
+        pipeline
+        prettier
+        product_intelligence
+        utility_css
+      ].freeze
+
+      CI_ONLY_RULES ||= %w[
+        ce_ee_vue_templates
+        changelog
+        ci_templates
+        metadata
+        feature_flag
+        roulette
+        sidekiq_queues
+        specialization_labels
+        specs
+      ].freeze
+
+      MESSAGE_PREFIX = '==>'.freeze
+
+      # First-match win, so be sure to put more specific regex at the top...
+      CATEGORIES = {
+        [%r{usage_data\.rb}, %r{^(\+|-).*\s+(count|distinct_count|estimate_batch_distinct_count)\(.*\)(.*)$}] => [:database, :backend],
+
+        %r{\A(ee/)?config/feature_flags/} => :feature_flag,
+
+        %r{\A(ee/)?(changelogs/unreleased)(-ee)?/} => :changelog,
+
+        %r{\Adoc/.*(\.(md|png|gif|jpg))\z} => :docs,
+        %r{\A(CONTRIBUTING|LICENSE|MAINTENANCE|PHILOSOPHY|PROCESS|README)(\.md)?\z} => :docs,
+        %r{\Adata/whats_new/} => :docs,
+
+        %r{\A(ee/)?app/(assets|views)/} => :frontend,
+        %r{\A(ee/)?public/} => :frontend,
+        %r{\A(ee/)?spec/(javascripts|frontend)/} => :frontend,
+        %r{\A(ee/)?vendor/assets/} => :frontend,
+        %r{\A(ee/)?scripts/frontend/} => :frontend,
+        %r{(\A|/)(
+          \.babelrc |
+          \.eslintignore |
+          \.eslintrc(\.yml)? |
+          \.nvmrc |
+          \.prettierignore |
+          \.prettierrc |
+          \.stylelintrc |
+          \.haml-lint.yml |
+          \.haml-lint_todo.yml |
+          babel\.config\.js |
+          jest\.config\.js |
+          package\.json |
+          yarn\.lock |
+          config/.+\.js
+        )\z}x => :frontend,
+
+        %r{(\A|/)(
+          \.gitlab/ci/frontend\.gitlab-ci\.yml
+        )\z}x => %i[frontend engineering_productivity],
+
+        %r{\A(ee/)?db/(geo/)?(migrate|post_migrate)/} => [:database, :migration],
+        %r{\A(ee/)?db/(?!fixtures)[^/]+} => :database,
+        %r{\A(ee/)?lib/gitlab/(database|background_migration|sql|github_import)(/|\.rb)} => :database,
+        %r{\A(app/models/project_authorization|app/services/users/refresh_authorized_projects_service)(/|\.rb)} => :database,
+        %r{\A(ee/)?app/finders/} => :database,
+        %r{\Arubocop/cop/migration(/|\.rb)} => :database,
+
+        %r{\A(\.gitlab-ci\.yml\z|\.gitlab\/ci)} => :engineering_productivity,
+        %r{\A\.codeclimate\.yml\z} => :engineering_productivity,
+        %r{\Alefthook.yml\z} => :engineering_productivity,
+        %r{\A\.editorconfig\z} => :engineering_productivity,
+        %r{Dangerfile\z} => :engineering_productivity,
+        %r{\A(ee/)?(danger/|tooling/danger/)} => :engineering_productivity,
+        %r{\A(ee/)?scripts/} => :engineering_productivity,
+        %r{\Atooling/} => :engineering_productivity,
+        %r{(CODEOWNERS)} => :engineering_productivity,
+        %r{(tests.yml)} => :engineering_productivity,
+
+        %r{\Alib/gitlab/ci/templates} => :ci_template,
+
+        %r{\A(ee/)?spec/features/} => :test,
+        %r{\A(ee/)?spec/support/shared_examples/features/} => :test,
+        %r{\A(ee/)?spec/support/shared_contexts/features/} => :test,
+        %r{\A(ee/)?spec/support/helpers/features/} => :test,
+
+        %r{\A(ee/)?app/(?!assets|views)[^/]+} => :backend,
+        %r{\A(ee/)?(bin|config|generator_templates|lib|rubocop)/} => :backend,
+        %r{\A(ee/)?spec/} => :backend,
+        %r{\A(ee/)?vendor/} => :backend,
+        %r{\A(Gemfile|Gemfile.lock|Rakefile)\z} => :backend,
+        %r{\A[A-Z_]+_VERSION\z} => :backend,
+        %r{\A\.rubocop((_manual)?_todo)?\.yml\z} => :backend,
+        %r{\Afile_hooks/} => :backend,
+
+        %r{\A(ee/)?qa/} => :qa,
+
+        # Files that don't fit into any category are marked with :none
+        %r{\A(ee/)?changelogs/} => :none,
+        %r{\Alocale/gitlab\.pot\z} => :none,
+
+        # GraphQL auto generated doc files and schema
+        %r{\Adoc/api/graphql/reference/} => :backend,
+
+        # Fallbacks in case the above patterns miss anything
+        %r{\.rb\z} => :backend,
+        %r{(
+          \.(md|txt)\z |
+          \.markdownlint\.json
+        )}x => :none, # To reinstate roulette for documentation, set to `:docs`.
+        %r{\.js\z} => :frontend
+      }.freeze
+
+      def changes_by_category
+        helper.changes_by_category(CATEGORIES)
+      end
+
+      def changes
+        helper.changes(CATEGORIES)
+      end
+
+      def categories_for_file(file)
+        helper.categories_for_file(file, CATEGORIES)
+      end
+
+      def local_warning_message
+        "#{MESSAGE_PREFIX} Only the following Danger rules can be run locally: #{LOCAL_RULES.join(', ')}"
+      end
+      module_function :local_warning_message # rubocop:disable Style/AccessModifierDeclarations
+
+      def success_message
+        "#{MESSAGE_PREFIX} No Danger rule violations!"
+      end
+      module_function :success_message # rubocop:disable Style/AccessModifierDeclarations
+
+      def rule_names
+        helper.ci? ? LOCAL_RULES | CI_ONLY_RULES : LOCAL_RULES
+      end
+
+      def all_ee_changes
+        all_changed_files.grep(%r{\Aee/})
+      end
+
+      def project_name
+        ee? ? 'gitlab' : 'gitlab-foss'
+      end
+
+      def missing_database_labels(current_mr_labels)
+        labels = if has_database_scoped_labels?(current_mr_labels)
+                   ['database']
+                 else
+                   ['database', 'database::review pending']
+                 end
+
+        labels - current_mr_labels
+      end
+
+      private
+
+      def ee?
+        # Support former project name for `dev` and support local Danger run
+        %w[gitlab gitlab-ee].include?(ENV['CI_PROJECT_NAME']) || Dir.exist?(File.expand_path('../../../ee', __dir__))
+      end
+
+      def has_database_scoped_labels?(current_mr_labels)
+        current_mr_labels.any? { |label| label.start_with?('database::') }
+      end
+    end
+  end
+end
diff --git a/tooling/danger/request_helper.rb b/tooling/danger/request_helper.rb
deleted file mode 100644
index d6b99f562f92e1b7208f827411ac4ab8efe5a0f2..0000000000000000000000000000000000000000
--- a/tooling/danger/request_helper.rb
+++ /dev/null
@@ -1,23 +0,0 @@
-# frozen_string_literal: true
-
-require 'net/http'
-require 'json'
-
-module Tooling
-  module Danger
-    module RequestHelper
-      HTTPError = Class.new(RuntimeError)
-
-      # @param [String] url
-      def self.http_get_json(url)
-        rsp = Net::HTTP.get_response(URI.parse(url))
-
-        unless rsp.is_a?(Net::HTTPOK)
-          raise HTTPError, "Failed to read #{url}: #{rsp.code} #{rsp.message}"
-        end
-
-        JSON.parse(rsp.body)
-      end
-    end
-  end
-end
diff --git a/tooling/danger/roulette.rb b/tooling/danger/roulette.rb
deleted file mode 100644
index c928fb2b6554f24762eb1bf163934c9ceaff74e6..0000000000000000000000000000000000000000
--- a/tooling/danger/roulette.rb
+++ /dev/null
@@ -1,169 +0,0 @@
-# frozen_string_literal: true
-
-require_relative 'teammate'
-require_relative 'request_helper'
-require_relative 'weightage/reviewers'
-require_relative 'weightage/maintainers'
-
-module Tooling
-  module Danger
-    module Roulette
-      ROULETTE_DATA_URL = 'https://gitlab-org.gitlab.io/gitlab-roulette/roulette.json'
-      HOURS_WHEN_PERSON_CAN_BE_PICKED = (6..14).freeze
-
-      INCLUDE_TIMEZONE_FOR_CATEGORY = {
-        database: false
-      }.freeze
-
-      Spin = Struct.new(:category, :reviewer, :maintainer, :optional_role, :timezone_experiment)
-
-      def team_mr_author
-        team.find { |person| person.username == mr_author_username }
-      end
-
-      # Assigns GitLab team members to be reviewer and maintainer
-      # for each change category that a Merge Request contains.
-      #
-      # @return [Array<Spin>]
-      def spin(project, categories, timezone_experiment: false)
-        spins = categories.sort.map do |category|
-          including_timezone = INCLUDE_TIMEZONE_FOR_CATEGORY.fetch(category, timezone_experiment)
-
-          spin_for_category(project, category, timezone_experiment: including_timezone)
-        end
-
-        backend_spin = spins.find { |spin| spin.category == :backend }
-
-        spins.each do |spin|
-          including_timezone = INCLUDE_TIMEZONE_FOR_CATEGORY.fetch(spin.category, timezone_experiment)
-          case spin.category
-          when :qa
-            # MR includes QA changes, but also other changes, and author isn't an SET
-            if categories.size > 1 && !team_mr_author&.any_capability?(project, spin.category)
-              spin.optional_role = :maintainer
-            end
-          when :test
-            spin.optional_role = :maintainer
-
-            if spin.reviewer.nil?
-              # Fetch an already picked backend reviewer, or pick one otherwise
-              spin.reviewer = backend_spin&.reviewer || spin_for_category(project, :backend, timezone_experiment: including_timezone).reviewer
-            end
-          when :engineering_productivity
-            if spin.maintainer.nil?
-              # Fetch an already picked backend maintainer, or pick one otherwise
-              spin.maintainer = backend_spin&.maintainer || spin_for_category(project, :backend, timezone_experiment: including_timezone).maintainer
-            end
-          when :ci_template
-            if spin.maintainer.nil?
-              # Fetch an already picked backend maintainer, or pick one otherwise
-              spin.maintainer = backend_spin&.maintainer || spin_for_category(project, :backend, timezone_experiment: including_timezone).maintainer
-            end
-          end
-        end
-
-        spins
-      end
-
-      # Looks up the current list of GitLab team members and parses it into a
-      # useful form
-      #
-      # @return [Array<Teammate>]
-      def team
-        @team ||=
-          begin
-            data = Tooling::Danger::RequestHelper.http_get_json(ROULETTE_DATA_URL)
-            data.map { |hash| ::Tooling::Danger::Teammate.new(hash) }
-          rescue JSON::ParserError
-            raise "Failed to parse JSON response from #{ROULETTE_DATA_URL}"
-          end
-      end
-
-      # Like +team+, but only returns teammates in the current project, based on
-      # project_name.
-      #
-      # @return [Array<Teammate>]
-      def project_team(project_name)
-        team.select { |member| member.in_project?(project_name) }
-      rescue => err
-        warn("Reviewer roulette failed to load team data: #{err.message}")
-        []
-      end
-
-      # Known issue: If someone is rejected due to OOO, and then becomes not OOO, the
-      # selection will change on next spin
-      # @param [Array<Teammate>] people
-      def spin_for_person(people, random:, timezone_experiment: false)
-        shuffled_people = people.shuffle(random: random)
-
-        if timezone_experiment
-          shuffled_people.find(&method(:valid_person_with_timezone?))
-        else
-          shuffled_people.find(&method(:valid_person?))
-        end
-      end
-
-      private
-
-      # @param [Teammate] person
-      # @return [Boolean]
-      def valid_person?(person)
-        !mr_author?(person) && person.available
-      end
-
-      # @param [Teammate] person
-      # @return [Boolean]
-      def valid_person_with_timezone?(person)
-        valid_person?(person) && HOURS_WHEN_PERSON_CAN_BE_PICKED.cover?(person.local_hour)
-      end
-
-      # @param [Teammate] person
-      # @return [Boolean]
-      def mr_author?(person)
-        person.username == mr_author_username
-      end
-
-      def mr_author_username
-        helper.gitlab_helper&.mr_author || `whoami`
-      end
-
-      def mr_source_branch
-        return `git rev-parse --abbrev-ref HEAD` unless helper.gitlab_helper&.mr_json
-
-        helper.gitlab_helper.mr_json['source_branch']
-      end
-
-      def mr_labels
-        helper.gitlab_helper&.mr_labels || []
-      end
-
-      def new_random(seed)
-        Random.new(Digest::MD5.hexdigest(seed).to_i(16))
-      end
-
-      def spin_role_for_category(team, role, project, category)
-        team.select do |member|
-          member.public_send("#{role}?", project, category, mr_labels) # rubocop:disable GitlabSecurity/PublicSend
-        end
-      end
-
-      def spin_for_category(project, category, timezone_experiment: false)
-        team = project_team(project)
-        reviewers, traintainers, maintainers =
-          %i[reviewer traintainer maintainer].map do |role|
-            spin_role_for_category(team, role, project, category)
-          end
-
-        random = new_random(mr_source_branch)
-
-        weighted_reviewers = Weightage::Reviewers.new(reviewers, traintainers).execute
-        weighted_maintainers = Weightage::Maintainers.new(maintainers).execute
-
-        reviewer = spin_for_person(weighted_reviewers, random: random, timezone_experiment: timezone_experiment)
-        maintainer = spin_for_person(weighted_maintainers, random: random, timezone_experiment: timezone_experiment)
-
-        Spin.new(category, reviewer, maintainer, false, timezone_experiment)
-      end
-    end
-  end
-end
diff --git a/tooling/danger/teammate.rb b/tooling/danger/teammate.rb
deleted file mode 100644
index bcd33bebdc9629945ac73da35a649aa10042ca82..0000000000000000000000000000000000000000
--- a/tooling/danger/teammate.rb
+++ /dev/null
@@ -1,121 +0,0 @@
-# frozen_string_literal: true
-
-module Tooling
-  module Danger
-    class Teammate
-      attr_reader :options, :username, :name, :role, :projects, :available, :hungry, :reduced_capacity, :tz_offset_hours
-
-      # The options data are produced by https://gitlab.com/gitlab-org/gitlab-roulette/-/blob/master/lib/team_member.rb
-      def initialize(options = {})
-        @options = options
-        @username = options['username']
-        @name = options['name']
-        @markdown_name = options['markdown_name']
-        @role = options['role']
-        @projects = options['projects']
-        @available = options['available']
-        @hungry = options['hungry']
-        @reduced_capacity = options['reduced_capacity']
-        @tz_offset_hours = options['tz_offset_hours']
-      end
-
-      def to_h
-        options
-      end
-
-      def ==(other)
-        return false unless other.respond_to?(:username)
-
-        other.username == username
-      end
-
-      def in_project?(name)
-        projects&.has_key?(name)
-      end
-
-      def any_capability?(project, category)
-        capabilities(project).any? { |capability| capability.end_with?(category.to_s) }
-      end
-
-      def reviewer?(project, category, labels)
-        has_capability?(project, category, :reviewer, labels)
-      end
-
-      def traintainer?(project, category, labels)
-        has_capability?(project, category, :trainee_maintainer, labels)
-      end
-
-      def maintainer?(project, category, labels)
-        has_capability?(project, category, :maintainer, labels)
-      end
-
-      def markdown_name(author: nil)
-        "#{@markdown_name} (#{utc_offset_text(author)})"
-      end
-
-      def local_hour
-        (Time.now.utc + tz_offset_hours * 3600).hour
-      end
-
-      protected
-
-      def floored_offset_hours
-        floored_offset = tz_offset_hours.floor(0)
-
-        floored_offset == tz_offset_hours ? floored_offset : tz_offset_hours
-      end
-
-      private
-
-      def utc_offset_text(author = nil)
-        offset_text =
-          if floored_offset_hours >= 0
-            "UTC+#{floored_offset_hours}"
-          else
-            "UTC#{floored_offset_hours}"
-          end
-
-        return offset_text unless author
-
-        "#{offset_text}, #{offset_diff_compared_to_author(author)}"
-      end
-
-      def offset_diff_compared_to_author(author)
-        diff = floored_offset_hours - author.floored_offset_hours
-        return "same timezone as `@#{author.username}`" if diff == 0
-
-        ahead_or_behind = diff < 0 ? 'behind' : 'ahead of'
-        pluralized_hours = pluralize(diff.abs, 'hour', 'hours')
-
-        "#{pluralized_hours} #{ahead_or_behind} `@#{author.username}`"
-      end
-
-      def has_capability?(project, category, kind, labels)
-        case category
-        when :test
-          area = role[/Software Engineer in Test(?:.*?, (\w+))/, 1]
-
-          area && labels.any?("devops::#{area.downcase}") if kind == :reviewer
-        when :engineering_productivity
-          return false unless role[/Engineering Productivity/]
-          return true if kind == :reviewer
-          return true if capabilities(project).include?("#{kind} engineering_productivity")
-
-          capabilities(project).include?("#{kind} backend")
-        else
-          capabilities(project).include?("#{kind} #{category}")
-        end
-      end
-
-      def capabilities(project)
-        Array(projects.fetch(project, []))
-      end
-
-      def pluralize(count, singular, plural)
-        word = count == 1 || count.to_s =~ /^1(\.0+)?$/ ? singular : plural
-
-        "#{count || 0} #{word}"
-      end
-    end
-  end
-end
diff --git a/tooling/danger/title_linting.rb b/tooling/danger/title_linting.rb
deleted file mode 100644
index dcd83df7d93a1d92db656d217063420d70ff395c..0000000000000000000000000000000000000000
--- a/tooling/danger/title_linting.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-# frozen_string_literal: true
-
-module Tooling
-  module Danger
-    module TitleLinting
-      DRAFT_REGEX = /\A*#{Regexp.union(/(?i)(\[WIP\]\s*|WIP:\s*|WIP$)/, /(?i)(\[draft\]|\(draft\)|draft:|draft\s\-\s|draft$)/)}+\s*/i.freeze
-      CHERRY_PICK_REGEX = /cherry[\s-]*pick/i.freeze
-      RUN_ALL_RSPEC_REGEX = /RUN ALL RSPEC/i.freeze
-      RUN_AS_IF_FOSS_REGEX = /RUN AS-IF-FOSS/i.freeze
-
-      module_function
-
-      def sanitize_mr_title(title)
-        remove_draft_flag(title).gsub(/`/, '\\\`')
-      end
-
-      def remove_draft_flag(title)
-        title.gsub(DRAFT_REGEX, '')
-      end
-
-      def has_draft_flag?(title)
-        DRAFT_REGEX.match?(title)
-      end
-
-      def has_cherry_pick_flag?(title)
-        CHERRY_PICK_REGEX.match?(title)
-      end
-
-      def has_run_all_rspec_flag?(title)
-        RUN_ALL_RSPEC_REGEX.match?(title)
-      end
-
-      def has_run_as_if_foss_flag?(title)
-        RUN_AS_IF_FOSS_REGEX.match?(title)
-      end
-    end
-  end
-end
diff --git a/tooling/danger/weightage.rb b/tooling/danger/weightage.rb
deleted file mode 100644
index cf8d17410dca20bfd39a3ffb61e468deff16cf35..0000000000000000000000000000000000000000
--- a/tooling/danger/weightage.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-# frozen_string_literal: true
-
-module Tooling
-  module Danger
-    module Weightage
-      CAPACITY_MULTIPLIER = 2 # change this number to change what it means to be a reduced capacity reviewer 1/this number
-      BASE_REVIEWER_WEIGHT = 1
-    end
-  end
-end
diff --git a/tooling/danger/weightage/maintainers.rb b/tooling/danger/weightage/maintainers.rb
deleted file mode 100644
index 068b24e79138e6ab342fe7ded8fed00e3bd5fd9b..0000000000000000000000000000000000000000
--- a/tooling/danger/weightage/maintainers.rb
+++ /dev/null
@@ -1,33 +0,0 @@
-# frozen_string_literal: true
-
-require_relative '../weightage'
-
-module Tooling
-  module Danger
-    module Weightage
-      class Maintainers
-        def initialize(maintainers)
-          @maintainers = maintainers
-        end
-
-        def execute
-          maintainers.each_with_object([]) do |maintainer, weighted_maintainers|
-            add_weighted_reviewer(weighted_maintainers, maintainer, BASE_REVIEWER_WEIGHT)
-          end
-        end
-
-        private
-
-        attr_reader :maintainers
-
-        def add_weighted_reviewer(reviewers, reviewer, weight)
-          if reviewer.reduced_capacity
-            reviewers.fill(reviewer, reviewers.size, weight)
-          else
-            reviewers.fill(reviewer, reviewers.size, weight * CAPACITY_MULTIPLIER)
-          end
-        end
-      end
-    end
-  end
-end
diff --git a/tooling/danger/weightage/reviewers.rb b/tooling/danger/weightage/reviewers.rb
deleted file mode 100644
index e74fce37187215e59aaef5be92024d5aa976d369..0000000000000000000000000000000000000000
--- a/tooling/danger/weightage/reviewers.rb
+++ /dev/null
@@ -1,65 +0,0 @@
-# frozen_string_literal: true
-
-require_relative '../weightage'
-
-module Tooling
-  module Danger
-    module Weightage
-      # Weights after (current multiplier of 2)
-      #
-      # +------------------------------+--------------------------------+
-      # |        reviewer type         | weight(times in reviewer pool) |
-      # +------------------------------+--------------------------------+
-      # | reduced capacity reviewer    |                              1 |
-      # | reviewer                     |                              2 |
-      # | hungry reviewer              |                              4 |
-      # | reduced capacity traintainer |                              3 |
-      # | traintainer                  |                              6 |
-      # | hungry traintainer           |                              8 |
-      # +------------------------------+--------------------------------+
-      #
-      class Reviewers
-        DEFAULT_REVIEWER_WEIGHT = CAPACITY_MULTIPLIER * BASE_REVIEWER_WEIGHT
-        TRAINTAINER_WEIGHT = 3
-
-        def initialize(reviewers, traintainers)
-          @reviewers = reviewers
-          @traintainers = traintainers
-        end
-
-        def execute
-          # TODO: take CODEOWNERS into account?
-          # https://gitlab.com/gitlab-org/gitlab/issues/26723
-
-          weighted_reviewers + weighted_traintainers
-        end
-
-        private
-
-        attr_reader :reviewers, :traintainers
-
-        def weighted_reviewers
-          reviewers.each_with_object([]) do |reviewer, total_reviewers|
-            add_weighted_reviewer(total_reviewers, reviewer, BASE_REVIEWER_WEIGHT)
-          end
-        end
-
-        def weighted_traintainers
-          traintainers.each_with_object([]) do |reviewer, total_traintainers|
-            add_weighted_reviewer(total_traintainers, reviewer, TRAINTAINER_WEIGHT)
-          end
-        end
-
-        def add_weighted_reviewer(reviewers, reviewer, weight)
-          if reviewer.reduced_capacity
-            reviewers.fill(reviewer, reviewers.size, weight)
-          elsif reviewer.hungry
-            reviewers.fill(reviewer, reviewers.size, weight * CAPACITY_MULTIPLIER + DEFAULT_REVIEWER_WEIGHT)
-          else
-            reviewers.fill(reviewer, reviewers.size, weight * CAPACITY_MULTIPLIER)
-          end
-        end
-      end
-    end
-  end
-end
diff --git a/tooling/gitlab_danger.rb b/tooling/gitlab_danger.rb
deleted file mode 100644
index d20d3499641778eff002a2951262f9db90e16098..0000000000000000000000000000000000000000
--- a/tooling/gitlab_danger.rb
+++ /dev/null
@@ -1,59 +0,0 @@
-# frozen_string_literal: true
-
-# rubocop:todo Gitlab/NamespacedClass
-class GitlabDanger
-  LOCAL_RULES ||= %w[
-    changes_size
-    commit_messages
-    database
-    documentation
-    duplicate_yarn_dependencies
-    eslint
-    karma
-    pajamas
-    pipeline
-    prettier
-    product_intelligence
-    utility_css
-  ].freeze
-
-  CI_ONLY_RULES ||= %w[
-    ce_ee_vue_templates
-    changelog
-    ci_templates
-    metadata
-    feature_flag
-    roulette
-    sidekiq_queues
-    specialization_labels
-    specs
-  ].freeze
-
-  MESSAGE_PREFIX = '==>'.freeze
-
-  attr_reader :gitlab_danger_helper
-
-  def initialize(gitlab_danger_helper)
-    @gitlab_danger_helper = gitlab_danger_helper
-  end
-
-  def self.local_warning_message
-    "#{MESSAGE_PREFIX} Only the following Danger rules can be run locally: #{LOCAL_RULES.join(', ')}"
-  end
-
-  def self.success_message
-    "#{MESSAGE_PREFIX} No Danger rule violations!"
-  end
-
-  def rule_names
-    ci? ? LOCAL_RULES | CI_ONLY_RULES : LOCAL_RULES
-  end
-
-  def html_link(str)
-    self.ci? ? gitlab_danger_helper.html_link(str) : str
-  end
-
-  def ci?
-    !gitlab_danger_helper.nil?
-  end
-end