quality_gate 0.1.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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +14 -0
- data/LICENSE.txt +21 -0
- data/README.md +571 -0
- data/config/37signals.yml +26 -0
- data/config/cops.yml +37 -0
- data/config/reek.yml +6 -0
- data/config/rubocop.yml +60 -0
- data/config/ruby.yml +8 -0
- data/docs/codex.md +31 -0
- data/docs/dogfood-log.md +47 -0
- data/docs/incidents.md +18 -0
- data/docs/releasing.md +46 -0
- data/exe/quality_gate +6 -0
- data/lib/generators/quality_gate/install/install_generator.rb +66 -0
- data/lib/generators/quality_gate/install/templates/agents_section.md.tt +15 -0
- data/lib/generators/quality_gate/install/templates/bullet.rb.tt +10 -0
- data/lib/generators/quality_gate/install/templates/claude_settings.json.tt +29 -0
- data/lib/generators/quality_gate/install/templates/hook_log_filesystem.rb.tt +76 -0
- data/lib/generators/quality_gate/install/templates/quality_gate.yml.tt +42 -0
- data/lib/generators/quality_gate/install/templates/quality_gate_fast.rb.tt +248 -0
- data/lib/generators/quality_gate/install/templates/quality_gate_verify_stop.rb.tt +466 -0
- data/lib/generators/quality_gate/install/templates/rubocop.yml.tt +1 -0
- data/lib/generators/quality_gate/install/templates/ruby_agents_section.md.tt +15 -0
- data/lib/generators/quality_gate/install/templates/ruby_quality_gate.yml.tt +40 -0
- data/lib/generators/quality_gate/install/templates/ruby_rubocop.yml.tt +1 -0
- data/lib/generators/quality_gate/install/templates/ruby_simplecov.rb.tt +11 -0
- data/lib/generators/quality_gate/install/templates/simplecov.rb.tt +10 -0
- data/lib/generators/quality_gate/install/templates/strong_migrations.rb.tt +10 -0
- data/lib/quality_gate/adapter.rb +328 -0
- data/lib/quality_gate/adapters/brakeman.rb +125 -0
- data/lib/quality_gate/adapters/bundler_audit.rb +235 -0
- data/lib/quality_gate/adapters/reek.rb +108 -0
- data/lib/quality_gate/adapters/rubocop.rb +142 -0
- data/lib/quality_gate/adapters/simplecov.rb +103 -0
- data/lib/quality_gate/adapters/test_suite.rb +81 -0
- data/lib/quality_gate/adapters/undercover.rb +357 -0
- data/lib/quality_gate/cli.rb +451 -0
- data/lib/quality_gate/config.rb +290 -0
- data/lib/quality_gate/exit_code.rb +14 -0
- data/lib/quality_gate/finding.rb +50 -0
- data/lib/quality_gate/hook_log.rb +131 -0
- data/lib/quality_gate/init_command.rb +90 -0
- data/lib/quality_gate/installation.rb +1577 -0
- data/lib/quality_gate/installer.rb +104 -0
- data/lib/quality_gate/railtie.rb +16 -0
- data/lib/quality_gate/reporters/field_sanitizer.rb +42 -0
- data/lib/quality_gate/reporters/json.rb +59 -0
- data/lib/quality_gate/reporters/text.rb +45 -0
- data/lib/quality_gate/rubocop.rb +34 -0
- data/lib/quality_gate/ruby_profile.rb +170 -0
- data/lib/quality_gate/runner.rb +150 -0
- data/lib/quality_gate/version.rb +5 -0
- data/lib/quality_gate.rb +27 -0
- data/lib/rubocop/cop/quality_gate/association_default_block_value.rb +46 -0
- data/lib/rubocop/cop/quality_gate/broadcast_in_controller.rb +64 -0
- data/lib/rubocop/cop/quality_gate/controller_instance_variables.rb +47 -0
- data/lib/rubocop/cop/quality_gate/prefer_after_save_commit.rb +84 -0
- data/lib/rubocop/cop/quality_gate/private_only_concern.rb +77 -0
- data/llm.txt +13 -0
- data/sig/quality_gate.rbs +226 -0
- metadata +260 -0
data/config/cops.yml
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
QualityGate/AssociationDefaultBlockValue:
|
|
2
|
+
Description: 'Defer request-context association defaults in a callable.'
|
|
3
|
+
Enabled: false
|
|
4
|
+
VersionAdded: '0.1.0'
|
|
5
|
+
Safe: true
|
|
6
|
+
|
|
7
|
+
QualityGate/PreferAfterSaveCommit:
|
|
8
|
+
Description: 'Use after_save_commit for literal create and update commit callbacks.'
|
|
9
|
+
Enabled: false
|
|
10
|
+
VersionAdded: '0.1.0'
|
|
11
|
+
Safe: true
|
|
12
|
+
SafeAutoCorrect: true
|
|
13
|
+
|
|
14
|
+
QualityGate/PrivateOnlyConcern:
|
|
15
|
+
Description: 'Review concerns that expose only private instance behavior.'
|
|
16
|
+
Enabled: false
|
|
17
|
+
VersionAdded: '0.1.0'
|
|
18
|
+
Safe: false
|
|
19
|
+
ConcernPaths: []
|
|
20
|
+
|
|
21
|
+
QualityGate/BroadcastInController:
|
|
22
|
+
Description: 'Review controller broadcasts for model ownership.'
|
|
23
|
+
Enabled: false
|
|
24
|
+
VersionAdded: '0.1.0'
|
|
25
|
+
Safe: false
|
|
26
|
+
Include:
|
|
27
|
+
- '**/app/controllers/**/*.rb'
|
|
28
|
+
AllowedMethods: []
|
|
29
|
+
|
|
30
|
+
QualityGate/ControllerInstanceVariables:
|
|
31
|
+
Description: 'Limit a controller action to one instance variable (Sandi Metz rule four).'
|
|
32
|
+
Enabled: false
|
|
33
|
+
VersionAdded: '0.1.0'
|
|
34
|
+
Safe: true
|
|
35
|
+
Max: 1
|
|
36
|
+
Include:
|
|
37
|
+
- '**/app/controllers/**/*.rb'
|
data/config/reek.yml
ADDED
data/config/rubocop.yml
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
plugins:
|
|
2
|
+
- rubocop-rails
|
|
3
|
+
- rubocop-performance
|
|
4
|
+
- rubocop-minitest
|
|
5
|
+
- quality_gate/rubocop:
|
|
6
|
+
plugin_class_name: QualityGate::RuboCopPlugin
|
|
7
|
+
|
|
8
|
+
AllCops:
|
|
9
|
+
NewCops: disable
|
|
10
|
+
TargetRubyVersion: "3.2"
|
|
11
|
+
Exclude:
|
|
12
|
+
- db/schema.rb
|
|
13
|
+
- bin/**/*
|
|
14
|
+
- node_modules/**/*
|
|
15
|
+
- vendor/**/*
|
|
16
|
+
|
|
17
|
+
Metrics/CyclomaticComplexity:
|
|
18
|
+
# Catch branch-heavy methods before they become opaque.
|
|
19
|
+
Max: 6
|
|
20
|
+
|
|
21
|
+
Metrics/AbcSize:
|
|
22
|
+
# Keep assignment, branch, and condition growth visible in review.
|
|
23
|
+
Max: 16
|
|
24
|
+
|
|
25
|
+
# Sandi Metz rules one to three are plain RuboCop budgets. Rule four is
|
|
26
|
+
# QualityGate/ControllerInstanceVariables, enabled at the bottom of this file.
|
|
27
|
+
#
|
|
28
|
+
# Test suites read as linear setup, action, assertion, so the length rules earn
|
|
29
|
+
# nothing there. Exempt them from your own .rubocop.yml, not from here: a path in
|
|
30
|
+
# an inherited gem config resolves against the gem directory, so "test/**/*"
|
|
31
|
+
# written here would name this gem's tests and never yours.
|
|
32
|
+
#
|
|
33
|
+
# Metrics/MethodLength:
|
|
34
|
+
# Exclude:
|
|
35
|
+
# - test/**/*
|
|
36
|
+
# - spec/**/*
|
|
37
|
+
|
|
38
|
+
Metrics/MethodLength:
|
|
39
|
+
# Sandi Metz rule two: a method says one thing in five lines or fewer.
|
|
40
|
+
Max: 5
|
|
41
|
+
|
|
42
|
+
Metrics/ClassLength:
|
|
43
|
+
# Sandi Metz rule one: a class stays under a hundred lines.
|
|
44
|
+
Max: 100
|
|
45
|
+
|
|
46
|
+
Metrics/ParameterLists:
|
|
47
|
+
# Sandi Metz rule three: pass no more than four parameters.
|
|
48
|
+
Max: 4
|
|
49
|
+
|
|
50
|
+
Style/Documentation:
|
|
51
|
+
# Library users should choose their own documentation style and coverage.
|
|
52
|
+
Enabled: false
|
|
53
|
+
|
|
54
|
+
Metrics/BlockLength:
|
|
55
|
+
# Test cases and DSL blocks get noisy fast, so block length stays opt-in.
|
|
56
|
+
Enabled: false
|
|
57
|
+
|
|
58
|
+
QualityGate/ControllerInstanceVariables:
|
|
59
|
+
# Sandi Metz rule four: a controller action instantiates one object.
|
|
60
|
+
Enabled: true
|
data/config/ruby.yml
ADDED
data/docs/codex.md
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Codex and QualityGate
|
|
2
|
+
|
|
3
|
+
Codex does not consume Claude Code `PostToolUse` or `Stop` hooks. The optional `.claude/settings.json`, `.claude/hooks/quality_gate_fast.rb`, and `.claude/hooks/quality_gate_verify_stop.rb` files are for Claude Code sessions and are installed only with `--agents`. In Codex, run the same gates manually or have the agent invoke them explicitly:
|
|
4
|
+
|
|
5
|
+
- Run `bundle exec quality_gate fast` after Ruby edits.
|
|
6
|
+
- Run `bundle exec quality_gate verify` before you finish a change.
|
|
7
|
+
- Run `bundle exec quality_gate audit` before you merge security-sensitive work.
|
|
8
|
+
|
|
9
|
+
Fix findings before you continue editing. Use `--format json` for structured results, and inspect `checks` alongside findings to see each tool's status and scope. Explicit selected paths must exist; a missing path is an input failure. A clean gate describes only its configured checks, not overall application correctness.
|
|
10
|
+
|
|
11
|
+
Automatic Claude hooks can fail open or cap retries. An agent session ending is not proof of verification. CI should run the gate commands directly and enforce their exit status.
|
|
12
|
+
|
|
13
|
+
Exit meanings stay the same in Codex and Claude Code:
|
|
14
|
+
|
|
15
|
+
- Exit 0 means the gate ran clean.
|
|
16
|
+
- Exit 1 means the gate reported findings and no tool failures.
|
|
17
|
+
- Exit 2 means the gate could not complete cleanly because of invalid input, configuration, or another tool failure.
|
|
18
|
+
|
|
19
|
+
If the automatic Claude Code hook becomes unavailable, inspect `log/quality_gate_hooks.jsonl`, run `bundle install`, and reinstall the generated integration with the setup command for your project.
|
|
20
|
+
|
|
21
|
+
For a plain Ruby project, install or refresh the generated contract files with:
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
bundle exec quality_gate init --profile ruby --agents
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
For Rails:
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
bin/rails generate quality_gate:install --agents
|
|
31
|
+
```
|
data/docs/dogfood-log.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Validation guide
|
|
2
|
+
|
|
3
|
+
The public acceptance fixtures exercise four representative incident classes with
|
|
4
|
+
shipped defaults: N+1 queries, unsafe migrations, complexity creep, and untested
|
|
5
|
+
changed code. The [incident catalogue](incidents.md) maps each class to its fixture,
|
|
6
|
+
command, and expected finding. These fixtures test detection behavior; they do not
|
|
7
|
+
establish compatibility with every application or dependency version.
|
|
8
|
+
|
|
9
|
+
## Reproduce the checks
|
|
10
|
+
|
|
11
|
+
After installing development dependencies, run the executable incident contracts:
|
|
12
|
+
|
|
13
|
+
```sh
|
|
14
|
+
bundle exec ruby -Itest test/acceptance/incident_catalog_test.rb
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Run the complete test suite and aggregate coverage checks with:
|
|
18
|
+
|
|
19
|
+
```sh
|
|
20
|
+
COVERAGE=1 bundle exec rake test
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Latency checks are opt-in because load and runtime configuration affect results:
|
|
24
|
+
|
|
25
|
+
```sh
|
|
26
|
+
QUALITY_GATE_ACCEPTANCE_TIMING=1 bundle exec ruby -Itest test/acceptance/latency_test.rb
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The fixture budgets are below 2 seconds for a warm file-scoped `fast` run and below
|
|
30
|
+
3 seconds for the installed hook round trip. These are test targets, not measured
|
|
31
|
+
release guarantees. A skipped timing check provides no latency evidence.
|
|
32
|
+
|
|
33
|
+
## Interpretation and limits
|
|
34
|
+
|
|
35
|
+
A clean gate means its configured tools completed without reportable findings.
|
|
36
|
+
A timeout, unavailable tool, skipped comparison, or unsupported dependency is not
|
|
37
|
+
proof of clean application behavior. Inspect findings and warnings alongside exit
|
|
38
|
+
status. The Stop hook can fail open when verification is unavailable; that outcome
|
|
39
|
+
must not be counted as successful verification.
|
|
40
|
+
|
|
41
|
+
Application initialization and third-party compatibility can prevent installation
|
|
42
|
+
or checks from completing. Validate the generator, optional initializers, and all
|
|
43
|
+
configured gates in the target application. Fixture results do not guarantee
|
|
44
|
+
production compatibility, performance, reduced review time, or agent adoption.
|
|
45
|
+
|
|
46
|
+
The public release includes reproducible fixture contracts rather than private
|
|
47
|
+
application records. No current production cohort measurements are claimed.
|
data/docs/incidents.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Incident catalogue
|
|
2
|
+
|
|
3
|
+
These four executable examples preserve the defect classes covered by the acceptance tests.
|
|
4
|
+
Each contract names the gate, detector, rule family, and message evidence that must catch the defect.
|
|
5
|
+
|
|
6
|
+
| id | class | source | example | command | tool | rule | cause |
|
|
7
|
+
|---|---|---|---|---|---|---|---|
|
|
8
|
+
| n_plus_one | N+1 query | [Validation guide](dogfood-log.md#reproduce-the-checks) | test/fixtures/acceptance/n_plus_one | verify | test_suite | test_failure | USE eager loading |
|
|
9
|
+
| unsafe_migration | Unsafe migration | [Validation guide](dogfood-log.md#reproduce-the-checks) | test/fixtures/acceptance/unsafe_migration | verify | test_suite | test_failure | Dangerous operation detected #strong_migrations |
|
|
10
|
+
| complexity_creep | Complexity creep | [Validation guide](dogfood-log.md#reproduce-the-checks) | test/fixtures/acceptance/complexity_creep | fast | rubocop | Metrics/* | emitted metric name |
|
|
11
|
+
| untested_change | Untested changed code | [Validation guide](dogfood-log.md#reproduce-the-checks) | test/fixtures/acceptance/untested_change | verify | undercover | uncovered_code | changed line range |
|
|
12
|
+
|
|
13
|
+
## Capture the next escaped class
|
|
14
|
+
|
|
15
|
+
A new escaped class is accepted only after its written source is recorded, a minimal fixture reproduces
|
|
16
|
+
the defect with shipped defaults, and a failing expected-finding assertion names its tool, rule, and
|
|
17
|
+
cause signature. Add the catalogue row only with those three pieces so documentation and execution
|
|
18
|
+
cannot drift apart.
|
data/docs/releasing.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# Releasing Quality Gate
|
|
2
|
+
|
|
3
|
+
Use Ruby 3.2 or newer and run these commands from the gem directory.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
bin/setup
|
|
7
|
+
bin/prepare_release
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
`bin/setup` installs the development bundle. `bin/prepare_release` runs the test
|
|
11
|
+
suite and RuboCop, regenerates `llm.txt`, and builds
|
|
12
|
+
`pkg/quality_gate-VERSION.gem`. It stops at the first failed prerequisite.
|
|
13
|
+
Preparation only creates local files; publishing is a separate maintainer action.
|
|
14
|
+
|
|
15
|
+
## Documentation
|
|
16
|
+
|
|
17
|
+
`bin/generate_llm.rb` produces a deterministic index and short overview using an
|
|
18
|
+
explicit list of public Markdown documents. It checks every linked document exists
|
|
19
|
+
before replacing `llm.txt`. Run it after changing the documentation set and review
|
|
20
|
+
the generated output. It requires only Ruby's standard library.
|
|
21
|
+
|
|
22
|
+
## Executables
|
|
23
|
+
|
|
24
|
+
The installed command lives in `exe/quality_gate`. The gemspec exposes it as
|
|
25
|
+
`quality_gate`. Scripts in `bin/` are development tools and are excluded from the
|
|
26
|
+
gem archive. `bin/console` opens an interactive session with the gem loaded.
|
|
27
|
+
|
|
28
|
+
## Release checklist
|
|
29
|
+
|
|
30
|
+
1. Update `lib/quality_gate/version.rb` and `CHANGELOG.md` for the intended release.
|
|
31
|
+
2. Run `bin/prepare_release` and review the generated documentation and package.
|
|
32
|
+
Run `bundle exec quality_gate verify` to enforce the repository coverage budgets.
|
|
33
|
+
On the initial commit, Undercover cannot compare against a parent commit, so
|
|
34
|
+
local verification reports a skip and exits nonzero. CI accepts that skip only
|
|
35
|
+
on the root commit, with no other findings and clean test suite and SimpleCov
|
|
36
|
+
checks enforcing the configured aggregate line and branch coverage budgets. The
|
|
37
|
+
repository configuration remains unchanged during verification. Subsequent commits
|
|
38
|
+
run normal verification, including diff coverage; no extra baseline commit is required.
|
|
39
|
+
3. Confirm the archive includes runtime code, configuration, licensing, and public
|
|
40
|
+
documentation, with no local paths, credentials, private logs, or internal plans.
|
|
41
|
+
4. Commit the reviewed release in the public repository and tag the version.
|
|
42
|
+
5. Publish the reviewed artifact with `gem push pkg/quality_gate-VERSION.gem`,
|
|
43
|
+
replacing `VERSION` with the actual release version. RubyGems MFA is required.
|
|
44
|
+
|
|
45
|
+
The Rakefile also loads Bundler's gem tasks. Its `release` task can publish and
|
|
46
|
+
push Git changes; use it only when those actions are intended.
|
data/exe/quality_gate
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../../quality_gate/installation"
|
|
4
|
+
|
|
5
|
+
if defined?(Rails::Generators::Base)
|
|
6
|
+
module QualityGate
|
|
7
|
+
# Defines the ordered command shell for installing Quality Gate into a Rails application.
|
|
8
|
+
class InstallGenerator < Rails::Generators::Base
|
|
9
|
+
include Installation
|
|
10
|
+
|
|
11
|
+
FILES = Installation::FILES
|
|
12
|
+
MARKER_START = Installation::MARKER_START
|
|
13
|
+
MARKER_END = Installation::MARKER_END
|
|
14
|
+
AGENT_TEMPLATE_PATHS = Installation.const_get(:AGENT_TEMPLATE_PATHS, false)
|
|
15
|
+
AGENT_CONTRACT_PATHS = Installation.const_get(:AGENT_CONTRACT_PATHS, false)
|
|
16
|
+
PRIOR_CLAUDE_SETTINGS = Installation.const_get(:PRIOR_CLAUDE_SETTINGS, false)
|
|
17
|
+
STRONG_MIGRATIONS_PROVENANCE = Installation.const_get(:STRONG_MIGRATIONS_PROVENANCE, false)
|
|
18
|
+
HOOK_MODE = Installation.const_get(:HOOK_MODE, false)
|
|
19
|
+
DEFAULT_AGENT_ARTIFACT_MODE = Installation.const_get(:DEFAULT_AGENT_ARTIFACT_MODE, false)
|
|
20
|
+
TEMPFILE_MODE = Installation.const_get(:TEMPFILE_MODE, false)
|
|
21
|
+
AGENT_CONTRACT_START = Installation.const_get(:AGENT_CONTRACT_START, false)
|
|
22
|
+
AGENT_CONTRACT_END = Installation.const_get(:AGENT_CONTRACT_END, false)
|
|
23
|
+
DirectoryOperations = Installation.const_get(:DirectoryOperations, false)
|
|
24
|
+
|
|
25
|
+
private_constant :AGENT_TEMPLATE_PATHS, :AGENT_CONTRACT_PATHS
|
|
26
|
+
private_constant :PRIOR_CLAUDE_SETTINGS
|
|
27
|
+
private_constant :STRONG_MIGRATIONS_PROVENANCE, :HOOK_MODE, :DEFAULT_AGENT_ARTIFACT_MODE, :TEMPFILE_MODE
|
|
28
|
+
private_constant :AGENT_CONTRACT_START, :AGENT_CONTRACT_END, :DirectoryOperations
|
|
29
|
+
|
|
30
|
+
source_root File.expand_path("templates", __dir__)
|
|
31
|
+
|
|
32
|
+
class_option :skip_initializers, type: :boolean, default: false
|
|
33
|
+
class_option :skip_coverage, type: :boolean, default: false
|
|
34
|
+
class_option :agents, type: :boolean, default: false
|
|
35
|
+
|
|
36
|
+
# Rails discovers generator tasks from methods declared on this class.
|
|
37
|
+
# Keep these forwarding methods so the shared module remains framework-neutral.
|
|
38
|
+
# rubocop:disable Lint/UselessMethodDefinition
|
|
39
|
+
def create_settings_file = super
|
|
40
|
+
def create_rules_file = super
|
|
41
|
+
def create_initializers = super
|
|
42
|
+
def inject_coverage = super
|
|
43
|
+
def create_agent_integration = super
|
|
44
|
+
def print_summary = super
|
|
45
|
+
# rubocop:enable Lint/UselessMethodDefinition
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def create_new_file(relative_path, content)
|
|
50
|
+
create_file(relative_path, content, verbose: false)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def template_source_root
|
|
54
|
+
self.class.source_root
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def template_for(name)
|
|
58
|
+
name
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def test_helper_path
|
|
62
|
+
"test/test_helper.rb"
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
<!-- quality_gate agent contract — start -->
|
|
2
|
+
## Quality Gate agent contract
|
|
3
|
+
|
|
4
|
+
Run `bundle exec quality_gate fast` after Ruby edits. Installed Claude Code hooks can run `bundle exec quality_gate verify` automatically at session end; other clients, including Codex, must run `bundle exec quality_gate verify` manually before you finish a change. Run `bundle exec quality_gate audit` before you merge security-sensitive work.
|
|
5
|
+
|
|
6
|
+
Fix findings before you continue editing.
|
|
7
|
+
|
|
8
|
+
- Exit 0 means the gate ran clean.
|
|
9
|
+
- Exit 1 means the gate reported findings and no tool failures.
|
|
10
|
+
- Exit 2 means the gate could not complete cleanly because of invalid input, configuration, or another tool failure.
|
|
11
|
+
|
|
12
|
+
The default fast path keeps feedback tight with a 10-second RuboCop timeout. Verify and audit keep the broader suite work on the 120-second default timeout.
|
|
13
|
+
|
|
14
|
+
If the automatic hook becomes unavailable, inspect `log/quality_gate_hooks.jsonl`, run `bundle install`, and reinstall the hook with `bin/rails generate quality_gate:install --agents`.
|
|
15
|
+
<!-- quality_gate agent contract — end -->
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'bullet'
|
|
4
|
+
|
|
5
|
+
Rails.application.config.after_initialize do
|
|
6
|
+
Bullet.enable = Rails.env.test? || Rails.env.development?
|
|
7
|
+
Bullet.raise = Rails.env.test?
|
|
8
|
+
Bullet.bullet_logger = Rails.env.development?
|
|
9
|
+
Bullet.rails_logger = Rails.env.development?
|
|
10
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"PostToolUse": [
|
|
4
|
+
{
|
|
5
|
+
"matcher": "Edit|Write",
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/quality_gate_fast.rb",
|
|
10
|
+
"args": [],
|
|
11
|
+
"timeout": 30
|
|
12
|
+
}
|
|
13
|
+
]
|
|
14
|
+
}
|
|
15
|
+
],
|
|
16
|
+
"Stop": [
|
|
17
|
+
{
|
|
18
|
+
"hooks": [
|
|
19
|
+
{
|
|
20
|
+
"type": "command",
|
|
21
|
+
"command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/quality_gate_verify_stop.rb",
|
|
22
|
+
"args": [],
|
|
23
|
+
"timeout": 300
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Opens fixed hook-log paths relative to already-open directory descriptors.
|
|
2
|
+
module HookLogFilesystem
|
|
3
|
+
extend self
|
|
4
|
+
|
|
5
|
+
O_DIRECTORY = File.const_defined?(:DIRECTORY) ? File::DIRECTORY : 0
|
|
6
|
+
O_NONBLOCK = File.const_defined?(:NONBLOCK) ? File::NONBLOCK : 0
|
|
7
|
+
|
|
8
|
+
def open_file(directory, name, flags, mode = 0)
|
|
9
|
+
fd = syscall(:openat, "int openat(int, const char*, int, int)", directory.fileno, name, flags, mode)
|
|
10
|
+
File.for_fd(fd).tap do |io|
|
|
11
|
+
io.close_on_exec = true if io.respond_to?(:close_on_exec=)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def open_directory(directory, name)
|
|
16
|
+
open_file(directory, name, File::RDONLY | File::NOFOLLOW | O_NONBLOCK | O_DIRECTORY)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def open_log_directory(directory)
|
|
20
|
+
open_directory(directory, "log")
|
|
21
|
+
rescue Errno::ENOENT
|
|
22
|
+
create_log_directory(directory)
|
|
23
|
+
open_directory(directory, "log")
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def open_existing_log_directory(directory)
|
|
27
|
+
open_directory(directory, "log")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def open_log_file_for_read(directory)
|
|
31
|
+
flags = File::RDONLY | File::NOFOLLOW | O_NONBLOCK
|
|
32
|
+
open_file(directory, "quality_gate_hooks.jsonl", flags)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def open_log_file(directory)
|
|
36
|
+
open_existing_log_file(directory)
|
|
37
|
+
rescue Errno::ENOENT
|
|
38
|
+
begin
|
|
39
|
+
flags = log_file_flags | File::CREAT | File::EXCL
|
|
40
|
+
[open_file(directory, "quality_gate_hooks.jsonl", flags, 0o644), true]
|
|
41
|
+
rescue Errno::EEXIST
|
|
42
|
+
open_existing_log_file(directory)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def create_log_directory(directory)
|
|
47
|
+
syscall(:mkdirat, "int mkdirat(int, const char*, unsigned short)", directory.fileno, "log", 0o755)
|
|
48
|
+
rescue Errno::EEXIST
|
|
49
|
+
nil
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def open_existing_log_file(directory)
|
|
55
|
+
[open_file(directory, "quality_gate_hooks.jsonl", log_file_flags), false]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def log_file_flags
|
|
59
|
+
File::WRONLY | File::APPEND | File::NOFOLLOW | O_NONBLOCK
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def syscall(function_name, signature, *arguments)
|
|
63
|
+
importer.extern(signature) unless importer.respond_to?(function_name)
|
|
64
|
+
result = importer.public_send(function_name, *arguments)
|
|
65
|
+
return result unless result == -1
|
|
66
|
+
|
|
67
|
+
fail SystemCallError.new(function_name.to_s, Fiddle.last_error)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def importer
|
|
71
|
+
@importer ||= Module.new.tap do |mod|
|
|
72
|
+
mod.extend(Fiddle::Importer)
|
|
73
|
+
mod.dlload(Fiddle.dlopen(nil))
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# # Output format: "text" or "json".
|
|
2
|
+
# format: text
|
|
3
|
+
#
|
|
4
|
+
# # Optional file paths passed to adapters; empty means no configured restriction.
|
|
5
|
+
# files: []
|
|
6
|
+
#
|
|
7
|
+
# # Adapters run by each gate, in order.
|
|
8
|
+
# adapters:
|
|
9
|
+
# fast:
|
|
10
|
+
# - rubocop
|
|
11
|
+
# verify:
|
|
12
|
+
# - reek
|
|
13
|
+
# - test_suite
|
|
14
|
+
# - undercover
|
|
15
|
+
# audit:
|
|
16
|
+
# - brakeman
|
|
17
|
+
# - bundler_audit
|
|
18
|
+
#
|
|
19
|
+
# # Command overrides by gate. Empty mappings use adapter defaults.
|
|
20
|
+
# commands:
|
|
21
|
+
# fast: {}
|
|
22
|
+
# verify:
|
|
23
|
+
# test_suite:
|
|
24
|
+
# - bin/rails
|
|
25
|
+
# - test
|
|
26
|
+
# audit: {}
|
|
27
|
+
#
|
|
28
|
+
# # Command timeouts in seconds. Adapter-specific values override the default.
|
|
29
|
+
# timeouts:
|
|
30
|
+
# default: 120
|
|
31
|
+
# rubocop: 10
|
|
32
|
+
# test_suite: 120
|
|
33
|
+
# undercover: 120
|
|
34
|
+
#
|
|
35
|
+
# # Aggregate SimpleCov budgets. Leave the entire mapping commented to disable coverage budgeting.
|
|
36
|
+
# coverage:
|
|
37
|
+
#
|
|
38
|
+
# # Git ref or commit for Undercover; blank selects the comparison point automatically.
|
|
39
|
+
# compare_point:
|
|
40
|
+
#
|
|
41
|
+
# # Explicit RuboCop config path; blank uses host config when present, otherwise the shipped config.
|
|
42
|
+
# rubocop_config:
|