aireview 0.3.0 → 2.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 +4 -4
- data/CHANGELOG.md +82 -0
- data/README.md +301 -29
- data/config/defaults.yml +47 -0
- data/lib/aireview/candidate_checker.rb +24 -22
- data/lib/aireview/cli.rb +79 -19
- data/lib/aireview/config.rb +65 -180
- data/lib/aireview/config_fallbacks.rb +79 -68
- data/lib/aireview/config_layers.rb +110 -0
- data/lib/aireview/config_limits.rb +10 -35
- data/lib/aireview/config_loader.rb +240 -0
- data/lib/aireview/context_budget.rb +22 -20
- data/lib/aireview/context_builder.rb +18 -17
- data/lib/aireview/diff_fetcher.rb +12 -11
- data/lib/aireview/dry_run_report.rb +33 -8
- data/lib/aireview/errors.rb +8 -0
- data/lib/aireview/gitlab_client.rb +10 -9
- data/lib/aireview/llm_client.rb +146 -0
- data/lib/aireview/llm_failure.rb +36 -19
- data/lib/aireview/llm_router.rb +315 -158
- data/lib/aireview/model_candidate.rb +28 -0
- data/lib/aireview/model_checker.rb +148 -0
- data/lib/aireview/model_pool.rb +224 -0
- data/lib/aireview/model_state.rb +82 -0
- data/lib/aireview/output_schemas.rb +3 -3
- data/lib/aireview/publisher.rb +6 -6
- data/lib/aireview/result_parser.rb +98 -0
- data/lib/aireview/review_marker.rb +16 -28
- data/lib/aireview/review_pipeline.rb +102 -85
- data/lib/aireview/review_renderer.rb +19 -11
- data/lib/aireview/reviewer.rb +44 -107
- data/lib/aireview/stage_chains.rb +113 -0
- data/lib/aireview/stages.rb +7 -0
- data/lib/aireview/utils.rb +29 -0
- data/lib/aireview/version.rb +1 -1
- data/lib/aireview.rb +1 -0
- metadata +21 -5
- data/lib/aireview/result_validation.rb +0 -65
data/lib/aireview/utils.rb
CHANGED
|
@@ -14,5 +14,34 @@ module Aireview
|
|
|
14
14
|
def presence(value)
|
|
15
15
|
present?(value) ? value.to_s.strip : nil
|
|
16
16
|
end
|
|
17
|
+
|
|
18
|
+
# Merges settings by key: nested hashes merge, everything else (arrays
|
|
19
|
+
# included) is replaced by the right side as a whole.
|
|
20
|
+
def deep_merge(left, right)
|
|
21
|
+
left.merge(right) do |_, old_value, new_value|
|
|
22
|
+
if old_value.is_a?(Hash) && new_value.is_a?(Hash)
|
|
23
|
+
deep_merge(old_value, new_value)
|
|
24
|
+
else
|
|
25
|
+
new_value
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Hash keys are strings at any depth: YAML, env and LLM answers arrive in
|
|
31
|
+
# different shapes, everything downstream works with one.
|
|
32
|
+
def normalize_hash(value)
|
|
33
|
+
case value
|
|
34
|
+
when Hash
|
|
35
|
+
value.each_with_object({}) { |(key, inner), result| result[key.to_s] = normalize_hash(inner) }
|
|
36
|
+
when Array
|
|
37
|
+
value.map { |item| normalize_hash(item) }
|
|
38
|
+
else
|
|
39
|
+
value
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def dig(data, *keys)
|
|
44
|
+
keys.reduce(data) { |accumulator, key| accumulator.is_a?(Hash) ? accumulator[key] : nil }
|
|
45
|
+
end
|
|
17
46
|
end
|
|
18
47
|
end
|
data/lib/aireview/version.rb
CHANGED
data/lib/aireview.rb
CHANGED
|
@@ -16,6 +16,7 @@ require_relative 'aireview/secret_scrubber'
|
|
|
16
16
|
require_relative 'aireview/context_builder'
|
|
17
17
|
require_relative 'aireview/reviewer'
|
|
18
18
|
require_relative 'aireview/review_pipeline'
|
|
19
|
+
require_relative 'aireview/model_checker'
|
|
19
20
|
require_relative 'aireview/gitlab_client'
|
|
20
21
|
require_relative 'aireview/jira_client'
|
|
21
22
|
require_relative 'aireview/review_marker'
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: aireview
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 2.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Denis Levenko
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-09-
|
|
11
|
+
date: 2026-09-25 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: dotenv
|
|
@@ -31,6 +31,9 @@ dependencies:
|
|
|
31
31
|
- - "~>"
|
|
32
32
|
- !ruby/object:Gem::Version
|
|
33
33
|
version: '2.14'
|
|
34
|
+
- - ">="
|
|
35
|
+
- !ruby/object:Gem::Version
|
|
36
|
+
version: 2.14.4
|
|
34
37
|
type: :runtime
|
|
35
38
|
prerelease: false
|
|
36
39
|
version_requirements: !ruby/object:Gem::Requirement
|
|
@@ -38,20 +41,23 @@ dependencies:
|
|
|
38
41
|
- - "~>"
|
|
39
42
|
- !ruby/object:Gem::Version
|
|
40
43
|
version: '2.14'
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: 2.14.4
|
|
41
47
|
- !ruby/object:Gem::Dependency
|
|
42
48
|
name: ruby_llm
|
|
43
49
|
requirement: !ruby/object:Gem::Requirement
|
|
44
50
|
requirements:
|
|
45
51
|
- - '='
|
|
46
52
|
- !ruby/object:Gem::Version
|
|
47
|
-
version:
|
|
53
|
+
version: 2.0.0
|
|
48
54
|
type: :runtime
|
|
49
55
|
prerelease: false
|
|
50
56
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
57
|
requirements:
|
|
52
58
|
- - '='
|
|
53
59
|
- !ruby/object:Gem::Version
|
|
54
|
-
version:
|
|
60
|
+
version: 2.0.0
|
|
55
61
|
description: 'Reviews self-hosted GitLab merge requests with a two-pass LLM pipeline:
|
|
56
62
|
the first pass finds candidate findings, the second one critiques them and drops
|
|
57
63
|
the weak ones. Supports Gemini and local Ollama, optional Jira context and posting
|
|
@@ -68,12 +74,15 @@ files:
|
|
|
68
74
|
- README.md
|
|
69
75
|
- bin/aireview
|
|
70
76
|
- config/.aireview.yml.example
|
|
77
|
+
- config/defaults.yml
|
|
71
78
|
- lib/aireview.rb
|
|
72
79
|
- lib/aireview/candidate_checker.rb
|
|
73
80
|
- lib/aireview/cli.rb
|
|
74
81
|
- lib/aireview/config.rb
|
|
75
82
|
- lib/aireview/config_fallbacks.rb
|
|
83
|
+
- lib/aireview/config_layers.rb
|
|
76
84
|
- lib/aireview/config_limits.rb
|
|
85
|
+
- lib/aireview/config_loader.rb
|
|
77
86
|
- lib/aireview/context_budget.rb
|
|
78
87
|
- lib/aireview/context_builder.rb
|
|
79
88
|
- lib/aireview/diff_fetcher.rb
|
|
@@ -81,20 +90,27 @@ files:
|
|
|
81
90
|
- lib/aireview/errors.rb
|
|
82
91
|
- lib/aireview/gitlab_client.rb
|
|
83
92
|
- lib/aireview/jira_client.rb
|
|
93
|
+
- lib/aireview/llm_client.rb
|
|
84
94
|
- lib/aireview/llm_failure.rb
|
|
85
95
|
- lib/aireview/llm_router.rb
|
|
96
|
+
- lib/aireview/model_candidate.rb
|
|
97
|
+
- lib/aireview/model_checker.rb
|
|
98
|
+
- lib/aireview/model_pool.rb
|
|
99
|
+
- lib/aireview/model_state.rb
|
|
86
100
|
- lib/aireview/mr_parser.rb
|
|
87
101
|
- lib/aireview/output_schemas.rb
|
|
88
102
|
- lib/aireview/prompts/critique.txt
|
|
89
103
|
- lib/aireview/prompts/generate.txt
|
|
90
104
|
- lib/aireview/publisher.rb
|
|
91
|
-
- lib/aireview/
|
|
105
|
+
- lib/aireview/result_parser.rb
|
|
92
106
|
- lib/aireview/review_marker.rb
|
|
93
107
|
- lib/aireview/review_pipeline.rb
|
|
94
108
|
- lib/aireview/review_renderer.rb
|
|
95
109
|
- lib/aireview/review_schemas.rb
|
|
96
110
|
- lib/aireview/reviewer.rb
|
|
97
111
|
- lib/aireview/secret_scrubber.rb
|
|
112
|
+
- lib/aireview/stage_chains.rb
|
|
113
|
+
- lib/aireview/stages.rb
|
|
98
114
|
- lib/aireview/utils.rb
|
|
99
115
|
- lib/aireview/version.rb
|
|
100
116
|
homepage: https://github.com/DenisDenis9331/aireview
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Aireview
|
|
4
|
-
# Проверка формы ответов LLM после разбора JSON: кандидаты, вердикты, id.
|
|
5
|
-
# Ошибка формы — SchemaError, пайплайн решает, чинить ли ответ повторным
|
|
6
|
-
# запросом. Ожидает от включающего класса value, normalize_id и
|
|
7
|
-
# normalize_decision.
|
|
8
|
-
module ResultValidation
|
|
9
|
-
class SchemaError < StandardError
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
private
|
|
13
|
-
|
|
14
|
-
def validate_generate_result_shape!(parsed)
|
|
15
|
-
valid_shape = parsed.is_a?(Hash) && parsed['candidates'].is_a?(Array)
|
|
16
|
-
raise SchemaError, 'expected an object with summary and candidates array' unless valid_shape
|
|
17
|
-
raise SchemaError, 'each generate candidate must be an object' unless parsed['candidates'].all?(Hash)
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def validate_critique_result_shape!(parsed)
|
|
21
|
-
valid_shape = parsed.is_a?(Hash) && parsed['verdicts'].is_a?(Array)
|
|
22
|
-
raise SchemaError, 'expected an object with verdicts array' unless valid_shape
|
|
23
|
-
raise SchemaError, 'each critique verdict must be an object' unless parsed['verdicts'].all?(Hash)
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def validate_identifiers!(identifiers, missing_message:, duplicate_prefix:)
|
|
27
|
-
raise SchemaError, missing_message unless identifiers.all?
|
|
28
|
-
|
|
29
|
-
duplicate_ids = identifiers.group_by(&:itself).select { |_, ids| ids.size > 1 }.keys
|
|
30
|
-
return if duplicate_ids.empty?
|
|
31
|
-
|
|
32
|
-
raise SchemaError, "#{duplicate_prefix}: #{duplicate_ids.join(', ')}"
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def validate_expected_verdict_ids!(verdict_ids, expected_ids)
|
|
36
|
-
return unless expected_ids
|
|
37
|
-
|
|
38
|
-
unknown_ids = verdict_ids - expected_ids
|
|
39
|
-
missing_ids = expected_ids - verdict_ids
|
|
40
|
-
raise SchemaError, "unknown verdict ids: #{unknown_ids.join(', ')}" unless unknown_ids.empty?
|
|
41
|
-
raise SchemaError, "missing verdict ids: #{missing_ids.join(', ')}" unless missing_ids.empty?
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def validate_verdict!(verdict)
|
|
45
|
-
id = normalize_id(value(verdict, 'id'))
|
|
46
|
-
decision = normalize_decision(value(verdict, 'decision'))
|
|
47
|
-
raise SchemaError, "invalid verdict decision for #{id}" unless %w[keep reject].include?(decision)
|
|
48
|
-
|
|
49
|
-
refinement = value(verdict, 'refinement')
|
|
50
|
-
raise SchemaError, "reject verdict cannot include refinement for #{id}" if invalid_refinement?(verdict, decision)
|
|
51
|
-
return if refinement.nil?
|
|
52
|
-
return if refinement.is_a?(Hash)
|
|
53
|
-
|
|
54
|
-
raise SchemaError, "refinement must be an object for #{id}"
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def invalid_refinement?(verdict, decision)
|
|
58
|
-
refinement_key?(verdict) && decision != 'keep'
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
def refinement_key?(verdict)
|
|
62
|
-
verdict.key?('refinement') || verdict.key?(:refinement)
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
end
|