From def975f5a5714eed14aa592cb4b7c556b39d4283 Mon Sep 17 00:00:00 2001 From: Dave Corson-Knowles Date: Sat, 6 Jun 2026 09:31:37 -0700 Subject: [PATCH 1/3] Add Description and VersionAdded to every cop in config/default.yml RuboCop's documented-cop convention expects each cop entry to carry a Description (used by the docs generator and `rubocop --show-cops`) and a VersionAdded. Backfilled from git history: Packs/* shipped in 0.0.2, PackwerkLite/* in 0.0.12. Co-Authored-By: Claude Opus 4.8 (1M context) --- config/default.yml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/config/default.yml b/config/default.yml index ac2bada..286d8c2 100644 --- a/config/default.yml +++ b/config/default.yml @@ -1,5 +1,7 @@ Packs/ClassMethodsAsPublicApis: + Description: 'Requires public API methods (in `app/public`) to be defined as class methods, which are more easily statically analyzable and typically hold less state.' Enabled: false + VersionAdded: '0.0.2' AcceptableParentClasses: - T::Enum - T::Struct @@ -8,18 +10,28 @@ Packs/ClassMethodsAsPublicApis: AcceptableMixins: [] Packs/RootNamespaceIsPackName: + Description: 'Requires that each file is namespaced under its pack name, so that each pack exposes exactly one root namespace.' Enabled: false + VersionAdded: '0.0.2' Packs/TypedPublicApis: + Description: "Requires that each pack's public API (in `app/public`) is `typed: strict`." Enabled: false + VersionAdded: '0.0.2' Packs/DocumentedPublicApis: + Description: 'Requires that each public API method (in `app/public`) has a documentation comment.' Enabled: false + VersionAdded: '0.0.2' PackwerkLite/Privacy: + Description: 'Detects references to the private implementation of another pack instead of its public API.' # It is recommended to use packwerk Enabled: false + VersionAdded: '0.0.12' PackwerkLite/Dependency: + Description: 'Detects references to a pack that is not listed as an explicit dependency.' # It is recommended to use packwerk Enabled: false + VersionAdded: '0.0.12' From 2784ba7c48ab1f09c6082e56d13f946a3d4ad5e8 Mon Sep 17 00:00:00 2001 From: Dave Corson-Knowles Date: Sat, 6 Jun 2026 11:10:19 -0700 Subject: [PATCH 2/3] Add 100% line+branch coverage gate for cops; modernize support_autocorrect? - Add simplecov (dev dep) and a .simplecov gate enforcing 100% line + branch coverage on the cop surface (lib/rubocop/cop/**). - Remove the deprecated instance-level `support_autocorrect?` overrides from the five cops that subclass the modern RuboCop::Cop::Base. On Base, autocorrect support is read from the class method (default false); the instance override is dead and was a leftover from the legacy RuboCop::Cop::Cop API. Behavior is unchanged (all five already reported false at the class level). - Close the remaining coverage gaps with real specs: top-level/dynamic-mixin cases for ClassMethodsAsPublicApis, non-rb / not-in-a-pack guards for RootNamespaceIsPackName, the PncApi skip and package_todo dependency case for PackwerkLite::Dependency, the package_todo privacy case for PackwerkLite::Privacy, and a DesiredZeitwerkApi unit spec (app/lib/neither). - Mark two genuinely defensive branches with `# :nocov:` and a justification. 81 examples, 0 failures; 100% line + 100% branch on the cop files. Co-Authored-By: Claude Opus 4.8 (1M context) --- .simplecov | 16 ++++++ Gemfile.lock | 8 +++ .../cop/packs/class_methods_as_public_apis.rb | 5 -- .../cop/packs/documented_public_apis.rb | 5 -- .../cop/packs/root_namespace_is_pack_name.rb | 11 ++-- .../cop/packwerk_lite/constant_resolver.rb | 2 + .../cop/packwerk_lite/dependency_checker.rb | 5 -- .../cop/packwerk_lite/privacy_checker.rb | 5 -- rubocop-packs.gemspec | 1 + .../class_methods_as_public_apis_spec.rb | 57 +++++++++++++++++++ .../desired_zeitwerk_api_spec.rb | 36 ++++++++++++ .../packs/root_namespace_is_pack_name_spec.rb | 26 +++++++++ .../packwerk_lite/dependency_checker_spec.rb | 48 ++++++++++++++++ .../cop/packwerk_lite/privacy_checker_spec.rb | 29 ++++++++++ spec/spec_helper.rb | 1 + 15 files changed, 229 insertions(+), 26 deletions(-) create mode 100644 .simplecov create mode 100644 spec/rubocop/cop/packs/root_namespace_is_pack_name/desired_zeitwerk_api_spec.rb diff --git a/.simplecov b/.simplecov new file mode 100644 index 0000000..148fab4 --- /dev/null +++ b/.simplecov @@ -0,0 +1,16 @@ +# typed: false +# frozen_string_literal: true + +SimpleCov.start do + enable_coverage :branch + + # Track only the cops (and their direct helpers); the gate enforces that the + # cop surface is fully exercised. + add_filter { |src| !src.filename.include?('/lib/rubocop/cop/') } + + # Enforce full coverage on complete runs (and when COVERAGE=true), but not + # when running a single spec file locally. + if ENV['COVERAGE'] == 'true' || ARGV.none? { |arg| arg.end_with?('_spec.rb') } + minimum_coverage line: 100, branch: 100 + end +end diff --git a/Gemfile.lock b/Gemfile.lock index 96cab4c..a865eb9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -37,6 +37,7 @@ GEM concurrent-ruby (1.3.6) connection_pool (3.0.2) diff-lcs (1.6.2) + docile (1.4.1) drb (2.2.3) erubi (1.13.1) i18n (1.14.8) @@ -133,6 +134,12 @@ GEM rubocop (>= 1.75.2) ruby-progressbar (1.13.0) securerandom (0.4.1) + simplecov (0.22.0) + docile (~> 1.1) + simplecov-html (~> 0.11) + simplecov_json_formatter (~> 0.1) + simplecov-html (0.13.2) + simplecov_json_formatter (0.1.4) sorbet (0.6.12992) sorbet-static (= 0.6.12992) sorbet-runtime (0.6.12992) @@ -185,6 +192,7 @@ DEPENDENCIES rubocop-extension-generator rubocop-gusto rubocop-packs! + simplecov sorbet tapioca diff --git a/lib/rubocop/cop/packs/class_methods_as_public_apis.rb b/lib/rubocop/cop/packs/class_methods_as_public_apis.rb index 4e95397..19a45e6 100644 --- a/lib/rubocop/cop/packs/class_methods_as_public_apis.rb +++ b/lib/rubocop/cop/packs/class_methods_as_public_apis.rb @@ -31,11 +31,6 @@ module Packs class ClassMethodsAsPublicApis < Base extend T::Sig - sig { returns(T::Boolean) } - def support_autocorrect? - false - end - sig { params(node: T.untyped).void } def on_def(node) # This cop only applies for ruby files in `app/public` diff --git a/lib/rubocop/cop/packs/documented_public_apis.rb b/lib/rubocop/cop/packs/documented_public_apis.rb index 349c3f7..c494c64 100644 --- a/lib/rubocop/cop/packs/documented_public_apis.rb +++ b/lib/rubocop/cop/packs/documented_public_apis.rb @@ -42,11 +42,6 @@ class DocumentedPublicApis < Style::DocumentationMethod # support this for some packs. extend T::Sig - sig { returns(T::Boolean) } - def support_autocorrect? - false - end - sig { params(node: T.untyped).void } def check(node) # This cop only applies for ruby files in `app/public` diff --git a/lib/rubocop/cop/packs/root_namespace_is_pack_name.rb b/lib/rubocop/cop/packs/root_namespace_is_pack_name.rb index b711f25..e02a3d3 100644 --- a/lib/rubocop/cop/packs/root_namespace_is_pack_name.rb +++ b/lib/rubocop/cop/packs/root_namespace_is_pack_name.rb @@ -44,7 +44,9 @@ def on_new_investigation return if package_for_path.nil? namespace_context = desired_zeitwerk_api.for_file(relative_filename, package_for_path) + # :nocov: defensive: for_file always returns a context for the `app/` paths that reach here return if namespace_context.nil? + # :nocov: allowed_global_namespaces = Set.new( [ @@ -75,12 +77,9 @@ def on_new_investigation end end - # In the future, we'd love this to support auto-correct. - # Perhaps by automatically renamespacing the file and changing its location? - sig { returns(T::Boolean) } - def support_autocorrect? - false - end + # In the future, we'd love this to support auto-correct, + # perhaps by automatically renamespacing the file and changing its location. + # That would mean extending `AutoCorrector` and implementing the correction. private diff --git a/lib/rubocop/cop/packwerk_lite/constant_resolver.rb b/lib/rubocop/cop/packwerk_lite/constant_resolver.rb index 57dbb30..6be5d24 100644 --- a/lib/rubocop/cop/packwerk_lite/constant_resolver.rb +++ b/lib/rubocop/cop/packwerk_lite/constant_resolver.rb @@ -60,7 +60,9 @@ def self.resolve(node, processed_source) # # Therefore when we've found possible files, we can sanity check there is only one, # and then assume the found pack defines the constant! + # :nocov: defensive invariant: Zeitwerk guarantees a constant maps to at most one file raise if found_files.count > 1 + # :nocov: expected_pack_contains_constant = found_files.any? diff --git a/lib/rubocop/cop/packwerk_lite/dependency_checker.rb b/lib/rubocop/cop/packwerk_lite/dependency_checker.rb index 9e1f743..b5111e6 100644 --- a/lib/rubocop/cop/packwerk_lite/dependency_checker.rb +++ b/lib/rubocop/cop/packwerk_lite/dependency_checker.rb @@ -39,11 +39,6 @@ module PackwerkLite class Dependency < Base extend T::Sig - sig { returns(T::Boolean) } - def support_autocorrect? - false - end - sig { params(node: RuboCop::AST::ConstNode).void } def on_const(node) return if Private.partial_const_reference?(node) diff --git a/lib/rubocop/cop/packwerk_lite/privacy_checker.rb b/lib/rubocop/cop/packwerk_lite/privacy_checker.rb index 3c0f24f..8662336 100644 --- a/lib/rubocop/cop/packwerk_lite/privacy_checker.rb +++ b/lib/rubocop/cop/packwerk_lite/privacy_checker.rb @@ -40,11 +40,6 @@ module PackwerkLite class Privacy < Base extend T::Sig - sig { returns(T::Boolean) } - def support_autocorrect? - false - end - sig { params(node: RuboCop::AST::ConstNode).void } def on_const(node) # See https://github.com/rubocop/rubocop/blob/master/lib/rubocop/cop/lint/constant_resolution.rb source code as an example diff --git a/rubocop-packs.gemspec b/rubocop-packs.gemspec index e9f28ef..eb1483f 100644 --- a/rubocop-packs.gemspec +++ b/rubocop-packs.gemspec @@ -42,6 +42,7 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'rspec', '~> 3.0' spec.add_development_dependency 'rubocop-extension-generator' spec.add_development_dependency 'rubocop-gusto' + spec.add_development_dependency 'simplecov' spec.add_development_dependency 'sorbet' spec.add_development_dependency 'tapioca' end diff --git a/spec/rubocop/cop/packs/class_methods_as_public_apis_spec.rb b/spec/rubocop/cop/packs/class_methods_as_public_apis_spec.rb index 120f310..d9ec323 100644 --- a/spec/rubocop/cop/packs/class_methods_as_public_apis_spec.rb +++ b/spec/rubocop/cop/packs/class_methods_as_public_apis_spec.rb @@ -21,6 +21,63 @@ write_file('packs/tool/app/public/tool.rb') end + context 'when an instance method is defined at the top level with no class or module' do + let(:source) do + <<~RUBY + def my_instance_method + ^^^^^^^^^^^^^^^^^^^^^^ Public API method must be a class method (e.g. `self.my_instance_method(...)`) + end + RUBY + end + + it { expect_offense source, Pathname.pwd.join('packs/tool/app/public/tool.rb').to_s } + end + + context 'when a class includes a dynamic (non-constant) mixin' do + let(:source) do + <<~RUBY + class Tool + include some_dynamic_mixin + def my_instance_method + ^^^^^^^^^^^^^^^^^^^^^^ Public API method must be a class method (e.g. `self.my_instance_method(...)`) + end + end + RUBY + end + + it { expect_offense source, Pathname.pwd.join('packs/tool/app/public/tool.rb').to_s } + end + + context 'when a class includes a non-acceptable mixin' do + let(:source) do + <<~RUBY + class Tool + include SomeOtherMixin + def my_instance_method + ^^^^^^^^^^^^^^^^^^^^^^ Public API method must be a class method (e.g. `self.my_instance_method(...)`) + end + end + RUBY + end + + it { expect_offense source, Pathname.pwd.join('packs/tool/app/public/tool.rb').to_s } + end + + context 'when a class includes more than one constant in a single include' do + let(:source) do + <<~RUBY + class Tool + include First, Second + def my_instance_method + ^^^^^^^^^^^^^^^^^^^^^^ Public API method must be a class method (e.g. `self.my_instance_method(...)`) + end + end + RUBY + end + + it { expect_offense source, Pathname.pwd.join('packs/tool/app/public/tool.rb').to_s } + end + context 'when class defines an instance method, does not inherit from anything' do let(:source) do <<~RUBY diff --git a/spec/rubocop/cop/packs/root_namespace_is_pack_name/desired_zeitwerk_api_spec.rb b/spec/rubocop/cop/packs/root_namespace_is_pack_name/desired_zeitwerk_api_spec.rb new file mode 100644 index 0000000..40e8d17 --- /dev/null +++ b/spec/rubocop/cop/packs/root_namespace_is_pack_name/desired_zeitwerk_api_spec.rb @@ -0,0 +1,36 @@ +# typed: false +# frozen_string_literal: true + +# Unit specs for the private DesiredZeitwerkApi helper. The cop only invokes it +# for `app/` paths, but the helper also supports `lib/` for API generality, so +# we exercise it directly here (imagine a not-yet-zeitwerk-compliant codebase). +RSpec.describe RuboCop::Cop::Packs::RootNamespaceIsPackName.const_get(:DesiredZeitwerkApi) do # rubocop:disable Sorbet/ConstantsFromStrings + subject(:api) { described_class.new } + + before { write_pack('packs/apples') } + + let(:pack) { Packs.find('packs/apples') } + + context 'when the file lives under app/' do + it 'computes an app-based namespace context' do + context = api.for_file('packs/apples/app/services/tool.rb', pack) + + expect(context.expected_namespace).to eq('Apples') + expect(context.expected_filepath).to eq('packs/apples/app/services/apples/tool.rb') + end + end + + context 'when the file lives under lib/' do + it 'computes a lib-based namespace context' do + context = api.for_file('packs/apples/lib/tool.rb', pack) + + expect(context.expected_filepath).to include('packs/apples/lib/') + end + end + + context 'when the file is in neither app/ nor lib/' do + it 'raises because the autoload folder cannot be determined' do + expect { api.for_file('packs/apples/tool.rb', pack) }.to raise_error(TypeError) + end + end +end diff --git a/spec/rubocop/cop/packs/root_namespace_is_pack_name_spec.rb b/spec/rubocop/cop/packs/root_namespace_is_pack_name_spec.rb index a7dd656..0421fc6 100644 --- a/spec/rubocop/cop/packs/root_namespace_is_pack_name_spec.rb +++ b/spec/rubocop/cop/packs/root_namespace_is_pack_name_spec.rb @@ -25,6 +25,32 @@ end end + context 'when the file is not a ruby file' do + let(:source) do + <<~RUBY + class Tool + end + RUBY + end + + it 'gracefully exits' do + expect_no_offenses source, Pathname.pwd.join(write_file('packs/apples/app/services/tool.rake')).to_s + end + end + + context 'when the file does not belong to a pack' do + let(:source) do + <<~RUBY + class Tool + end + RUBY + end + + it 'gracefully exits' do + expect_no_offenses source, Pathname.pwd.join(write_file('app/services/tool.rb')).to_s + end + end + context 'unnested pack' do context 'globally permitted namespaces not configured' do context 'when file establishes different namespace' do diff --git a/spec/rubocop/cop/packwerk_lite/dependency_checker_spec.rb b/spec/rubocop/cop/packwerk_lite/dependency_checker_spec.rb index b5da0a1..8cc95c2 100644 --- a/spec/rubocop/cop/packwerk_lite/dependency_checker_spec.rb +++ b/spec/rubocop/cop/packwerk_lite/dependency_checker_spec.rb @@ -4,6 +4,54 @@ RSpec.describe RuboCop::Cop::PackwerkLite::Dependency, :config do subject(:cop) { described_class.new(config) } + context 'when the referenced constant is the temporarily-ignored PncApi' do + before do + write_pack('packs/pnc_api', 'enforce_dependencies' => true, 'enforce_privacy' => false) + write_pack('packs/tools', 'enforce_dependencies' => true, 'enforce_privacy' => false) + write_file('packs/pnc_api/app/public/pnc_api.rb') + write_file('packs/tools/app/public/tool.rb') + end + + let(:source) do + <<~SOURCE + class Tools + PncApi + end + SOURCE + end + + it { expect_no_offenses source, File.expand_path('packs/tools/app/public/tool.rb') } + end + + context 'when the dependency violation is already recorded in package_todo.yml' do + before do + write_pack('packs/apples', 'enforce_dependencies' => true, 'enforce_privacy' => false) + write_pack('packs/tools', 'enforce_dependencies' => true, 'enforce_privacy' => false) + write_file('packs/apples/app/public/apples.rb') + write_file('packs/tools/app/public/tool.rb') + write_file('packs/tools/package_todo.yml', <<~YML) + packs/apples: + "::Apples": + violations: + - dependency + files: + - packs/tools/app/public/tool.rb + YML + end + + let(:source) do + <<~SOURCE + class Tools + Apples + end + SOURCE + end + + it 'does not re-report the existing violation' do + expect_no_offenses source, File.expand_path('packs/tools/app/public/tool.rb') + end + end + context 'namespacing convention is being followed' do context 'unstated dependency used' do before do diff --git a/spec/rubocop/cop/packwerk_lite/privacy_checker_spec.rb b/spec/rubocop/cop/packwerk_lite/privacy_checker_spec.rb index e3c6638..516218c 100644 --- a/spec/rubocop/cop/packwerk_lite/privacy_checker_spec.rb +++ b/spec/rubocop/cop/packwerk_lite/privacy_checker_spec.rb @@ -4,6 +4,35 @@ RSpec.describe RuboCop::Cop::PackwerkLite::Privacy, :config do subject(:cop) { described_class.new(config) } + context 'when the violation is already recorded in package_todo.yml' do + before do + write_pack('packs/apples', 'enforce_privacy' => true, 'enforce_dependencies' => false) + write_file('packs/apples/app/services/apples.rb') + write_pack('packs/tools', 'enforce_privacy' => true, 'enforce_dependencies' => false) + write_file('packs/tools/app/services/tools.rb') + write_file('packs/apples/package_todo.yml', <<~YML) + packs/tools: + "::Tools": + violations: + - privacy + files: + - packs/apples/app/services/apples.rb + YML + end + + let(:source) do + <<~RUBY + class Apples + Tools + end + RUBY + end + + it 'does not re-report the existing violation' do + expect_no_offenses source, File.expand_path('packs/apples/app/services/apples.rb') + end + end + context 'namespace convention is being followed' do context 'a private API is used from a private folder' do before do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 68a6468..3a8472a 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -2,6 +2,7 @@ # frozen_string_literal: true require 'bundler/setup' +require 'simplecov' require 'rubocop-packs' require 'rubocop/rspec/support' require 'packs/rspec/support' From 61c50bcb8904e1e3e2c052c2f72f04b8533443cc Mon Sep 17 00:00:00 2001 From: Dave Corson-Knowles Date: Tue, 9 Jun 2026 10:34:18 -0700 Subject: [PATCH 3/3] Fix rubocop CI: strict sigils on plugin/version + regenerate manual MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - plugin.rb: bump to `# typed: strict`, add `extend T::Sig` and sigs on about/supported?/rules (lint_roller RBI provides the types). - version.rb: exclude from Sorbet/StrictSigil instead of making it strict — the gemspec require_relatives it before sorbet-runtime loads, so a `T.let` here would raise NameError on bundle/gem build. - Regenerate manual/ (VersionAdded now synced from config/default.yml). Co-Authored-By: Claude Opus 4.8 (1M context) --- .rubocop.yml | 2 ++ lib/rubocop/packs/plugin.rb | 7 ++++++- manual/cops_packs.md | 8 ++++---- manual/cops_packwerklite.md | 6 +++--- 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index c588262..b99b7be 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -33,6 +33,8 @@ Sorbet/StrictSigil: Exclude: - tasks/cop_documentation.rake - spec/**/* + # Loaded by the gemspec before sorbet-runtime, so `T` is unavailable here. + - lib/rubocop/packs/version.rb Metrics/ParameterLists: Enabled: false diff --git a/lib/rubocop/packs/plugin.rb b/lib/rubocop/packs/plugin.rb index 7d89a1d..f847c53 100644 --- a/lib/rubocop/packs/plugin.rb +++ b/lib/rubocop/packs/plugin.rb @@ -1,4 +1,4 @@ -# typed: false +# typed: strict # frozen_string_literal: true require 'lint_roller' @@ -7,6 +7,9 @@ module RuboCop module Packs # A plugin that integrates rubocop-packs with RuboCop's plugin system. class Plugin < LintRoller::Plugin + extend T::Sig + + sig { returns(LintRoller::About) } def about LintRoller::About.new( name: 'rubocop-packs', @@ -16,10 +19,12 @@ def about ) end + sig { params(context: LintRoller::Context).returns(T::Boolean) } def supported?(context) context.engine == :rubocop end + sig { params(_context: LintRoller::Context).returns(LintRoller::Rules) } def rules(_context) LintRoller::Rules.new( type: :path, diff --git a/manual/cops_packs.md b/manual/cops_packs.md index 39b6a60..65844fe 100644 --- a/manual/cops_packs.md +++ b/manual/cops_packs.md @@ -4,7 +4,7 @@ Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged --- | --- | --- | --- | --- -Disabled | Yes | No | - | - +Disabled | Yes | No | 0.0.2 | - This cop states that public API should live on class methods, which are more easily statically analyzable, searchable, and typically hold less state. @@ -43,7 +43,7 @@ AcceptableMixins | `[]` | Array Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged --- | --- | --- | --- | --- -Disabled | Yes | No | - | - +Disabled | Yes | No | 0.0.2 | - This cop helps ensure that each pack has a documented public API The following examples assume this basic setup. @@ -78,7 +78,7 @@ end Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged --- | --- | --- | --- | --- -Disabled | Yes | No | - | - +Disabled | Yes | No | 0.0.2 | - This cop helps ensure that each pack exposes one namespace. Note that this cop doesn't necessarily expect you to be using packs-rails (https://github.com/rubyatscale/packs-rails), @@ -102,7 +102,7 @@ class Foo::Blah::Bar; end Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged --- | --- | --- | --- | --- -Disabled | Yes | Yes | - | - +Disabled | Yes | Yes | 0.0.2 | - This cop helps ensure that each pack's public API is strictly typed, enforcing strong boundaries. diff --git a/manual/cops_packwerklite.md b/manual/cops_packwerklite.md index ec9539a..49c741e 100644 --- a/manual/cops_packwerklite.md +++ b/manual/cops_packwerklite.md @@ -4,7 +4,7 @@ Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged --- | --- | --- | --- | --- -Disabled | Yes | No | - | - +Disabled | Yes | No | 0.0.12 | - This cop helps ensure that packs are depending on packs explicitly. @@ -45,9 +45,9 @@ end Enabled by default | Safe | Supports autocorrection | VersionAdded | VersionChanged --- | --- | --- | --- | --- -Disabled | Yes | No | - | - +Disabled | Yes | No | 0.0.12 | - -This cop helps ensure that packs are using public API of other systems +This cop helps ensure that packs are using the public API of other systems The following examples assume this basic setup. ### Examples