diff --git a/bin/feature-flag b/bin/feature-flag
deleted file mode 100755
index c6019722e7fb5dbcb155bc6a26a1faeb5e8966f0..0000000000000000000000000000000000000000
--- a/bin/feature-flag
+++ /dev/null
@@ -1,291 +0,0 @@
-#!/usr/bin/env ruby
-#
-# Generate a feature flag entry file in the correct location.
-#
-# Automatically stages the file and amends the previous commit if the `--amend`
-# argument is used.
-
-require 'optparse'
-require 'yaml'
-require 'fileutils'
-require 'cgi'
-
-require_relative '../lib/feature/shared' unless defined?(Feature::Shared)
-
-Options = Struct.new(
-  :name,
-  :type,
-  :group,
-  :ee,
-  :amend,
-  :dry_run,
-  :force,
-  :introduced_by_url,
-  :rollout_issue_url
-)
-
-module FeatureFlagHelpers
-  Abort = Class.new(StandardError)
-  Done = Class.new(StandardError)
-
-  def capture_stdout(cmd)
-    output = IO.popen(cmd, &:read)
-    fail_with "command failed: #{cmd.join(' ')}" unless $?.success?
-    output
-  end
-
-  def fail_with(message)
-    raise Abort, "\e[31merror\e[0m #{message}"
-  end
-end
-
-class FeatureFlagOptionParser
-  extend FeatureFlagHelpers
-  extend ::Feature::Shared
-
-  class << self
-    def parse(argv)
-      options = Options.new
-
-      parser = OptionParser.new do |opts|
-        opts.banner = "Usage: #{__FILE__} [options] <feature-flag>\n\n"
-
-        # Note: We do not provide a shorthand for this in order to match the `git
-        # commit` interface
-        opts.on('--amend', 'Amend the previous commit') do |value|
-          options.amend = value
-        end
-
-        opts.on('-f', '--force', 'Overwrite an existing entry') do |value|
-          options.force = value
-        end
-
-        opts.on('-m', '--introduced-by-url [string]', String, 'URL to Merge Request introducing Feature Flag') do |value|
-          options.introduced_by_url = value
-        end
-
-        opts.on('-i', '--rollout-issue-url [string]', String, 'URL to Issue rolling out Feature Flag') do |value|
-          options.rollout_issue_url = value
-        end
-
-        opts.on('-n', '--dry-run', "Don't actually write anything, just print") do |value|
-          options.dry_run = value
-        end
-
-        opts.on('-g', '--group [string]', String, "The group introducing a feature flag, like: `group::apm`") do |value|
-          options.group = value if value.start_with?('group::')
-        end
-
-        opts.on('-t', '--type [string]', String, "The category of the feature flag, valid options are: #{TYPES.keys.map(&:to_s).join(', ')}") do |value|
-          options.type = value.to_sym if TYPES[value.to_sym]
-        end
-
-        opts.on('-e', '--ee', 'Generate a feature flag entry for GitLab EE') do |value|
-          options.ee = value
-        end
-
-        opts.on('-h', '--help', 'Print help message') do
-          $stdout.puts opts
-          raise Done.new
-        end
-      end
-
-      parser.parse!(argv)
-
-      unless argv.one?
-        $stdout.puts parser.help
-        $stdout.puts
-        raise Abort, 'Feature flag name is required'
-      end
-
-      # Name is a first name
-      options.name = argv.first
-
-      options
-    end
-
-    def read_group
-      $stdout.puts ">> Please specify the group introducing feature flag, like `group::apm`:"
-
-      loop do
-        $stdout.print "\n?> "
-        group = $stdin.gets.strip
-        group = nil if group.empty?
-        return group if group.nil? || group.start_with?('group::')
-
-        $stderr.puts "Group needs to include `group::`"
-      end
-    end
-
-    def read_type
-      $stdout.puts ">> Please specify the type of your feature flag:"
-      $stdout.puts
-      TYPES.each do |type, data|
-        $stdout.puts "#{type.to_s.rjust(15)}#{' '*6}#{data[:description]}"
-      end
-
-      loop do
-        $stdout.print "\n?> "
-
-        type = $stdin.gets.strip.to_sym
-        return type if TYPES[type]
-
-        $stderr.puts "Invalid type specified '#{type}'"
-      end
-    end
-
-    def read_issue_url(options)
-      return unless TYPES.dig(options.type, :rollout_issue)
-
-      url = "https://gitlab.com/gitlab-org/gitlab/-/issues/new"
-      title = "[Feature flag] Rollout of `#{options.name}`"
-      description = File.read('.gitlab/issue_templates/Feature Flag Roll Out.md')
-      description.sub!(':feature_name', options.name)
-
-      issue_new_url = url + "?" +
-        "issue[title]=" + CGI.escape(title) + "&"
-        # TODO: We should be able to pick `issueable_template`
-        # + "issue[description]=" + CGI.escape(description)
-
-      $stdout.puts ">> Open this URL and fill the rest of details:"
-      $stdout.puts issue_new_url
-      $stdout.puts
-
-      $stdout.puts ">> Paste URL here, or enter to skip:"
-
-      loop do
-        $stdout.print "\n?> "
-        created_url = $stdin.gets.strip
-        created_url = nil if created_url.empty?
-        return created_url if created_url.nil? || created_url.start_with?('https://')
-
-        $stderr.puts "URL needs to start with https://"
-      end
-    end
-  end
-end
-
-class FeatureFlagCreator
-  include FeatureFlagHelpers
-
-  attr_reader :options
-
-  def initialize(options)
-    @options = options
-  end
-
-  def execute
-    assert_feature_branch!
-    assert_name!
-    assert_existing_feature_flag!
-
-    # Read type from $stdin unless is already set
-    options.type ||= FeatureFlagOptionParser.read_type
-    options.group ||= FeatureFlagOptionParser.read_group
-    options.rollout_issue_url ||= FeatureFlagOptionParser.read_issue_url(options)
-
-    $stdout.puts "\e[32mcreate\e[0m #{file_path}"
-    $stdout.puts contents
-
-    unless options.dry_run
-      write
-      amend_commit if options.amend
-    end
-
-    if editor
-      system("#{editor} '#{file_path}'")
-    end
-  end
-
-  private
-
-  def contents
-    YAML.dump(
-      'name'              => options.name,
-      'introduced_by_url' => options.introduced_by_url,
-      'rollout_issue_url' => options.rollout_issue_url,
-      'group'             => options.group.to_s,
-      'type'              => options.type.to_s,
-      'default_enabled'   => false
-    ).strip
-  end
-
-  def write
-    FileUtils.mkdir_p(File.dirname(file_path))
-    File.write(file_path, contents)
-  end
-
-  def editor
-    ENV['EDITOR']
-  end
-
-  def amend_commit
-    fail_with "git add failed" unless system(*%W[git add #{file_path}])
-
-    Kernel.exec(*%w[git commit --amend])
-  end
-
-  def assert_feature_branch!
-    return unless branch_name == 'master'
-
-    fail_with "Create a branch first!"
-  end
-
-  def assert_existing_feature_flag!
-    existing_path = all_feature_flag_names[options.name]
-    return unless existing_path
-    return if options.force
-
-    fail_with "#{existing_path} already exists! Use `--force` to overwrite."
-  end
-
-  def assert_name!
-    return if options.name.match(/\A[a-z0-9_-]+\Z/)
-
-    fail_with "Provide a name for the feature flag that is [a-z0-9_-]"
-  end
-
-  def file_path
-    feature_flags_paths.last
-      .sub('**', options.type.to_s)
-      .sub('*.yml', options.name + '.yml')
-  end
-
-  def all_feature_flag_names
-    @all_feature_flag_names ||=
-      feature_flags_paths.map do |glob_path|
-        Dir.glob(glob_path).map do |path|
-          [File.basename(path, '.yml'), path]
-        end
-      end.flatten(1).to_h
-  end
-
-  def feature_flags_paths
-    paths = []
-    paths << File.join('config', 'feature_flags', '**', '*.yml')
-    paths << File.join('ee', 'config', 'feature_flags', '**', '*.yml') if ee?
-    paths
-  end
-
-  def ee?
-    options.ee
-  end
-
-  def branch_name
-    @branch_name ||= capture_stdout(%w[git symbolic-ref --short HEAD]).strip
-  end
-end
-
-if $0 == __FILE__
-  begin
-    options = FeatureFlagOptionParser.parse(ARGV)
-    FeatureFlagCreator.new(options).execute
-  rescue FeatureFlagHelpers::Abort => ex
-    $stderr.puts ex.message
-    exit 1
-  rescue FeatureFlagHelpers::Done
-    exit
-  end
-end
-
-# vim: ft=ruby
diff --git a/spec/bin/feature_flag_spec.rb b/spec/bin/feature_flag_spec.rb
deleted file mode 100644
index 3a315a136860b90a5be0ab9909fbc2b8af0d7020..0000000000000000000000000000000000000000
--- a/spec/bin/feature_flag_spec.rb
+++ /dev/null
@@ -1,191 +0,0 @@
-# frozen_string_literal: true
-
-require 'spec_helper'
-
-load File.expand_path('../../bin/feature-flag', __dir__)
-
-RSpec.describe 'bin/feature-flag' do
-  using RSpec::Parameterized::TableSyntax
-
-  describe FeatureFlagCreator do
-    let(:argv) { %w[feature-flag-name -t development -g group::memory -i https://url] }
-    let(:options) { FeatureFlagOptionParser.parse(argv) }
-    let(:creator) { described_class.new(options) }
-    let(:existing_flag) { File.join('config', 'feature_flags', 'development', 'existing-feature-flag.yml') }
-
-    before do
-      # create a dummy feature flag
-      FileUtils.mkdir_p(File.dirname(existing_flag))
-      File.write(existing_flag, '{}')
-
-      # ignore writes
-      allow(File).to receive(:write).and_return(true)
-
-      # ignore stdin
-      allow($stdin).to receive(:gets).and_raise('EOF')
-
-      # ignore Git commands
-      allow(creator).to receive(:branch_name) { 'feature-branch' }
-    end
-
-    after do
-      FileUtils.rm_f(existing_flag)
-    end
-
-    subject { creator.execute }
-
-    it 'properly creates a feature flag' do
-      expect(File).to receive(:write).with(
-        File.join('config', 'feature_flags', 'development', 'feature-flag-name.yml'),
-        anything)
-
-      expect do
-        subject
-      end.to output(/name: feature-flag-name/).to_stdout
-    end
-
-    context 'when running on master' do
-      it 'requires feature branch' do
-        expect(creator).to receive(:branch_name) { 'master' }
-
-        expect { subject }.to raise_error(FeatureFlagHelpers::Abort, /Create a branch first/)
-      end
-    end
-
-    context 'validates feature flag name' do
-      where(:argv, :ex) do
-        %w[.invalid.feature.flag] | /Provide a name for the feature flag that is/
-        %w[existing-feature-flag] | /already exists!/
-      end
-
-      with_them do
-        it do
-          expect { subject }.to raise_error(ex)
-        end
-      end
-    end
-  end
-
-  describe FeatureFlagOptionParser do
-    describe '.parse' do
-      where(:param, :argv, :result) do
-        :name              | %w[foo]                                 | 'foo'
-        :amend             | %w[foo --amend]                         | true
-        :force             | %w[foo -f]                              | true
-        :force             | %w[foo --force]                         | true
-        :ee                | %w[foo -e]                              | true
-        :ee                | %w[foo --ee]                            | true
-        :introduced_by_url | %w[foo -m https://url]                  | 'https://url'
-        :introduced_by_url | %w[foo --introduced-by-url https://url] | 'https://url'
-        :rollout_issue_url | %w[foo -i https://url]                  | 'https://url'
-        :rollout_issue_url | %w[foo --rollout-issue-url https://url] | 'https://url'
-        :dry_run           | %w[foo -n]                              | true
-        :dry_run           | %w[foo --dry-run]                       | true
-        :type              | %w[foo -t development]                  | :development
-        :type              | %w[foo --type development]              | :development
-        :type              | %w[foo -t invalid]                      | nil
-        :type              | %w[foo --type invalid]                  | nil
-        :group             | %w[foo -g group::memory]                | 'group::memory'
-        :group             | %w[foo --group group::memory]           | 'group::memory'
-        :group             | %w[foo -g invalid]                      | nil
-        :group             | %w[foo --group invalid]                 | nil
-      end
-
-      with_them do
-        it do
-          options = described_class.parse(Array(argv))
-
-          expect(options.public_send(param)).to eq(result)
-        end
-      end
-
-      it 'missing feature flag name' do
-        expect do
-          expect { described_class.parse(%w[--amend]) }.to output(/Feature flag name is required/).to_stdout
-        end.to raise_error(FeatureFlagHelpers::Abort)
-      end
-
-      it 'parses -h' do
-        expect do
-          expect { described_class.parse(%w[foo -h]) }.to output(/Usage:/).to_stdout
-        end.to raise_error(FeatureFlagHelpers::Done)
-      end
-    end
-
-    describe '.read_type' do
-      let(:type) { 'development' }
-
-      it 'reads type from $stdin' do
-        expect($stdin).to receive(:gets).and_return(type)
-        expect do
-          expect(described_class.read_type).to eq(:development)
-        end.to output(/specify the type/).to_stdout
-      end
-
-      context 'invalid type given' do
-        let(:type) { 'invalid' }
-
-        it 'shows error message and retries' do
-          expect($stdin).to receive(:gets).and_return(type)
-          expect($stdin).to receive(:gets).and_raise('EOF')
-
-          expect do
-            expect { described_class.read_type }.to raise_error(/EOF/)
-          end.to output(/specify the type/).to_stdout
-            .and output(/Invalid type specified/).to_stderr
-        end
-      end
-    end
-
-    describe '.read_group' do
-      let(:group) { 'group::memory' }
-
-      it 'reads type from $stdin' do
-        expect($stdin).to receive(:gets).and_return(group)
-        expect do
-          expect(described_class.read_group).to eq('group::memory')
-        end.to output(/specify the group/).to_stdout
-      end
-
-      context 'invalid group given' do
-        let(:type) { 'invalid' }
-
-        it 'shows error message and retries' do
-          expect($stdin).to receive(:gets).and_return(type)
-          expect($stdin).to receive(:gets).and_raise('EOF')
-
-          expect do
-            expect { described_class.read_group }.to raise_error(/EOF/)
-          end.to output(/specify the group/).to_stdout
-            .and output(/Group needs to include/).to_stderr
-        end
-      end
-    end
-
-    describe '.rollout_issue_url' do
-      let(:options) { OpenStruct.new(name: 'foo', type: :development) }
-      let(:url) { 'https://issue' }
-
-      it 'reads type from $stdin' do
-        expect($stdin).to receive(:gets).and_return(url)
-        expect do
-          expect(described_class.read_issue_url(options)).to eq('https://issue')
-        end.to output(/Paste URL here/).to_stdout
-      end
-
-      context 'invalid URL given' do
-        let(:type) { 'invalid' }
-
-        it 'shows error message and retries' do
-          expect($stdin).to receive(:gets).and_return(type)
-          expect($stdin).to receive(:gets).and_raise('EOF')
-
-          expect do
-            expect { described_class.read_issue_url(options) }.to raise_error(/EOF/)
-          end.to output(/Paste URL here/).to_stdout
-            .and output(/URL needs to start/).to_stderr
-        end
-      end
-    end
-  end
-end