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.
Files changed (62) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +14 -0
  3. data/LICENSE.txt +21 -0
  4. data/README.md +571 -0
  5. data/config/37signals.yml +26 -0
  6. data/config/cops.yml +37 -0
  7. data/config/reek.yml +6 -0
  8. data/config/rubocop.yml +60 -0
  9. data/config/ruby.yml +8 -0
  10. data/docs/codex.md +31 -0
  11. data/docs/dogfood-log.md +47 -0
  12. data/docs/incidents.md +18 -0
  13. data/docs/releasing.md +46 -0
  14. data/exe/quality_gate +6 -0
  15. data/lib/generators/quality_gate/install/install_generator.rb +66 -0
  16. data/lib/generators/quality_gate/install/templates/agents_section.md.tt +15 -0
  17. data/lib/generators/quality_gate/install/templates/bullet.rb.tt +10 -0
  18. data/lib/generators/quality_gate/install/templates/claude_settings.json.tt +29 -0
  19. data/lib/generators/quality_gate/install/templates/hook_log_filesystem.rb.tt +76 -0
  20. data/lib/generators/quality_gate/install/templates/quality_gate.yml.tt +42 -0
  21. data/lib/generators/quality_gate/install/templates/quality_gate_fast.rb.tt +248 -0
  22. data/lib/generators/quality_gate/install/templates/quality_gate_verify_stop.rb.tt +466 -0
  23. data/lib/generators/quality_gate/install/templates/rubocop.yml.tt +1 -0
  24. data/lib/generators/quality_gate/install/templates/ruby_agents_section.md.tt +15 -0
  25. data/lib/generators/quality_gate/install/templates/ruby_quality_gate.yml.tt +40 -0
  26. data/lib/generators/quality_gate/install/templates/ruby_rubocop.yml.tt +1 -0
  27. data/lib/generators/quality_gate/install/templates/ruby_simplecov.rb.tt +11 -0
  28. data/lib/generators/quality_gate/install/templates/simplecov.rb.tt +10 -0
  29. data/lib/generators/quality_gate/install/templates/strong_migrations.rb.tt +10 -0
  30. data/lib/quality_gate/adapter.rb +328 -0
  31. data/lib/quality_gate/adapters/brakeman.rb +125 -0
  32. data/lib/quality_gate/adapters/bundler_audit.rb +235 -0
  33. data/lib/quality_gate/adapters/reek.rb +108 -0
  34. data/lib/quality_gate/adapters/rubocop.rb +142 -0
  35. data/lib/quality_gate/adapters/simplecov.rb +103 -0
  36. data/lib/quality_gate/adapters/test_suite.rb +81 -0
  37. data/lib/quality_gate/adapters/undercover.rb +357 -0
  38. data/lib/quality_gate/cli.rb +451 -0
  39. data/lib/quality_gate/config.rb +290 -0
  40. data/lib/quality_gate/exit_code.rb +14 -0
  41. data/lib/quality_gate/finding.rb +50 -0
  42. data/lib/quality_gate/hook_log.rb +131 -0
  43. data/lib/quality_gate/init_command.rb +90 -0
  44. data/lib/quality_gate/installation.rb +1577 -0
  45. data/lib/quality_gate/installer.rb +104 -0
  46. data/lib/quality_gate/railtie.rb +16 -0
  47. data/lib/quality_gate/reporters/field_sanitizer.rb +42 -0
  48. data/lib/quality_gate/reporters/json.rb +59 -0
  49. data/lib/quality_gate/reporters/text.rb +45 -0
  50. data/lib/quality_gate/rubocop.rb +34 -0
  51. data/lib/quality_gate/ruby_profile.rb +170 -0
  52. data/lib/quality_gate/runner.rb +150 -0
  53. data/lib/quality_gate/version.rb +5 -0
  54. data/lib/quality_gate.rb +27 -0
  55. data/lib/rubocop/cop/quality_gate/association_default_block_value.rb +46 -0
  56. data/lib/rubocop/cop/quality_gate/broadcast_in_controller.rb +64 -0
  57. data/lib/rubocop/cop/quality_gate/controller_instance_variables.rb +47 -0
  58. data/lib/rubocop/cop/quality_gate/prefer_after_save_commit.rb +84 -0
  59. data/lib/rubocop/cop/quality_gate/private_only_concern.rb +77 -0
  60. data/llm.txt +13 -0
  61. data/sig/quality_gate.rbs +226 -0
  62. 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
@@ -0,0 +1,6 @@
1
+ # Default Reek configuration shipped with quality_gate.
2
+ # Used only when the host project has no .reek.yml of its own.
3
+ detectors:
4
+ # Demanding a comment on every class pushes agents toward noise comments.
5
+ IrresponsibleModule:
6
+ enabled: false
@@ -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
@@ -0,0 +1,8 @@
1
+ inherit_from: rubocop.yml
2
+
3
+ # Plain Ruby projects do not load Rails or Rails-specific cops.
4
+ Rails:
5
+ Enabled: false
6
+
7
+ QualityGate/ControllerInstanceVariables:
8
+ Enabled: false
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
+ ```
@@ -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,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "quality_gate"
5
+
6
+ exit QualityGate::CLI.run(ARGV, dir: Dir.pwd)
@@ -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: