docquet 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +20 -0
  3. data/LICENSE +21 -0
  4. data/README.md +205 -0
  5. data/config/cops/bundler.yml +19 -0
  6. data/config/cops/capybara.yml +1 -0
  7. data/config/cops/capybara_rspec.yml +1 -0
  8. data/config/cops/gemspec.yml +4 -0
  9. data/config/cops/i18n_gettext.yml +2 -0
  10. data/config/cops/i18n_railsi18n.yml +1 -0
  11. data/config/cops/layout.yml +79 -0
  12. data/config/cops/lint.yml +7 -0
  13. data/config/cops/metrics.yml +14 -0
  14. data/config/cops/migration.yml +2 -0
  15. data/config/cops/naming.yml +4 -0
  16. data/config/cops/performance.yml +7 -0
  17. data/config/cops/rake.yml +5 -0
  18. data/config/cops/rspec.yml +41 -0
  19. data/config/cops/security.yml +1 -0
  20. data/config/cops/sequel.yml +2 -0
  21. data/config/cops/style.yml +153 -0
  22. data/config/cops/thread_safety.yml +5 -0
  23. data/config/defaults/bundler.yml +97 -0
  24. data/config/defaults/capybara.yml +103 -0
  25. data/config/defaults/capybara_rspec.yml +24 -0
  26. data/config/defaults/gemspec.yml +121 -0
  27. data/config/defaults/i18n_gettext.yml +20 -0
  28. data/config/defaults/i18n_railsi18n.yml +9 -0
  29. data/config/defaults/layout.yml +1121 -0
  30. data/config/defaults/lint.yml +1315 -0
  31. data/config/defaults/metrics.yml +119 -0
  32. data/config/defaults/migration.yml +9 -0
  33. data/config/defaults/naming.yml +332 -0
  34. data/config/defaults/performance.yml +445 -0
  35. data/config/defaults/rake.yml +34 -0
  36. data/config/defaults/rspec.yml +1081 -0
  37. data/config/defaults/security.yml +67 -0
  38. data/config/defaults/sequel.yml +59 -0
  39. data/config/defaults/style.yml +2985 -0
  40. data/config/defaults/thread_safety.yml +45 -0
  41. data/exe/docquet +7 -0
  42. data/lib/docquet/cli/base.rb +21 -0
  43. data/lib/docquet/cli/install_config.rb +75 -0
  44. data/lib/docquet/cli/regenerate_todo.rb +46 -0
  45. data/lib/docquet/cli.rb +6 -0
  46. data/lib/docquet/commands.rb +12 -0
  47. data/lib/docquet/config_processor.rb +52 -0
  48. data/lib/docquet/generators/rubocop_yml_generator.rb +78 -0
  49. data/lib/docquet/inflector.rb +22 -0
  50. data/lib/docquet/plugin_detector.rb +29 -0
  51. data/lib/docquet/rake_task.rb +126 -0
  52. data/lib/docquet/version.rb +6 -0
  53. data/lib/docquet.rb +17 -0
  54. data/templates/rubocop.yml.erb +35 -0
  55. metadata +172 -0
@@ -0,0 +1,45 @@
1
+ # Department 'ThreadSafety' (6):
2
+ # https://docs.rubocop.org/rubocop-thread_safety/cops_thread_safety.html#threadsafetyclassandmoduleattributes
3
+ ThreadSafety/ClassAndModuleAttributes:
4
+ Description: Avoid mutating class and module attributes.
5
+ Enabled: true
6
+ ActiveSupportClassAttributeAllowed: false
7
+
8
+ # https://docs.rubocop.org/rubocop-thread_safety/cops_thread_safety.html#threadsafetyclassinstancevariable
9
+ ThreadSafety/ClassInstanceVariable:
10
+ Description: Avoid class instance variables.
11
+ Enabled: true
12
+
13
+ # https://docs.rubocop.org/rubocop-thread_safety/cops_thread_safety.html#threadsafetydirchdir
14
+ ThreadSafety/DirChdir:
15
+ Description: Avoid using `Dir.chdir` due to its process-wide effect.
16
+ Enabled: true
17
+ AllowCallWithBlock: false
18
+
19
+ # Supports --autocorrect
20
+ # https://docs.rubocop.org/rubocop-thread_safety/cops_thread_safety.html#threadsafetymutableclassinstancevariable
21
+ ThreadSafety/MutableClassInstanceVariable:
22
+ Description: Do not assign mutable objects to class instance variables.
23
+ Enabled: true
24
+ EnforcedStyle: literals
25
+ SafeAutoCorrect: false
26
+ SupportedStyles:
27
+ - literals
28
+ - strict
29
+
30
+ # https://docs.rubocop.org/rubocop-thread_safety/cops_thread_safety.html#threadsafetynewthread
31
+ ThreadSafety/NewThread:
32
+ Description: Avoid starting new threads. Let a framework like Sidekiq handle the threads.
33
+ Enabled: true
34
+
35
+ # https://docs.rubocop.org/rubocop-thread_safety/cops_thread_safety.html#threadsafetyrackmiddlewareinstancevariable
36
+ ThreadSafety/RackMiddlewareInstanceVariable:
37
+ Description: Avoid instance variables in Rack middleware.
38
+ Enabled: true
39
+ Include:
40
+ - app/middleware/**/*.rb
41
+ - lib/middleware/**/*.rb
42
+ - app/middlewares/**/*.rb
43
+ - lib/middlewares/**/*.rb
44
+ AllowedIdentifiers: []
45
+
data/exe/docquet ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../lib/docquet"
5
+ require "dry/cli"
6
+
7
+ Dry::CLI.new(Docquet::Commands).call
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/cli"
4
+
5
+ module Docquet
6
+ module CLI
7
+ class Base < Dry::CLI::Command
8
+ private def rubocop_yml_exists?
9
+ File.exist?(".rubocop.yml")
10
+ end
11
+
12
+ private def rubocop_command
13
+ bundle_exec_available? ? "bundle exec rubocop" : "rubocop"
14
+ end
15
+
16
+ private def bundle_exec_available?
17
+ system("which bundle > /dev/null 2>&1")
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Docquet
4
+ module CLI
5
+ class InstallConfig < Base
6
+ desc "Install RuboCop configuration and generate TODO file"
7
+
8
+ option :force, type: :boolean, default: false, desc: "Overwrite existing files"
9
+
10
+ def call(force: false, **)
11
+ check_existing_files(force)
12
+
13
+ # Processing order is important for correct TODO file generation:
14
+ # 1. Create empty .rubocop_todo.yml first to prevent reference errors
15
+ # 2. Generate .rubocop.yml with gem configuration
16
+ # 3. Generate actual .rubocop_todo.yml with gem rules applied
17
+ # This ensures TODO file reflects violations against gem configuration
18
+
19
+ create_empty_todo_file
20
+
21
+ generator = Generators::RuboCopYMLGenerator.new
22
+ generator.generate
23
+ puts "✓ Generated .rubocop.yml"
24
+
25
+ generate_todo_file
26
+
27
+ show_completion_message
28
+ end
29
+
30
+ private def check_existing_files(force)
31
+ existing_files = %w[.rubocop.yml .rubocop_todo.yml].select {|file| File.exist?(file) }
32
+
33
+ return if existing_files.none? || force
34
+
35
+ puts "Error: Files already exist: #{existing_files.join(", ")}. Use --force to overwrite."
36
+ exit 1
37
+ end
38
+
39
+ private def create_empty_todo_file
40
+ File.write(".rubocop_todo.yml", <<~TODO)
41
+ # This file contains configuration to temporarily disable cops
42
+ # Configuration will be generated by running: rubocop --regenerate-todo
43
+ TODO
44
+ end
45
+
46
+ private def generate_todo_file
47
+ puts "Generating .rubocop_todo.yml..."
48
+
49
+ command = build_todo_command
50
+ if system(command)
51
+ puts "✓ Generated .rubocop_todo.yml"
52
+ else
53
+ puts "Error: Failed to generate .rubocop_todo.yml"
54
+ exit 1
55
+ end
56
+ end
57
+
58
+ private def build_todo_command
59
+ "#{rubocop_command} --regenerate-todo --no-exclude-limit --no-offense-counts --no-auto-gen-timestamp"
60
+ end
61
+
62
+ private def show_completion_message
63
+ puts <<~MESSAGE
64
+
65
+ ✓ RuboCop setup complete!
66
+
67
+ Next steps:
68
+ 1. Review .rubocop_todo.yml and gradually fix violations
69
+ 2. Use 'docquet regenerate-todo' for future updates
70
+ 3. Run 'bundle exec rubocop' to check your code
71
+ MESSAGE
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "digest"
4
+
5
+ module Docquet
6
+ module CLI
7
+ class RegenerateTodo < Base
8
+ desc "Regenerate .rubocop_todo.yml"
9
+
10
+ def call(**)
11
+ unless rubocop_yml_exists?
12
+ puts "Error: .rubocop.yml not found. Run 'docquet install-config' first."
13
+ exit 1
14
+ end
15
+
16
+ before_hash = calculate_file_hash(".rubocop_todo.yml")
17
+ command = build_command
18
+
19
+ puts "Running: #{command}"
20
+ if system(command)
21
+ after_hash = calculate_file_hash(".rubocop_todo.yml")
22
+ changed = before_hash != after_hash
23
+
24
+ puts <<~MESSAGE
25
+ ✓ Regenerated .rubocop_todo.yml
26
+ #{changed ? "📝 TODO file was updated with changes" : "✅ TODO file unchanged (no new violations)"}
27
+ Review the updated TODO file and continue fixing violations.
28
+ MESSAGE
29
+ else
30
+ puts "Error: Failed to regenerate .rubocop_todo.yml"
31
+ exit 1
32
+ end
33
+ end
34
+
35
+ private def build_command
36
+ "#{rubocop_command} --regenerate-todo --no-exclude-limit --no-offense-counts --no-auto-gen-timestamp"
37
+ end
38
+
39
+ private def calculate_file_hash(file_path)
40
+ return nil unless File.exist?(file_path)
41
+
42
+ Digest::SHA256.hexdigest(File.read(file_path))
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Docquet
4
+ module CLI
5
+ end
6
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/cli"
4
+
5
+ module Docquet
6
+ module Commands
7
+ extend Dry::CLI::Registry
8
+
9
+ register "install-config", CLI::InstallConfig
10
+ register "regenerate-todo", CLI::RegenerateTodo
11
+ end
12
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+
5
+ module Docquet
6
+ class ConfigProcessor
7
+ def initialize(plugin_names)
8
+ @plugin_names = plugin_names
9
+ end
10
+
11
+ def process(content, department, gem_name, base)
12
+ content = add_department_header(content, department)
13
+ content = enable_all_cops(content)
14
+ content = remove_deprecated_config(content)
15
+ content = add_documentation_links(content, department, gem_name, base)
16
+ content = normalize_paths(content)
17
+ remove_trailing_whitespace(content)
18
+ end
19
+
20
+ private def add_department_header(content, department)
21
+ cop_count = content.scan(%r{^#{Regexp.escape(department)}/}).length
22
+ header = "# Department '#{department}' (#{cop_count}):\n"
23
+ header + content
24
+ end
25
+
26
+ private def enable_all_cops(content)
27
+ content.gsub(/(?<=^ )Enabled: (false|pending)$/) { "Enabled: true # was #{$1}" }
28
+ end
29
+
30
+ private def remove_deprecated_config(content)
31
+ content.gsub(/^\s+AllowOnlyRestArgument:.*\n/, "")
32
+ end
33
+
34
+ private def add_documentation_links(content, department, gem_name, base)
35
+ content.gsub(%r{(?=^#{department}/(.+):$)}) do
36
+ cop_name = $1
37
+ path = "/#{gem_name}/cops_#{base}.html"
38
+ fragment = "#{department}#{cop_name}".downcase.delete("/_")
39
+
40
+ "# #{URI::HTTPS.build(scheme: "https", host: "docs.rubocop.org", path:, fragment:)}\n"
41
+ end
42
+ end
43
+
44
+ private def normalize_paths(content)
45
+ content.gsub("#{Dir.pwd}/", "")
46
+ end
47
+
48
+ private def remove_trailing_whitespace(content)
49
+ content.gsub(/ +$/, "")
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "erb"
4
+
5
+ module Docquet
6
+ module Generators
7
+ class RuboCopYMLGenerator
8
+ def initialize
9
+ @inflector = Inflector.instance
10
+ @detected_plugin_names = PluginDetector.detect_plugin_names
11
+ @filtered_configs = filtered_config_files
12
+ end
13
+
14
+ def generate
15
+ content = ERB.new(File.read(template_path("rubocop.yml.erb")), trim_mode: "-").result(binding)
16
+ File.write(".rubocop.yml", content)
17
+ end
18
+
19
+ private def detected_ruby_version
20
+ # Detect from .ruby-version or Gemfile
21
+ if File.exist?(".ruby-version")
22
+ File.read(".ruby-version").strip
23
+ else
24
+ RUBY_VERSION[/\A\d+\.\d+/]
25
+ end
26
+ end
27
+
28
+ private def template_path(filename)
29
+ File.join(File.dirname(__dir__, 3), "templates", filename)
30
+ end
31
+
32
+ private def detect_available_config_files
33
+ gem_config_dir = File.join(File.dirname(__dir__, 3), "config", "cops")
34
+ Dir.glob("#{gem_config_dir}/*.yml").map {|path| File.basename(path, ".yml") }
35
+ end
36
+
37
+ private def filtered_config_files
38
+ available_configs = detect_available_config_files
39
+ core_departments = %w[style layout lint metrics security gemspec bundler naming]
40
+
41
+ available_configs.select do |config|
42
+ # Extract department name from config file name
43
+ department = extract_department_from_config(config)
44
+
45
+ if core_departments.include?(@inflector.underscore(department))
46
+ true # Core departments are always included
47
+ else
48
+ # Check if corresponding plugin is detected
49
+ plugin_name = @inflector.underscore(department)
50
+ @detected_plugin_names.include?(plugin_name)
51
+ end
52
+ end
53
+ end
54
+
55
+ private def extract_department_from_config(config)
56
+ # Extract department name from the corresponding defaults file
57
+ cops_file = File.join(File.dirname(__dir__, 3), "config", "cops", "#{config}.yml")
58
+
59
+ if File.exist?(cops_file)
60
+ cops_content = File.read(cops_file)
61
+ if cops_content =~ %r{inherit_from:\s*\.\./defaults/(.+)\.yml}
62
+ defaults_file = File.join(File.dirname(__dir__, 3), "config", "defaults", "#{$1}.yml")
63
+
64
+ if File.exist?(defaults_file)
65
+ defaults_content = File.read(defaults_file)
66
+ if defaults_content =~ /^# Department '(.+)'/
67
+ return $1
68
+ end
69
+ end
70
+ end
71
+ end
72
+
73
+ # Fallback: use the simple split method
74
+ config.split("_").first
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/inflector"
4
+
5
+ module Docquet
6
+ # Provides a centralized inflector with consistent acronym rules.
7
+ #
8
+ # This module ensures all classes use the same inflection rules,
9
+ # preventing inconsistencies in department name transformations.
10
+ module Inflector
11
+ # Returns a configured Dry::Inflector instance with custom acronyms.
12
+ #
13
+ # @return [Dry::Inflector] configured inflector instance
14
+ def self.instance
15
+ @instance ||= Dry::Inflector.new do |inflections|
16
+ inflections.acronym("RSpec")
17
+ inflections.acronym("GetText")
18
+ inflections.acronym("RailsI18n")
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Docquet
4
+ # Detects available RuboCop plugins and provides consistent naming.
5
+ #
6
+ # Terminology:
7
+ # - Plugin Gem Name: Full gem name (e.g., "rubocop-performance", "rubocop-rspec")
8
+ # - Plugin Name: Short name without prefix (e.g., "performance", "rspec")
9
+ #
10
+ # Both forms are used in different contexts:
11
+ # - Plugin Gem Names: For gem operations, --plugin CLI arguments
12
+ # - Plugin Names: For config file matching, department filtering
13
+ module PluginDetector
14
+ # Returns full gem names of detected RuboCop plugins.
15
+ #
16
+ # @return [Array<String>] plugin gem names (e.g., ["rubocop-performance", "rubocop-rspec"])
17
+ module_function def detect_plugin_gem_names
18
+ plugins = Gem::Specification.select {|spec| /\ARuboCop::.*::Plugin\z/ =~ spec.metadata["default_lint_roller_plugin"] }
19
+ plugins.map(&:name)
20
+ end
21
+
22
+ # Returns short names of detected RuboCop plugins (without "rubocop-" prefix).
23
+ #
24
+ # @return [Array<String>] plugin names (e.g., ["performance", "rspec"])
25
+ module_function def detect_plugin_names
26
+ detect_plugin_gem_names.map {|name| name.delete_prefix("rubocop-") }
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,126 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Only load in docquet gem development environment
4
+ unless File.exist?("docquet.gemspec")
5
+ warn "Warning: docquet/rake_task loaded outside of docquet project - skipping loading"
6
+ return
7
+ end
8
+
9
+ # Standard library and framework requires
10
+ require "fileutils"
11
+ require "open3"
12
+ require "rake/tasklib"
13
+ require "uri"
14
+ require "yaml"
15
+
16
+ # RuboCop core
17
+ require "rubocop"
18
+
19
+ # RuboCop plugins
20
+ require "rubocop-capybara"
21
+ require "rubocop-i18n"
22
+ require "rubocop-performance"
23
+ require "rubocop-rake"
24
+ require "rubocop-rspec"
25
+ require "rubocop-sequel"
26
+ require "rubocop-thread_safety"
27
+
28
+ # Load docquet for zeitwerk autoloading
29
+ require_relative "../docquet"
30
+
31
+ module Docquet
32
+ class RakeTask < Rake::TaskLib
33
+ def initialize
34
+ super
35
+ @inflector = Inflector.instance
36
+
37
+ @plugin_gem_names = PluginDetector.detect_plugin_gem_names
38
+ departments = RuboCop::Cop::Registry.global.map {|c| c.department.to_s }
39
+ departments.sort!
40
+ departments.uniq!
41
+ @departments = departments
42
+
43
+ define_tasks
44
+ end
45
+
46
+ private def define_tasks
47
+ desc "Create defaults directory"
48
+ directory "config/defaults"
49
+
50
+ desc "Generate default configuration files"
51
+ task generate_defaults: "config/defaults"
52
+
53
+ desc "Check cops configuration inheritance"
54
+ task check_cops: :generate_defaults do
55
+ check_cops_configurations
56
+ end
57
+
58
+ desc "Clean existing default configuration files"
59
+ task :clean_defaults do
60
+ FileUtils.rm_rf("config/defaults")
61
+ end
62
+
63
+ desc "Clean and regenerate all configuration files"
64
+ task regenerate: %i[clean_defaults generate_defaults check_cops]
65
+
66
+ # Department-specific file generation tasks
67
+ @departments.each do |department|
68
+ base = @inflector.underscore(department).tr("/", "_")
69
+ target_file = "config/defaults/#{base}.yml"
70
+
71
+ desc "Generate configuration for #{department}"
72
+ file target_file => "config/defaults" do |t|
73
+ generate_default_config(department, t.name)
74
+ end
75
+
76
+ task generate_defaults: target_file
77
+ end
78
+ end
79
+
80
+ private def generate_default_config(department, target_file)
81
+ puts "Generating #{department} configuration..."
82
+
83
+ base = @inflector.underscore(department).tr("/", "_")
84
+ plugin_name = @inflector.underscore(department.sub(%r{/.*}, ""))
85
+ gem_name = "rubocop-#{plugin_name}"
86
+ gem_name = "rubocop" unless @plugin_gem_names.include?(gem_name)
87
+
88
+ options = %W[--show-cops=#{department}/* --force-default-config --display-cop-names --extra-details --display-style-guide]
89
+
90
+ cmd = ["bin/rubocop", *options, *@plugin_gem_names.sort.flat_map {|name| %W[--plugin #{name}] }]
91
+
92
+ puts "Running: #{cmd.join(" ")}"
93
+ content, status = Open3.capture2e(*cmd)
94
+
95
+ if status.success?
96
+ processor = ConfigProcessor.new(@plugin_gem_names)
97
+ processed_content = processor.process(content, department, gem_name, base)
98
+ File.write(target_file, processed_content)
99
+ puts "✓ Generated #{target_file}"
100
+ else
101
+ puts "✗ Failed to generate #{department} configuration"
102
+ exit 1
103
+ end
104
+ end
105
+
106
+ private def check_cops_configurations
107
+ puts "Checking cops configurations..."
108
+
109
+ Dir["config/defaults/*.yml"].each do |default_file|
110
+ base_name = File.basename(default_file)
111
+ cops_file = "config/cops/#{base_name}"
112
+
113
+ next unless File.exist?(cops_file)
114
+
115
+ puts "Checking #{cops_file}..."
116
+
117
+ cops_content = File.read(cops_file)
118
+ if cops_content.include?("inherit_from: ../defaults/#{base_name}")
119
+ puts " ✓ Inheritance looks good"
120
+ else
121
+ puts " Warning: #{cops_file} may need inherit_from update"
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Docquet
4
+ VERSION = "1.0.0"
5
+ public_constant :VERSION
6
+ end
data/lib/docquet.rb ADDED
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "zeitwerk"
4
+ require_relative "docquet/version"
5
+
6
+ module Docquet
7
+ class Error < StandardError; end
8
+
9
+ loader = Zeitwerk::Loader.for_gem
10
+ loader.inflector.inflect(
11
+ "cli" => "CLI",
12
+ "rubocop_yml_generator" => "RuboCopYMLGenerator"
13
+ )
14
+ loader.ignore("#{__dir__}/docquet/version.rb")
15
+ loader.ignore("#{__dir__}/docquet/rake_task.rb")
16
+ loader.setup
17
+ end
@@ -0,0 +1,35 @@
1
+ AllCops:
2
+ DisplayCopNames: true
3
+ DisplayStyleGuide: true
4
+ EnabledByDefault: true
5
+ Exclude:
6
+ - bin/**/*
7
+ - vendor/**/*
8
+ ExtraDetails: true
9
+ NewCops: enable
10
+ TargetRubyVersion: <%= detected_ruby_version %>
11
+ UseCache: true
12
+
13
+ inherit_mode:
14
+ merge:
15
+ - Exclude
16
+
17
+ <% unless @detected_plugin_names.empty? -%>
18
+ plugins:
19
+ <% @detected_plugin_names.each do |plugin| -%>
20
+ - rubocop-<%= plugin %>
21
+ <% end -%>
22
+
23
+ <% end -%>
24
+ inherit_gem:
25
+ docquet:
26
+ <% @filtered_configs.each do |config| -%>
27
+ - config/cops/<%= config %>.yml
28
+ <% end -%>
29
+
30
+ inherit_from: .rubocop_todo.yml
31
+
32
+ # Project-specific settings can be added here
33
+ # Example:
34
+ # Style/Documentation:
35
+ # Enabled: false