policial 0.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: f9ea96661ceb59f07846827daf7ccf1d9f1ed2c6
4
+ data.tar.gz: 26d33c00ee9d4dfa3e65196f3faa6d8c4bfad416
5
+ SHA512:
6
+ metadata.gz: 59ea217f976c38a00fb0a80bac2fd23c73f592622efcef1e8689db454ced0e54365da3e9a459f0b0a1a26d6e34ae5c4e5a4105d748f90ccb16e98a20b7e1bf8e
7
+ data.tar.gz: 58be1670722255f6886aae39685cf7357406e4b1e74d97b752752faa682574b8d5914bd233ad9c32f8683197e33e79e38220062faf4144deb34a85ccd729ddd0
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Volmer Soares
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,79 @@
1
+ # Policial
2
+
3
+ [![Build Status](https://travis-ci.org/volmer/policial.svg)](https://travis-ci.org/volmer/policial)
4
+
5
+ *Policial* is a gem that investigates pull requests and accuses style guide
6
+ violations. It is based on thoughtbot's
7
+ [Hound project](https://github.com/thoughtbot/hound).
8
+ Currently it only investigates ruby code. You can setup your ruby code style
9
+ rules by defining a `.rubocop.yml` file in you repo. Please see
10
+ [RuboCop's README](https://github.com/bbatsov/rubocop).
11
+
12
+ ## Installation
13
+
14
+ Add this line to your application's Gemfile:
15
+
16
+ ```ruby
17
+ gem 'policial'
18
+ ```
19
+
20
+ And then execute:
21
+
22
+ $ bundle
23
+
24
+ Or install it yourself as:
25
+
26
+ $ gem install policial
27
+
28
+ ## Usage
29
+
30
+ First, you need to set your GitHub credentials. For more information on
31
+ this, please check [Octokit README](https://github.com/octokit/octokit.rb).
32
+
33
+ ```ruby
34
+ Octokit.configure do |c|
35
+ c.access_tokein = 'mygithubtoken666'
36
+ end
37
+ ```
38
+
39
+ You start with a pull request which Policial will run an investigation
40
+ against. You can setup a pull request manually:
41
+
42
+ ```ruby
43
+ pull_request = Policial::PullRequest.new(
44
+ repo: 'volmer/my_repo',
45
+ number: 3,
46
+ head_sha: 'headsha'
47
+ )
48
+ ```
49
+
50
+ Or you can extract a pull request from a
51
+ [GitHub `pull_request` webhook](https://developer.github.com/webhooks):
52
+
53
+ ```ruby
54
+ event = Policial::PullRequestEvent.new(webhook_payload)
55
+ pull_request = event.pull_request
56
+ ```
57
+
58
+ Now you can start an investigation:
59
+
60
+ ```ruby
61
+ investigation = Policial::Investigation.new(pull_request)
62
+
63
+ # Let's investigate this pull request...
64
+ investigation.run
65
+
66
+ # Want to know the violations found?
67
+ investigation.violations
68
+
69
+ # Hurry, post comments about those violations on the pull request!
70
+ investigation.accuse
71
+ ```
72
+
73
+ ## Contributing
74
+
75
+ 1. Fork it ( https://github.com/volmer/policial/fork )
76
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
77
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
78
+ 4. Push to the branch (`git push origin my-new-feature`)
79
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+ require 'rubocop/rake_task'
4
+
5
+ RuboCop::RakeTask.new
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: [:rubocop, :spec]
data/lib/policial.rb ADDED
@@ -0,0 +1,28 @@
1
+ require 'octokit'
2
+
3
+ require 'policial/accusation_policy'
4
+ require 'policial/commenter'
5
+ require 'policial/commit'
6
+ require 'policial/commit_file'
7
+ require 'policial/investigation'
8
+ require 'policial/line'
9
+ require 'policial/patch'
10
+ require 'policial/pull_request'
11
+ require 'policial/pull_request_event'
12
+ require 'policial/repo_config'
13
+ require 'policial/style_checker'
14
+ require 'policial/style_guides/base'
15
+ require 'policial/style_guides/ruby'
16
+ require 'policial/style_guides/unsupported'
17
+ require 'policial/unchanged_line'
18
+ require 'policial/version'
19
+ require 'policial/violation'
20
+
21
+ # Public: The global gem module. It exposes some module attribute accessors
22
+ # so you can configure GitHub credentials, enable/disable style guides
23
+ # and more.
24
+ module Policial
25
+ STYLE_GUIDES = [Policial::StyleGuides::Ruby]
26
+
27
+ Octokit.auto_paginate = true
28
+ end
@@ -0,0 +1,34 @@
1
+ module Policial
2
+ # Public: Checks if a given pull request should be investigated or not.
3
+ class AccusationPolicy
4
+ def initialize(pull_request)
5
+ @pull_request = pull_request
6
+ end
7
+
8
+ def allowed_for?(violation)
9
+ unreported_violation_messages(violation).any?
10
+ end
11
+
12
+ private
13
+
14
+ def unreported_violation_messages(violation)
15
+ violation.messages - existing_violation_messages(violation)
16
+ end
17
+
18
+ def existing_violation_messages(violation)
19
+ previous_comments_on_line(violation).map(&:body)
20
+ .flat_map { |body| body.split('<br>') }
21
+ end
22
+
23
+ def previous_comments_on_line(violation)
24
+ existing_comments.select do |comment|
25
+ comment.path == violation.filename &&
26
+ comment.original_position == violation.patch_position
27
+ end
28
+ end
29
+
30
+ def existing_comments
31
+ @existing_comments ||= @pull_request.comments
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,25 @@
1
+ module Policial
2
+ # Public: Comment violations on pull request.
3
+ class Commenter
4
+ def initialize(pull_request)
5
+ @pull_request = pull_request
6
+ end
7
+
8
+ def comment_violation(violation)
9
+ Octokit.create_pull_request_comment(
10
+ @pull_request.repo,
11
+ @pull_request.number,
12
+ comment_body(violation),
13
+ @pull_request.head_commit.sha,
14
+ violation.filename,
15
+ violation.line_number
16
+ )
17
+ end
18
+
19
+ private
20
+
21
+ def comment_body(violation)
22
+ violation.messages.join('<br/>')
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,23 @@
1
+ module Policial
2
+ # Public: A Commit in a GitHub repo.
3
+ class Commit
4
+ attr_reader :repo, :sha
5
+
6
+ def initialize(repo, sha)
7
+ @repo = repo
8
+ @sha = sha
9
+ end
10
+
11
+ def file_content(filename)
12
+ contents = Octokit.contents(@repo, path: filename, ref: @sha)
13
+
14
+ if contents && contents.content
15
+ Base64.decode64(contents.content).force_encoding('UTF-8')
16
+ else
17
+ ''
18
+ end
19
+ rescue Octokit::NotFound
20
+ ''
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,40 @@
1
+ module Policial
2
+ # Public: A file in a commit.
3
+ class CommitFile
4
+ attr_reader :commit
5
+
6
+ def initialize(file, commit)
7
+ @file = file
8
+ @commit = commit
9
+ end
10
+
11
+ def filename
12
+ @file.filename
13
+ end
14
+
15
+ def content
16
+ @content ||= begin
17
+ @commit.file_content(filename) unless removed?
18
+ end
19
+ end
20
+
21
+ def removed?
22
+ @file.status == 'removed'
23
+ end
24
+
25
+ def line_at(line_number)
26
+ changed_lines.detect { |line| line.number == line_number } ||
27
+ UnchangedLine.new
28
+ end
29
+
30
+ private
31
+
32
+ def changed_lines
33
+ @changed_lines ||= patch.changed_lines
34
+ end
35
+
36
+ def patch
37
+ Patch.new(@file.patch)
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,35 @@
1
+ module Policial
2
+ # Public: Starting with unparsed data coming from a pull request event,
3
+ # it checks all changes introduced looking for style guide violations. It
4
+ # also accuse all present violations through comments on all relevant lines
5
+ # in the pull request.
6
+ class Investigation
7
+ attr_accessor :violations, :pull_request
8
+
9
+ def initialize(pull_request)
10
+ @pull_request = pull_request
11
+ end
12
+
13
+ def run
14
+ @violations ||= StyleChecker.new(@pull_request).violations
15
+ end
16
+
17
+ def accuse
18
+ return if @violations.blank?
19
+
20
+ commenter = Commenter.new(@pull_request)
21
+
22
+ @violations.each do |violation|
23
+ if accusation_policy.allowed_for?(violation)
24
+ commenter.comment_violation(violation)
25
+ end
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def accusation_policy
32
+ @accusation_policy ||= AccusationPolicy.new(@pull_request)
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,16 @@
1
+ module Policial
2
+ # Public: a changed line in a commit file.
3
+ class Line
4
+ attr_reader :number, :patch_position
5
+
6
+ def initialize(number, content, patch_position)
7
+ @number = number
8
+ @content = content
9
+ @patch_position = patch_position
10
+ end
11
+
12
+ def changed?
13
+ true
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,35 @@
1
+ module Policial
2
+ # Public: a chunk of changed code in a commit file.
3
+ class Patch
4
+ RANGE_INFORMATION_LINE = /^@@ .+\+(?<line_number>\d+),/
5
+ MODIFIED_LINE = /^\+(?!\+|\+)/
6
+ NOT_REMOVED_LINE = /^[^-]/
7
+
8
+ def initialize(body)
9
+ @body = body || ''
10
+ end
11
+
12
+ def changed_lines
13
+ line_number = 0
14
+
15
+ @body.lines.each_with_index.with_object([]) do |(line, patch_pos), lines|
16
+ line_number =
17
+ parse_line(line, line_number, patch_pos, lines) || line_number
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def parse_line(line_content, line_number, patch_position, lines)
24
+ case line_content
25
+ when RANGE_INFORMATION_LINE
26
+ Regexp.last_match[:line_number].to_i
27
+ when MODIFIED_LINE
28
+ lines << Line.new(line_number, line_content, patch_position)
29
+ line_number + 1
30
+ when NOT_REMOVED_LINE
31
+ line_number + 1
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,60 @@
1
+ module Policial
2
+ # Public: A GibHub Pull Request.
3
+ class PullRequest
4
+ attr_reader :repo, :number, :user
5
+
6
+ def initialize(repo:, number:, head_sha:, user: nil)
7
+ @repo = repo
8
+ @number = number
9
+ @head_sha = head_sha
10
+ @user = user
11
+ end
12
+
13
+ def comments
14
+ @comments ||= fetch_comments
15
+ end
16
+
17
+ def files
18
+ @files ||= Octokit.pull_request_files(@repo, @number).map do |file|
19
+ build_commit_file(file)
20
+ end
21
+ end
22
+
23
+ def head_commit
24
+ @head_commit ||= Commit.new(@repo, @head_sha)
25
+ end
26
+
27
+ private
28
+
29
+ def build_commit_file(file)
30
+ CommitFile.new(file, head_commit)
31
+ end
32
+
33
+ def fetch_comments
34
+ paginate do |page|
35
+ Octokit.pull_request_comments(
36
+ @repo,
37
+ @number,
38
+ page: page
39
+ )
40
+ end
41
+ end
42
+
43
+ private
44
+
45
+ def paginate
46
+ page, results, all_pages_fetched = 1, [], false
47
+
48
+ until all_pages_fetched
49
+ if (page_results = yield(page)).empty?
50
+ all_pages_fetched = true
51
+ else
52
+ results += page_results
53
+ page += 1
54
+ end
55
+ end
56
+
57
+ results
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,29 @@
1
+ require 'json'
2
+
3
+ module Policial
4
+ # Public: Parses a pull request event payload.
5
+ class PullRequestEvent
6
+ attr_reader :payload
7
+
8
+ def initialize(payload)
9
+ @payload = payload
10
+ end
11
+
12
+ def pull_request
13
+ @pull_request ||= PullRequest.new(
14
+ repo: @payload['repository']['full_name'],
15
+ number: @payload['number'],
16
+ head_sha: @payload['pull_request']['head']['sha'],
17
+ user: @payload['pull_request']['user']['login']
18
+ )
19
+ rescue NoMethodError
20
+ nil
21
+ end
22
+
23
+ def should_investigate?
24
+ !pull_request.nil? && (
25
+ @payload['action'] == 'opened' || @payload['action'] == 'synchronize'
26
+ )
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,36 @@
1
+ module Policial
2
+ # Public: Load and parse config files from GitHub repo.
3
+ class RepoConfig
4
+ def initialize(commit)
5
+ @commit = commit
6
+ end
7
+
8
+ def enabled_for?(style_guide_class)
9
+ Policial::STYLE_GUIDES.include?(style_guide_class)
10
+ end
11
+
12
+ def for(style_guide_class)
13
+ config_file = style_guide_class.config_file
14
+
15
+ if config_file
16
+ load_file(config_file)
17
+ else
18
+ {}
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def load_file(file)
25
+ config_file_content = @commit.file_content(file)
26
+
27
+ parse_yaml(config_file_content) || {}
28
+ end
29
+
30
+ def parse_yaml(content)
31
+ YAML.load(content)
32
+ rescue Psych::SyntaxError
33
+ {}
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,48 @@
1
+ module Policial
2
+ # Public: Filters files to reviewable subset, builds style guide based on file
3
+ # extension and delegates to style guide for line violations.
4
+ class StyleChecker
5
+ def initialize(pull_request)
6
+ @pull_request = pull_request
7
+ @style_guides = {}
8
+ end
9
+
10
+ def violations
11
+ @violations ||= violations_in_checked_files.select(&:on_changed_line?)
12
+ end
13
+
14
+ private
15
+
16
+ attr_reader :pull_request, :style_guides
17
+
18
+ def violations_in_checked_files
19
+ files_to_check.flat_map do |file|
20
+ style_guide(file.filename).violations_in_file(file)
21
+ end
22
+ end
23
+
24
+ def files_to_check
25
+ pull_request.files.reject(&:removed?).select do |file|
26
+ style_guide(file.filename).enabled?
27
+ end
28
+ end
29
+
30
+ def style_guide(filename)
31
+ style_guide_class = style_guide_class(filename)
32
+ style_guides[style_guide_class] ||= style_guide_class.new(config)
33
+ end
34
+
35
+ def style_guide_class(filename)
36
+ case filename
37
+ when /.+\.rb\z/
38
+ StyleGuides::Ruby
39
+ else
40
+ StyleGuides::Unsupported
41
+ end
42
+ end
43
+
44
+ def config
45
+ @config ||= RepoConfig.new(pull_request.head_commit)
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,18 @@
1
+ module Policial
2
+ module StyleGuides
3
+ # Public: Base to contain common style guide logic.
4
+ class Base
5
+ def initialize(repo_config)
6
+ @repo_config = repo_config
7
+ end
8
+
9
+ def enabled?
10
+ @repo_config.enabled_for?(self.class)
11
+ end
12
+
13
+ def violations_in_file(_file)
14
+ fail NotImplementedError, "must implement ##{__method__}"
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,67 @@
1
+ require 'rubocop'
2
+
3
+ module Policial
4
+ module StyleGuides
5
+ # Public: Determine Ruby style guide violations per-line.
6
+ class Ruby < Base
7
+ class << self
8
+ attr_writer :config_file
9
+
10
+ def config_file
11
+ @config_file || RuboCop::ConfigLoader::DOTFILE
12
+ end
13
+ end
14
+
15
+ def violations_in_file(file)
16
+ if config.file_to_exclude?(file.filename)
17
+ []
18
+ else
19
+ offenses = team.inspect_file(parsed_source(file))
20
+ build_violations(offenses, file)
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def team
27
+ RuboCop::Cop::Team.new(RuboCop::Cop::Cop.all, config, rubocop_options)
28
+ end
29
+
30
+ def parsed_source(file)
31
+ RuboCop::ProcessedSource.new(file.content, file.filename)
32
+ end
33
+
34
+ def config
35
+ @config ||= RuboCop::ConfigLoader.merge_with_default(custom_config, '')
36
+ end
37
+
38
+ def custom_config
39
+ custom = @repo_config.for(self.class)
40
+
41
+ if custom.is_a?(Hash)
42
+ RuboCop::Config.new(custom, '').tap do |config|
43
+ config.add_missing_namespaces
44
+ config.make_excludes_absolute
45
+ end
46
+ else
47
+ RuboCop::Config.new
48
+ end
49
+ end
50
+
51
+ def rubocop_options
52
+ { debug: true } if config['ShowCopNames']
53
+ end
54
+
55
+ def build_violations(offenses, file)
56
+ offenses.each_with_object({}) do |offense, violations|
57
+ if violations[offense.line]
58
+ violations[offense.line].add_messages([offense.message])
59
+ else
60
+ violations[offense.line] =
61
+ Violation.new(file, offense.line, offense.message)
62
+ end
63
+ end.values
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,10 @@
1
+ module Policial
2
+ module StyleGuides
3
+ # Public: Returns empty set of violations.
4
+ class Unsupported < Base
5
+ def violations_in_file(_)
6
+ []
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,15 @@
1
+ module Policial
2
+ # Public: An unchanged line in a commit file.
3
+ class UnchangedLine
4
+ def initialize(*)
5
+ end
6
+
7
+ def patch_position
8
+ -1
9
+ end
10
+
11
+ def changed?
12
+ false
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,4 @@
1
+ # Public: The gem version.
2
+ module Policial
3
+ VERSION = '0.0.1'
4
+ end
@@ -0,0 +1,30 @@
1
+ module Policial
2
+ # Public: Hold file, line, and violation message values. Built by style
3
+ # guides.
4
+ class Violation
5
+ attr_reader :line_number, :filename
6
+
7
+ def initialize(file, line_number, message)
8
+ @filename = file.filename
9
+ @line = file.line_at(line_number)
10
+ @line_number = line_number
11
+ @messages = [message]
12
+ end
13
+
14
+ def add_messages(messages)
15
+ @messages += messages
16
+ end
17
+
18
+ def messages
19
+ @messages.uniq
20
+ end
21
+
22
+ def patch_position
23
+ @line.patch_position
24
+ end
25
+
26
+ def on_changed_line?
27
+ @line.changed?
28
+ end
29
+ end
30
+ end
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: policial
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Volmer Soares
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-12-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: octokit
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.7'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rubocop
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.2'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.2'
41
+ description: Review pull requests for style guide violations.
42
+ email:
43
+ - volmerius@gmail.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - LICENSE.txt
49
+ - README.md
50
+ - Rakefile
51
+ - lib/policial.rb
52
+ - lib/policial/accusation_policy.rb
53
+ - lib/policial/commenter.rb
54
+ - lib/policial/commit.rb
55
+ - lib/policial/commit_file.rb
56
+ - lib/policial/investigation.rb
57
+ - lib/policial/line.rb
58
+ - lib/policial/patch.rb
59
+ - lib/policial/pull_request.rb
60
+ - lib/policial/pull_request_event.rb
61
+ - lib/policial/repo_config.rb
62
+ - lib/policial/style_checker.rb
63
+ - lib/policial/style_guides/base.rb
64
+ - lib/policial/style_guides/ruby.rb
65
+ - lib/policial/style_guides/unsupported.rb
66
+ - lib/policial/unchanged_line.rb
67
+ - lib/policial/version.rb
68
+ - lib/policial/violation.rb
69
+ homepage: https://github.com/volmer/policial
70
+ licenses:
71
+ - MIT
72
+ metadata: {}
73
+ post_install_message:
74
+ rdoc_options: []
75
+ require_paths:
76
+ - lib
77
+ required_ruby_version: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ required_rubygems_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ requirements: []
88
+ rubyforge_project:
89
+ rubygems_version: 2.4.5
90
+ signing_key:
91
+ specification_version: 4
92
+ summary: Review pull requests for style guide violations
93
+ test_files: []