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.
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aireview
4
+ # The two review stages: Generate finds the findings, Critique checks them.
5
+ # The single definition; inside the code a stage is a string.
6
+ STAGES = %w[generate critique].freeze
7
+ end
@@ -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
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module Aireview
3
- VERSION = '0.3.0'
3
+ VERSION = '2.1.0'
4
4
  end
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: 0.3.0
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-14 00:00:00.000000000 Z
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: 1.16.0
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: 1.16.0
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/result_validation.rb
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