code_ownership 2.0.0.pre.1-arm64-darwin → 2.0.0.pre.3-arm64-darwin
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/code_ownership/3.2/code_ownership.bundle +0 -0
- data/lib/code_ownership/3.4/code_ownership.bundle +0 -0
- data/lib/code_ownership/cli.rb +5 -18
- data/lib/code_ownership/private/for_file_output_builder.rb +83 -0
- data/lib/code_ownership/private/team_finder.rb +21 -7
- data/lib/code_ownership/version.rb +1 -1
- data/lib/code_ownership.rb +173 -6
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b2f959f149fbbace4157a6bcd12ac2d15f8a43d517fce8ea4d1898273059c4bc
|
4
|
+
data.tar.gz: 5c4d451d92e34a5f64fa4207ce9fe6099a512a80f3037e8c739be89fc6dca18b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 920fecf5b66a01c71c00f43dda6b613e215fc810e54fb1b6fd8c18eaf247266dc3f860676b2f7fce2da9ab2a0c4b2c5a416c83b96890a7cfba087d2308d50290
|
7
|
+
data.tar.gz: 14f964a70e0c750e826bffbd9cc84db851e0172abb2d062898b6923d17f0743e8ce44276d1f668e7dde81dce5a46098363c03c912924b6987be25e4255c53e4f
|
Binary file
|
Binary file
|
data/lib/code_ownership/cli.rb
CHANGED
@@ -95,6 +95,10 @@ module CodeOwnership
|
|
95
95
|
options[:json] = true
|
96
96
|
end
|
97
97
|
|
98
|
+
opts.on('--verbose', 'Output verbose information') do
|
99
|
+
options[:verbose] = true
|
100
|
+
end
|
101
|
+
|
98
102
|
opts.on('--help', 'Shows this prompt') do
|
99
103
|
puts opts
|
100
104
|
exit
|
@@ -107,24 +111,7 @@ module CodeOwnership
|
|
107
111
|
raise "Please pass in one file. Use `#{EXECUTABLE} for_file --help` for more info"
|
108
112
|
end
|
109
113
|
|
110
|
-
|
111
|
-
|
112
|
-
team_name = team&.name || 'Unowned'
|
113
|
-
team_yml = team&.config_yml || 'Unowned'
|
114
|
-
|
115
|
-
if options[:json]
|
116
|
-
json = {
|
117
|
-
team_name: team_name,
|
118
|
-
team_yml: team_yml
|
119
|
-
}
|
120
|
-
|
121
|
-
puts json.to_json
|
122
|
-
else
|
123
|
-
puts <<~MSG
|
124
|
-
Team: #{team_name}
|
125
|
-
Team YML: #{team_yml}
|
126
|
-
MSG
|
127
|
-
end
|
114
|
+
puts CodeOwnership::Private::ForFileOutputBuilder.build(file_path: files.first, json: !!options[:json], verbose: !!options[:verbose])
|
128
115
|
end
|
129
116
|
|
130
117
|
def self.for_team(argv)
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# typed: strict
|
4
|
+
|
5
|
+
module CodeOwnership
|
6
|
+
module Private
|
7
|
+
class ForFileOutputBuilder
|
8
|
+
extend T::Sig
|
9
|
+
private_class_method :new
|
10
|
+
|
11
|
+
sig { params(file_path: String, json: T::Boolean, verbose: T::Boolean).void }
|
12
|
+
def initialize(file_path:, json:, verbose:)
|
13
|
+
@file_path = file_path
|
14
|
+
@json = json
|
15
|
+
@verbose = verbose
|
16
|
+
end
|
17
|
+
|
18
|
+
sig { params(file_path: String, json: T::Boolean, verbose: T::Boolean).returns(String) }
|
19
|
+
def self.build(file_path:, json:, verbose:)
|
20
|
+
new(file_path: file_path, json: json, verbose: verbose).build
|
21
|
+
end
|
22
|
+
|
23
|
+
UNOWNED_OUTPUT = T.let(
|
24
|
+
{
|
25
|
+
team_name: 'Unowned',
|
26
|
+
team_yml: 'Unowned'
|
27
|
+
},
|
28
|
+
T::Hash[Symbol, T.untyped]
|
29
|
+
)
|
30
|
+
|
31
|
+
sig { returns(String) }
|
32
|
+
def build
|
33
|
+
result_hash = @verbose ? build_verbose : build_terse
|
34
|
+
|
35
|
+
return result_hash.to_json if @json
|
36
|
+
|
37
|
+
build_message_for(result_hash)
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
sig { returns(T::Hash[Symbol, T.untyped]) }
|
43
|
+
def build_verbose
|
44
|
+
result = CodeOwnership.for_file_verbose(@file_path)
|
45
|
+
return UNOWNED_OUTPUT if result.nil?
|
46
|
+
|
47
|
+
{
|
48
|
+
team_name: result[:team_name],
|
49
|
+
team_yml: result[:team_config_yml],
|
50
|
+
description: result[:reasons]
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
sig { returns(T::Hash[Symbol, T.untyped]) }
|
55
|
+
def build_terse
|
56
|
+
team = CodeOwnership.for_file(@file_path, from_codeowners: false, allow_raise: true)
|
57
|
+
|
58
|
+
if team.nil?
|
59
|
+
UNOWNED_OUTPUT
|
60
|
+
else
|
61
|
+
{
|
62
|
+
team_name: team.name,
|
63
|
+
team_yml: team.config_yml
|
64
|
+
}
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
sig { params(result_hash: T::Hash[Symbol, T.untyped]).returns(String) }
|
69
|
+
def build_message_for(result_hash)
|
70
|
+
messages = ["Team: #{result_hash[:team_name]}", "Team YML: #{result_hash[:team_yml]}"]
|
71
|
+
description_list = T.let(Array(result_hash[:description]), T::Array[String])
|
72
|
+
messages << build_description_message(description_list) unless description_list.empty?
|
73
|
+
messages.last << "\n"
|
74
|
+
messages.join("\n")
|
75
|
+
end
|
76
|
+
|
77
|
+
sig { params(reasons: T::Array[String]).returns(String) }
|
78
|
+
def build_description_message(reasons)
|
79
|
+
"Description:\n- #{reasons.join("\n-")}"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -12,8 +12,8 @@ module CodeOwnership
|
|
12
12
|
|
13
13
|
requires_ancestor { Kernel }
|
14
14
|
|
15
|
-
sig { params(file_path: String).returns(T.nilable(CodeTeams::Team)) }
|
16
|
-
def for_file(file_path)
|
15
|
+
sig { params(file_path: String, allow_raise: T::Boolean).returns(T.nilable(CodeTeams::Team)) }
|
16
|
+
def for_file(file_path, allow_raise: false)
|
17
17
|
return nil if file_path.start_with?('./')
|
18
18
|
|
19
19
|
return FilePathTeamCache.get(file_path) if FilePathTeamCache.cached?(file_path)
|
@@ -24,12 +24,22 @@ module CodeOwnership
|
|
24
24
|
if result[:team_name].nil?
|
25
25
|
FilePathTeamCache.set(file_path, nil)
|
26
26
|
else
|
27
|
-
FilePathTeamCache.set(file_path, T.let(find_team!(T.must(result[:team_name])), T.nilable(CodeTeams::Team)))
|
27
|
+
FilePathTeamCache.set(file_path, T.let(find_team!(T.must(result[:team_name]), allow_raise: allow_raise), T.nilable(CodeTeams::Team)))
|
28
28
|
end
|
29
29
|
|
30
30
|
FilePathTeamCache.get(file_path)
|
31
31
|
end
|
32
32
|
|
33
|
+
sig { params(files: T::Array[String], allow_raise: T::Boolean).returns(T::Hash[String, T.nilable(CodeTeams::Team)]) }
|
34
|
+
def teams_for_files(files, allow_raise: false)
|
35
|
+
::RustCodeOwners.teams_for_files(files).each_with_object({}) do |path_team, hash|
|
36
|
+
file_path, team = path_team
|
37
|
+
found_team = team ? find_team!(team[:team_name], allow_raise: allow_raise) : nil
|
38
|
+
FilePathTeamCache.set(file_path, found_team)
|
39
|
+
hash[file_path] = found_team
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
33
43
|
sig { params(klass: T.nilable(T.any(T::Class[T.anything], Module))).returns(T.nilable(::CodeTeams::Team)) }
|
34
44
|
def for_class(klass)
|
35
45
|
file_path = FilePathFinder.path_from_klass(klass)
|
@@ -43,7 +53,7 @@ module CodeOwnership
|
|
43
53
|
owner_name = package.raw_hash['owner'] || package.metadata['owner']
|
44
54
|
return nil if owner_name.nil?
|
45
55
|
|
46
|
-
find_team!(owner_name)
|
56
|
+
find_team!(owner_name, allow_raise: true)
|
47
57
|
end
|
48
58
|
|
49
59
|
sig { params(backtrace: T.nilable(T::Array[String]), excluded_teams: T::Array[::CodeTeams::Team]).returns(T.nilable(::CodeTeams::Team)) }
|
@@ -63,10 +73,14 @@ module CodeOwnership
|
|
63
73
|
nil
|
64
74
|
end
|
65
75
|
|
66
|
-
sig { params(team_name: String).returns(CodeTeams::Team) }
|
67
|
-
def find_team!(team_name)
|
68
|
-
CodeTeams.find(team_name)
|
76
|
+
sig { params(team_name: String, allow_raise: T::Boolean).returns(T.nilable(CodeTeams::Team)) }
|
77
|
+
def find_team!(team_name, allow_raise: false)
|
78
|
+
team = CodeTeams.find(team_name)
|
79
|
+
if team.nil? && allow_raise
|
69
80
|
raise(StandardError, "Could not find team with name: `#{team_name}`. Make sure the team is one of `#{CodeTeams.all.map(&:name).sort}`")
|
81
|
+
end
|
82
|
+
|
83
|
+
team
|
70
84
|
end
|
71
85
|
|
72
86
|
private_class_method(:find_team!)
|
data/lib/code_ownership.rb
CHANGED
@@ -11,6 +11,7 @@ require 'code_ownership/version'
|
|
11
11
|
require 'code_ownership/private/file_path_finder'
|
12
12
|
require 'code_ownership/private/file_path_team_cache'
|
13
13
|
require 'code_ownership/private/team_finder'
|
14
|
+
require 'code_ownership/private/for_file_output_builder'
|
14
15
|
require 'code_ownership/cli'
|
15
16
|
|
16
17
|
begin
|
@@ -33,15 +34,135 @@ module CodeOwnership
|
|
33
34
|
requires_ancestor { Kernel }
|
34
35
|
GlobsToOwningTeamMap = T.type_alias { T::Hash[String, CodeTeams::Team] }
|
35
36
|
|
37
|
+
# Returns the version of the code_ownership gem and the codeowners-rs gem.
|
36
38
|
sig { returns(T::Array[String]) }
|
37
39
|
def version
|
38
40
|
["code_ownership version: #{VERSION}",
|
39
41
|
"codeowners-rs version: #{::RustCodeOwners.version}"]
|
40
42
|
end
|
41
43
|
|
42
|
-
|
43
|
-
|
44
|
-
|
44
|
+
# Returns the owning team for a given file path.
|
45
|
+
#
|
46
|
+
# @param file [String] The path to the file to find ownership for. Can be relative or absolute.
|
47
|
+
# @param from_codeowners [Boolean] (default: true) When true, uses CODEOWNERS file to determine ownership.
|
48
|
+
# When false, uses alternative team finding strategies (e.g., package ownership).
|
49
|
+
# from_codeowners true is faster because it simply matches the provided file to the generate CODEOWNERS file. This is a safe option when you can trust the CODEOWNERS file to be up to date.
|
50
|
+
# @param allow_raise [Boolean] (default: false) When true, raises an exception if ownership cannot be determined.
|
51
|
+
# When false, returns nil for files without ownership.
|
52
|
+
#
|
53
|
+
# @return [CodeTeams::Team, nil] The team that owns the file, or nil if no owner is found
|
54
|
+
# (unless allow_raise is true, in which case an exception is raised).
|
55
|
+
#
|
56
|
+
# @example Find owner for a file using CODEOWNERS
|
57
|
+
# team = CodeOwnership.for_file('app/models/user.rb')
|
58
|
+
# # => #<CodeTeams::Team:0x... @name="platform">
|
59
|
+
#
|
60
|
+
# @example Find owner without using CODEOWNERS
|
61
|
+
# team = CodeOwnership.for_file('app/models/user.rb', from_codeowners: false)
|
62
|
+
# # => #<CodeTeams::Team:0x... @name="platform">
|
63
|
+
#
|
64
|
+
# @example Raise if no owner is found
|
65
|
+
# team = CodeOwnership.for_file('unknown_file.rb', allow_raise: true)
|
66
|
+
# # => raises exception if no owner found
|
67
|
+
#
|
68
|
+
sig { params(file: String, from_codeowners: T::Boolean, allow_raise: T::Boolean).returns(T.nilable(CodeTeams::Team)) }
|
69
|
+
def for_file(file, from_codeowners: true, allow_raise: false)
|
70
|
+
if from_codeowners
|
71
|
+
teams_for_files_from_codeowners([file], allow_raise: allow_raise).values.first
|
72
|
+
else
|
73
|
+
Private::TeamFinder.for_file(file, allow_raise: allow_raise)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Returns the owning teams for multiple file paths using the CODEOWNERS file.
|
78
|
+
#
|
79
|
+
# This method efficiently determines ownership for multiple files in a single operation
|
80
|
+
# by leveraging the generated CODEOWNERS file. It's more performant than calling
|
81
|
+
# `for_file` multiple times when you need to check ownership for many files.
|
82
|
+
#
|
83
|
+
# @param files [Array<String>] An array of file paths to find ownership for.
|
84
|
+
# Paths can be relative to the project root or absolute.
|
85
|
+
# @param allow_raise [Boolean] (default: false) When true, raises an exception if a team
|
86
|
+
# name in CODEOWNERS cannot be resolved to an actual team.
|
87
|
+
# When false, returns nil for files with unresolvable teams.
|
88
|
+
#
|
89
|
+
# @return [T::Hash[String, T.nilable(CodeTeams::Team)]] A hash mapping each file path to its
|
90
|
+
# owning team. Files without ownership
|
91
|
+
# or with unresolvable teams will map to nil.
|
92
|
+
#
|
93
|
+
# @example Get owners for multiple files
|
94
|
+
# files = ['app/models/user.rb', 'app/controllers/users_controller.rb', 'config/routes.rb']
|
95
|
+
# owners = CodeOwnership.teams_for_files_from_codeowners(files)
|
96
|
+
# # => {
|
97
|
+
# # 'app/models/user.rb' => #<CodeTeams::Team:0x... @name="platform">,
|
98
|
+
# # 'app/controllers/users_controller.rb' => #<CodeTeams::Team:0x... @name="platform">,
|
99
|
+
# # 'config/routes.rb' => #<CodeTeams::Team:0x... @name="infrastructure">
|
100
|
+
# # }
|
101
|
+
#
|
102
|
+
# @example Handle files without owners
|
103
|
+
# files = ['owned_file.rb', 'unowned_file.txt']
|
104
|
+
# owners = CodeOwnership.teams_for_files_from_codeowners(files)
|
105
|
+
# # => {
|
106
|
+
# # 'owned_file.rb' => #<CodeTeams::Team:0x... @name="backend">,
|
107
|
+
# # 'unowned_file.txt' => nil
|
108
|
+
# # }
|
109
|
+
#
|
110
|
+
# @note This method uses caching internally for performance. The cache is populated
|
111
|
+
# as files are processed and reused for subsequent lookups.
|
112
|
+
#
|
113
|
+
# @note This method relies on the CODEOWNERS file being up-to-date. Run
|
114
|
+
# `CodeOwnership.validate!` to ensure the CODEOWNERS file is current.
|
115
|
+
#
|
116
|
+
# @see #for_file for single file ownership lookup
|
117
|
+
# @see #validate! for ensuring CODEOWNERS file is up-to-date
|
118
|
+
#
|
119
|
+
sig { params(files: T::Array[String], allow_raise: T::Boolean).returns(T::Hash[String, T.nilable(CodeTeams::Team)]) }
|
120
|
+
def teams_for_files_from_codeowners(files, allow_raise: false)
|
121
|
+
Private::TeamFinder.teams_for_files(files, allow_raise: allow_raise)
|
122
|
+
end
|
123
|
+
|
124
|
+
# Returns detailed ownership information for a given file path.
|
125
|
+
#
|
126
|
+
# This method provides verbose ownership details including the team name,
|
127
|
+
# team configuration file path, and the reasons/sources for ownership assignment.
|
128
|
+
# It's particularly useful for debugging ownership assignments and understanding
|
129
|
+
# why a file is owned by a specific team.
|
130
|
+
#
|
131
|
+
# @param file [String] The path to the file to find ownership for. Can be relative or absolute.
|
132
|
+
#
|
133
|
+
# @return [T::Hash[Symbol, String], nil] A hash containing detailed ownership information,
|
134
|
+
# or nil if no owner is found.
|
135
|
+
#
|
136
|
+
# The returned hash contains the following keys when an owner is found:
|
137
|
+
# - :team_name [String] - The name of the owning team
|
138
|
+
# - :team_config_yml [String] - Path to the team's configuration YAML file
|
139
|
+
# - :reasons [Array<String>] - List of reasons/sources explaining why this team owns the file
|
140
|
+
# (e.g., "CODEOWNERS pattern: /app/models/**", "Package ownership")
|
141
|
+
#
|
142
|
+
# @example Get verbose ownership details
|
143
|
+
# details = CodeOwnership.for_file_verbose('app/models/user.rb')
|
144
|
+
# # => {
|
145
|
+
# # team_name: "platform",
|
146
|
+
# # team_config_yml: "config/teams/platform.yml",
|
147
|
+
# # reasons: ["Matched pattern '/app/models/**' in CODEOWNERS"]
|
148
|
+
# # }
|
149
|
+
#
|
150
|
+
# @example Handle unowned files
|
151
|
+
# details = CodeOwnership.for_file_verbose('unowned_file.txt')
|
152
|
+
# # => nil
|
153
|
+
#
|
154
|
+
# @note This method is primarily used by the CLI tool when the --verbose flag is provided,
|
155
|
+
# allowing users to understand the ownership assignment logic.
|
156
|
+
#
|
157
|
+
# @note Unlike `for_file`, this method always uses the CODEOWNERS file and other ownership
|
158
|
+
# sources to determine ownership, providing complete context about the ownership decision.
|
159
|
+
#
|
160
|
+
# @see #for_file for a simpler ownership lookup that returns just the team
|
161
|
+
# @see CLI#for_file for the command-line interface that uses this method
|
162
|
+
#
|
163
|
+
sig { params(file: String).returns(T.nilable(T::Hash[Symbol, String])) }
|
164
|
+
def for_file_verbose(file)
|
165
|
+
::RustCodeOwners.for_file(file)
|
45
166
|
end
|
46
167
|
|
47
168
|
sig { params(team: T.any(CodeTeams::Team, String)).returns(T::Array[String]) }
|
@@ -50,9 +171,55 @@ module CodeOwnership
|
|
50
171
|
::RustCodeOwners.for_team(team.name)
|
51
172
|
end
|
52
173
|
|
53
|
-
|
54
|
-
|
55
|
-
|
174
|
+
# Validates code ownership configuration and optionally corrects issues.
|
175
|
+
#
|
176
|
+
# This method performs comprehensive validation of the code ownership setup, ensuring:
|
177
|
+
# 1. Only one ownership mechanism is defined per file (no conflicts between annotations, packages, or globs)
|
178
|
+
# 2. All referenced teams are valid (exist in CodeTeams configuration)
|
179
|
+
# 3. All files have ownership (unless explicitly listed in unowned_globs)
|
180
|
+
# 4. The .github/CODEOWNERS file is up-to-date and properly formatted
|
181
|
+
#
|
182
|
+
# When autocorrect is enabled, the method will automatically:
|
183
|
+
# - Generate or update the CODEOWNERS file based on current ownership rules
|
184
|
+
# - Fix any formatting issues in the CODEOWNERS file
|
185
|
+
# - Stage the corrected CODEOWNERS file (unless stage_changes is false)
|
186
|
+
#
|
187
|
+
# @param autocorrect [Boolean] Whether to automatically fix correctable issues (default: true)
|
188
|
+
# When true, regenerates and updates the CODEOWNERS file
|
189
|
+
# When false, only validates without making changes
|
190
|
+
#
|
191
|
+
# @param stage_changes [Boolean] Whether to stage the CODEOWNERS file after autocorrection (default: true)
|
192
|
+
# Only applies when autocorrect is true
|
193
|
+
# When false, changes are written but not staged with git
|
194
|
+
#
|
195
|
+
# @param files [Array<String>, nil] Ignored. This is a legacy parameter that is no longer used.
|
196
|
+
#
|
197
|
+
# @return [void]
|
198
|
+
#
|
199
|
+
# @raise [RuntimeError] Raises an error if validation fails with details about:
|
200
|
+
# - Files with conflicting ownership definitions
|
201
|
+
# - References to non-existent teams
|
202
|
+
# - Files without ownership (not in unowned_globs)
|
203
|
+
# - CODEOWNERS file inconsistencies
|
204
|
+
#
|
205
|
+
# @example Basic validation with autocorrection
|
206
|
+
# CodeOwnership.validate!
|
207
|
+
# # Validates all files and auto-corrects/stages CODEOWNERS if needed
|
208
|
+
#
|
209
|
+
# @example Validation without making changes
|
210
|
+
# CodeOwnership.validate!(autocorrect: false)
|
211
|
+
# # Only checks for issues without updating CODEOWNERS
|
212
|
+
#
|
213
|
+
# @example Validate and fix but don't stage changes
|
214
|
+
# CodeOwnership.validate!(autocorrect: true, stage_changes: false)
|
215
|
+
# # Fixes CODEOWNERS but doesn't stage it with git
|
216
|
+
#
|
217
|
+
# @note This method is called by the CLI command: bin/codeownership validate
|
218
|
+
# @note The validation can be disabled for CODEOWNERS by setting skip_codeowners_validation: true in config/code_ownership.yml
|
219
|
+
#
|
220
|
+
# @see CLI.validate! for the command-line interface
|
221
|
+
# @see https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners for CODEOWNERS format
|
222
|
+
#
|
56
223
|
sig do
|
57
224
|
params(
|
58
225
|
autocorrect: T::Boolean,
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: code_ownership
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0.0.pre.
|
4
|
+
version: 2.0.0.pre.3
|
5
5
|
platform: arm64-darwin
|
6
6
|
authors:
|
7
7
|
- Gusto Engineers
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-09-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: code_teams
|
@@ -181,6 +181,7 @@ files:
|
|
181
181
|
- lib/code_ownership/cli.rb
|
182
182
|
- lib/code_ownership/private/file_path_finder.rb
|
183
183
|
- lib/code_ownership/private/file_path_team_cache.rb
|
184
|
+
- lib/code_ownership/private/for_file_output_builder.rb
|
184
185
|
- lib/code_ownership/private/permit_pack_owner_top_level_key.rb
|
185
186
|
- lib/code_ownership/private/team_finder.rb
|
186
187
|
- lib/code_ownership/version.rb
|