lex-validator 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 18b5db3a943ad18053278559b952dc861529612e72005265aafa89a3697b8d36
4
+ data.tar.gz: 5e71f61a7e71cdde4d6e1eacb7d96d026ab24b96f8b57b356f5b673d1a953da2
5
+ SHA512:
6
+ metadata.gz: ef7ae53288a6e579a36af99a5243303d57c64763e69c2dff126e33c66a93b87d10c301a1b4b5e4ee26387127983b81c366817023410034f545f19ea1767c9f93
7
+ data.tar.gz: 24c331734f30d6a6ff8d63eee42a3f44ffcbeafe5ec09cfc78c4269934d39d04a5ae9fa0e26297af0ffe7a836494b3af506538540987bb275df1c1d0089dda77
@@ -0,0 +1,34 @@
1
+ name: CI
2
+ on:
3
+ push:
4
+ branches: [main]
5
+ pull_request:
6
+ schedule:
7
+ - cron: '0 9 * * 1'
8
+
9
+ jobs:
10
+ ci:
11
+ uses: LegionIO/.github/.github/workflows/ci.yml@main
12
+
13
+ excluded-files:
14
+ uses: LegionIO/.github/.github/workflows/excluded-files.yml@main
15
+
16
+ security:
17
+ uses: LegionIO/.github/.github/workflows/security-scan.yml@main
18
+
19
+ version-changelog:
20
+ uses: LegionIO/.github/.github/workflows/version-changelog.yml@main
21
+
22
+ dependency-review:
23
+ uses: LegionIO/.github/.github/workflows/dependency-review.yml@main
24
+
25
+ stale:
26
+ if: github.event_name == 'schedule'
27
+ uses: LegionIO/.github/.github/workflows/stale.yml@main
28
+
29
+ release:
30
+ needs: [ci, excluded-files]
31
+ if: github.event_name == 'push' && github.ref == 'refs/heads/main'
32
+ uses: LegionIO/.github/.github/workflows/release.yml@main
33
+ secrets:
34
+ rubygems-api-key: ${{ secrets.RUBYGEMS_API_KEY }}
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ # Generated test artifacts
2
+ .rspec_status
3
+ tmp/
4
+ *.gem
5
+ pkg/
6
+ Gemfile.lock
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.rspec_status ADDED
@@ -0,0 +1,50 @@
1
+ example_id | status | run_time |
2
+ ------------------------------------------------------------------------------- | ------ | --------------- |
3
+ ./spec/legion/extensions/validator/helpers/quality_gate_spec.rb[1:1:1] | passed | 0.00032 seconds |
4
+ ./spec/legion/extensions/validator/helpers/quality_gate_spec.rb[1:1:2] | passed | 0.00026 seconds |
5
+ ./spec/legion/extensions/validator/helpers/quality_gate_spec.rb[1:1:3] | passed | 0.00002 seconds |
6
+ ./spec/legion/extensions/validator/helpers/quality_gate_spec.rb[1:1:4] | passed | 0.00043 seconds |
7
+ ./spec/legion/extensions/validator/helpers/quality_gate_spec.rb[1:1:5] | passed | 0.00002 seconds |
8
+ ./spec/legion/extensions/validator/helpers/quality_gate_spec.rb[1:1:6] | passed | 0.00002 seconds |
9
+ ./spec/legion/extensions/validator/helpers/quality_gate_spec.rb[1:1:7] | passed | 0.00002 seconds |
10
+ ./spec/legion/extensions/validator/helpers/quality_gate_spec.rb[1:1:8] | passed | 0.00002 seconds |
11
+ ./spec/legion/extensions/validator/helpers/quality_gate_spec.rb[1:2:1] | passed | 0.00027 seconds |
12
+ ./spec/legion/extensions/validator/helpers/quality_gate_spec.rb[1:2:2] | passed | 0.00002 seconds |
13
+ ./spec/legion/extensions/validator/helpers/quality_gate_spec.rb[1:2:3] | passed | 0.00002 seconds |
14
+ ./spec/legion/extensions/validator/helpers/quality_gate_spec.rb[1:2:4] | passed | 0.00002 seconds |
15
+ ./spec/legion/extensions/validator/helpers/quality_gate_spec.rb[1:3:1] | passed | 0.00002 seconds |
16
+ ./spec/legion/extensions/validator/helpers/review_orchestrator_spec.rb[1:1:1:1] | passed | 0.00031 seconds |
17
+ ./spec/legion/extensions/validator/helpers/review_orchestrator_spec.rb[1:1:1:2] | passed | 0.00016 seconds |
18
+ ./spec/legion/extensions/validator/helpers/review_orchestrator_spec.rb[1:1:1:3] | passed | 0.001 seconds |
19
+ ./spec/legion/extensions/validator/helpers/review_orchestrator_spec.rb[1:1:2:1] | passed | 0.00314 seconds |
20
+ ./spec/legion/extensions/validator/helpers/review_orchestrator_spec.rb[1:1:2:2] | passed | 0.00192 seconds |
21
+ ./spec/legion/extensions/validator/helpers/review_orchestrator_spec.rb[1:1:3:1] | passed | 0.00004 seconds |
22
+ ./spec/legion/extensions/validator/helpers/review_orchestrator_spec.rb[1:1:4:1] | passed | 0.00044 seconds |
23
+ ./spec/legion/extensions/validator/helpers/review_orchestrator_spec.rb[1:1:4:2] | passed | 0.00029 seconds |
24
+ ./spec/legion/extensions/validator/helpers/review_orchestrator_spec.rb[1:2:1] | passed | 0.00024 seconds |
25
+ ./spec/legion/extensions/validator/helpers/review_orchestrator_spec.rb[1:2:2] | passed | 0.00003 seconds |
26
+ ./spec/legion/extensions/validator/helpers/review_orchestrator_spec.rb[1:2:3] | passed | 0.00025 seconds |
27
+ ./spec/legion/extensions/validator/helpers/review_orchestrator_spec.rb[1:3:1] | passed | 0.00003 seconds |
28
+ ./spec/legion/extensions/validator/helpers/test_runner_spec.rb[1:1:1] | passed | 0.00004 seconds |
29
+ ./spec/legion/extensions/validator/helpers/test_runner_spec.rb[1:1:2] | passed | 0.00064 seconds |
30
+ ./spec/legion/extensions/validator/helpers/test_runner_spec.rb[1:1:3] | passed | 0.00003 seconds |
31
+ ./spec/legion/extensions/validator/helpers/test_runner_spec.rb[1:1:4] | passed | 0.00028 seconds |
32
+ ./spec/legion/extensions/validator/helpers/test_runner_spec.rb[1:2:1] | passed | 0.00004 seconds |
33
+ ./spec/legion/extensions/validator/helpers/test_runner_spec.rb[1:2:2] | passed | 0.0003 seconds |
34
+ ./spec/legion/extensions/validator/helpers/test_runner_spec.rb[1:2:3] | passed | 0.00096 seconds |
35
+ ./spec/legion/extensions/validator/helpers/test_runner_spec.rb[1:3:1] | passed | 0.00028 seconds |
36
+ ./spec/legion/extensions/validator/helpers/test_runner_spec.rb[1:3:2] | passed | 0.00002 seconds |
37
+ ./spec/legion/extensions/validator/helpers/test_runner_spec.rb[1:3:3] | passed | 0.00002 seconds |
38
+ ./spec/legion/extensions/validator/runners/validator_spec.rb[1:1:1] | passed | 0.00039 seconds |
39
+ ./spec/legion/extensions/validator/runners/validator_spec.rb[1:1:2] | passed | 0.0003 seconds |
40
+ ./spec/legion/extensions/validator/runners/validator_spec.rb[1:1:3] | passed | 0.00054 seconds |
41
+ ./spec/legion/extensions/validator/runners/validator_spec.rb[1:1:4] | passed | 0.00036 seconds |
42
+ ./spec/legion/extensions/validator/runners/validator_spec.rb[1:1:5] | passed | 0.00065 seconds |
43
+ ./spec/legion/extensions/validator/runners/validator_spec.rb[1:1:6] | passed | 0.00013 seconds |
44
+ ./spec/legion/extensions/validator/runners/validator_spec.rb[1:1:7:1] | passed | 0.00035 seconds |
45
+ ./spec/legion/extensions/validator/runners/validator_spec.rb[1:1:8:1] | passed | 0.00052 seconds |
46
+ ./spec/legion/extensions/validator/runners/validator_spec.rb[1:1:9:1] | passed | 0.00027 seconds |
47
+ ./spec/legion/extensions/validator/runners/validator_spec.rb[1:1:10:1] | passed | 0.00004 seconds |
48
+ ./spec/legion/extensions/validator/runners/validator_spec.rb[1:1:11] | passed | 0.00043 seconds |
49
+ ./spec/legion/extensions/validator/version_spec.rb[1:1] | passed | 0.00009 seconds |
50
+ ./spec/legion/extensions/validator/version_spec.rb[1:2] | passed | 0.00002 seconds |
data/.rubocop.yml ADDED
@@ -0,0 +1,27 @@
1
+ inherit_gem:
2
+ rubocop-legion: config/lex.yml
3
+
4
+ Legion/Extension/RunnerReturnHash:
5
+ Enabled: false
6
+
7
+ Legion/HelperMigration/LoggingGuard:
8
+ Enabled: false
9
+
10
+ Naming/MethodParameterName:
11
+ AllowedNames:
12
+ - as
13
+ - at
14
+ - by
15
+ - cc
16
+ - db
17
+ - id
18
+ - if
19
+ - in
20
+ - io
21
+ - ip
22
+ - k
23
+ - of
24
+ - "on"
25
+ - os
26
+ - pp
27
+ - to
data/CLAUDE.md ADDED
@@ -0,0 +1,43 @@
1
+ # lex-validator: Fleet Pipeline Validation
2
+
3
+ **Level 3 Documentation**
4
+ - **Parent**: `CLAUDE.md` (monorepo root)
5
+
6
+ ## Purpose
7
+
8
+ Fourth and final stage of the Fleet Pipeline. Receives implemented work items, runs tests (rspec), lint (rubocop), security scan, and adversarial multi-model LLM review. Applies a quality gate with weighted scoring and renders a verdict (approved/rejected). Approved items route to the ship runner. Rejected items route back to the developer for feedback incorporation.
9
+
10
+ **Gem**: `lex-validator`
11
+ **Version**: 0.1.0
12
+ **Namespace**: `Legion::Extensions::Validator`
13
+
14
+ ## Key Design Decisions
15
+
16
+ - **Unanimity gate**: All k validators must approve. Any dissent = rejection. Fail closed on LLM errors.
17
+ - **Own implementation**: `ReviewOrchestrator` is NOT inherited from lex-eval `AgenticReview`. Uses `Legion::LLM::Prompt.dispatch` with post-hoc JSON extraction to preserve pipeline tracing.
18
+ - **Quality gate scoring** (absorbed from lex-factory): Completeness 35%, Correctness 35%, Quality 20%, Security 10%. Pass threshold >= 0.8.
19
+ - **Intent variation**: Each reviewer gets a different `intent.capability` to encourage Router model diversity (MAKER paper).
20
+
21
+ ## Runners
22
+
23
+ ### `Runners::Validator`
24
+ - `validate(work_item:, **)` -- Run tests, lint, security scan, adversarial review, quality gate, render verdict
25
+
26
+ ## Helpers
27
+
28
+ - `Helpers::QualityGate` -- Weighted scoring with configurable threshold (absorbed from lex-factory)
29
+ - `Helpers::TestRunner` -- Delegates to lex-exec for rspec, rubocop, security scan
30
+ - `Helpers::ReviewOrchestrator` -- k-factor adversarial review with unanimity gate, varied intent per reviewer
31
+
32
+ ## Transport
33
+
34
+ - Exchange: `lex.validator` (topic, durable)
35
+ - Queue: `lex.validator.runners.validator` (durable, routing key `lex.validator.runners.validator.#`)
36
+
37
+ ## Development
38
+
39
+ ```bash
40
+ bundle install
41
+ bundle exec rspec
42
+ bundle exec rubocop
43
+ ```
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+ gemspec
5
+
6
+ group :test do
7
+ gem 'rake'
8
+ gem 'rspec', '~> 3.13'
9
+ gem 'rspec_junit_formatter'
10
+ gem 'rubocop', '~> 1.75'
11
+ gem 'rubocop-legion', '~> 0.1', require: false
12
+ gem 'rubocop-rspec'
13
+ gem 'simplecov'
14
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/legion/extensions/validator/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'lex-validator'
7
+ spec.version = Legion::Extensions::Validator::VERSION
8
+ spec.authors = ['Esity']
9
+ spec.email = ['matthewdiverson@gmail.com']
10
+
11
+ spec.summary = 'Legion::Extensions::Validator'
12
+ spec.description = 'Fleet pipeline validation: tests, lint, security scan, adversarial LLM review'
13
+ spec.homepage = 'https://github.com/LegionIO/lex-validator'
14
+ spec.license = 'MIT'
15
+ spec.required_ruby_version = '>= 3.4'
16
+
17
+ spec.metadata['homepage_uri'] = spec.homepage
18
+ spec.metadata['source_code_uri'] = 'https://github.com/LegionIO/lex-validator'
19
+ spec.metadata['changelog_uri'] = 'https://github.com/LegionIO/lex-validator'
20
+ spec.metadata['documentation_uri'] = 'https://github.com/LegionIO/lex-validator'
21
+ spec.metadata['bug_tracker_uri'] = 'https://github.com/LegionIO/lex-validator/issues'
22
+ spec.metadata['rubygems_mfa_required'] = 'true'
23
+
24
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
25
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
26
+ end
27
+ spec.require_paths = ['lib']
28
+
29
+ spec.add_dependency 'concurrent-ruby', '>= 1.1'
30
+ spec.add_dependency 'legion-cache', '>= 1.3.11'
31
+ spec.add_dependency 'legion-crypt', '>= 1.4.9'
32
+ spec.add_dependency 'legion-data', '>= 1.4.17'
33
+ spec.add_dependency 'legion-json', '>= 1.2.1'
34
+ spec.add_dependency 'legion-llm', '>= 0.1'
35
+ spec.add_dependency 'legion-logging', '>= 1.3.2'
36
+ spec.add_dependency 'legion-settings', '>= 1.3.14'
37
+ spec.add_dependency 'legion-transport', '>= 1.3.9'
38
+
39
+ spec.add_development_dependency 'rspec', '~> 3.13'
40
+ spec.add_development_dependency 'rspec_junit_formatter'
41
+ spec.add_development_dependency 'rubocop', '~> 1.75'
42
+ spec.add_development_dependency 'rubocop-rspec'
43
+ spec.add_development_dependency 'simplecov'
44
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ return unless defined?(Legion::Extensions::Actors::Subscription)
4
+
5
+ module Legion
6
+ module Extensions
7
+ module Validator
8
+ module Actor
9
+ class Validator < Legion::Extensions::Actors::Subscription
10
+ def runner_function
11
+ 'validate'
12
+ end
13
+
14
+ def check_subtask?
15
+ true
16
+ end
17
+
18
+ def generate_task?
19
+ false
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Validator
6
+ module Helpers
7
+ module QualityGate
8
+ module_function
9
+
10
+ WEIGHTS = {
11
+ completeness: 0.35,
12
+ correctness: 0.35,
13
+ quality: 0.20,
14
+ security: 0.10
15
+ }.freeze
16
+
17
+ DEFAULT_THRESHOLD = 0.8
18
+
19
+ def score(completeness:, correctness:, quality:, security:)
20
+ w = Legion::Settings.dig(:fleet, :validation, :quality_weights) || WEIGHTS
21
+
22
+ aggregate = (
23
+ (completeness * (w[:completeness] || WEIGHTS[:completeness])) +
24
+ (correctness * (w[:correctness] || WEIGHTS[:correctness])) +
25
+ (quality * (w[:quality] || WEIGHTS[:quality])) +
26
+ (security * (w[:security] || WEIGHTS[:security]))
27
+ ).round(3)
28
+
29
+ {
30
+ completeness: completeness,
31
+ correctness: correctness,
32
+ quality: quality,
33
+ security: security,
34
+ aggregate: aggregate
35
+ }
36
+ end
37
+
38
+ def passing?(score:, threshold: nil)
39
+ threshold ||= Legion::Settings.dig(:fleet, :validation, :quality_gate_threshold) || DEFAULT_THRESHOLD
40
+ score[:aggregate] >= threshold
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,195 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'concurrent'
5
+
6
+ module Legion
7
+ module Extensions
8
+ module Validator
9
+ module Helpers
10
+ module ReviewOrchestrator
11
+ extend self
12
+
13
+ REVIEW_PROMPT_TEMPLATE = <<~PROMPT
14
+ You are a senior code reviewer performing an adversarial review of a code change.
15
+ Your job is to find bugs, security issues, logic errors, and quality problems.
16
+
17
+ Be thorough and critical. If you see ANY issue, reject the change.
18
+
19
+ ## Work Item
20
+ Title: %<title>s
21
+ Repository: %<repo>s
22
+ Language: %<language>s
23
+
24
+ ## Diff
25
+ %<diff>s
26
+
27
+ ## Instructions
28
+ Respond with a JSON object inside a fenced code block:
29
+ ```json
30
+ {
31
+ "verdict": "approve" or "reject",
32
+ "confidence": 0.0 to 1.0,
33
+ "issues": ["list of specific issues found"],
34
+ "summary": "brief explanation of your verdict"
35
+ }
36
+ ```
37
+ PROMPT
38
+
39
+ REVIEW_SCHEMA = {
40
+ type: :object,
41
+ properties: {
42
+ verdict: { type: :string, enum: %w[approve reject] },
43
+ confidence: { type: :number, minimum: 0.0, maximum: 1.0 },
44
+ issues: { type: :array, items: { type: :string } },
45
+ summary: { type: :string }
46
+ },
47
+ required: %i[verdict confidence issues summary]
48
+ }.freeze
49
+
50
+ CAPABILITY_LEVELS = %i[basic moderate reasoning].freeze
51
+
52
+ def review(work_item:, diff:, k:)
53
+ return { verdict: 'approved', reviews: [], merged_feedback: [] } if k.zero?
54
+
55
+ exclude = build_upstream_exclusion(work_item: work_item)
56
+ timeout = Legion::Settings.dig(:fleet, :llm, :validator_timeout_seconds) || 120
57
+
58
+ futures = (0...k).map do |i|
59
+ Concurrent::Future.execute do
60
+ run_single_review(work_item: work_item, diff: diff, index: i, exclude: exclude)
61
+ end
62
+ end
63
+
64
+ reviews = collect_reviews(futures: futures, timeout: timeout)
65
+ verdict = reviews.all? { |r| r[:verdict] == 'approve' } ? 'approved' : 'rejected'
66
+ merged_feedback = extract_merged_feedback(reviews: reviews)
67
+
68
+ { verdict: verdict, reviews: reviews, merged_feedback: merged_feedback }
69
+ end
70
+
71
+ def parse_review_response(content:)
72
+ json_str = extract_json(content)
73
+ parsed = ::JSON.parse(json_str, symbolize_names: true)
74
+ validate_review(parsed)
75
+ rescue ::JSON::ParserError, TypeError => e
76
+ { verdict: 'reject', confidence: 0.0,
77
+ issues: ["Failed to parse review response: #{e.message}"],
78
+ summary: 'Review response was not valid JSON' }
79
+ end
80
+
81
+ def intent_for_reviewer(index:, base_difficulty:)
82
+ base_idx = difficulty_to_capability_index(base_difficulty)
83
+ shifted = (base_idx + index) % CAPABILITY_LEVELS.size
84
+ { capability: CAPABILITY_LEVELS[shifted] }
85
+ end
86
+
87
+ private
88
+
89
+ def collect_reviews(futures:, timeout:)
90
+ futures.each_with_index.map do |future, i|
91
+ result = future.value(timeout)
92
+ next result unless result.nil?
93
+
94
+ if future.rejected?
95
+ { verdict: 'reject', confidence: 0.0,
96
+ issues: ["Reviewer #{i} raised an unhandled exception: #{future.reason&.message}"],
97
+ summary: 'Validator exception — fail closed' }
98
+ else
99
+ timeout_review(index: i, timeout: timeout)
100
+ end
101
+ end
102
+ end
103
+
104
+ def timeout_review(index:, timeout:)
105
+ { verdict: 'reject', confidence: 0.0,
106
+ issues: ["Reviewer #{index} timed out after #{timeout}s"],
107
+ summary: 'Validator timeout — fail closed' }
108
+ end
109
+
110
+ def extract_merged_feedback(reviews:)
111
+ reviews
112
+ .flat_map { |r| Array(r[:issues]) }
113
+ .reject(&:empty?)
114
+ .uniq
115
+ end
116
+
117
+ def run_single_review(work_item:, diff:, index:, exclude: {})
118
+ difficulty = work_item.dig(:config, :estimated_difficulty) || 0.5
119
+ intent = intent_for_reviewer(index: index, base_difficulty: difficulty)
120
+ prompt = build_review_prompt(work_item: work_item, diff: diff)
121
+
122
+ response = Legion::LLM::Prompt.dispatch(
123
+ prompt,
124
+ schema: REVIEW_SCHEMA,
125
+ tools: [],
126
+ exclude: exclude,
127
+ intent: intent,
128
+ caller: { extension: 'lex-validator', operation: "adversarial_review_#{index}" },
129
+ agent: {
130
+ id: "fleet:validator:reviewer_#{index}",
131
+ name: "Fleet Validator Reviewer #{index}",
132
+ type: :autonomous,
133
+ goal: "Review: #{work_item[:title]}"
134
+ },
135
+ tracing: {
136
+ trace_id: work_item[:work_item_id],
137
+ correlation_id: work_item[:source_ref]
138
+ }
139
+ )
140
+
141
+ parse_review_response(content: response[:content])
142
+ rescue StandardError => e
143
+ { verdict: 'reject', confidence: 0.0,
144
+ issues: ["Reviewer #{index} failed: #{e.message}"],
145
+ summary: "LLM review error: #{e.message}" }
146
+ end
147
+
148
+ def build_review_prompt(work_item:, diff:)
149
+ format(
150
+ REVIEW_PROMPT_TEMPLATE,
151
+ title: work_item[:title],
152
+ repo: "#{work_item.dig(:repo, :owner)}/#{work_item.dig(:repo, :name)}",
153
+ language: work_item.dig(:repo, :language) || 'unknown',
154
+ diff: diff
155
+ )
156
+ end
157
+
158
+ def build_upstream_exclusion(work_item:)
159
+ exclude = {}
160
+ Array(work_item.dig(:pipeline, :trace)).each do |t|
161
+ next unless t[:model]
162
+
163
+ (exclude[t[:provider]&.to_sym] ||= []) << t[:model]
164
+ end
165
+ exclude.each_value(&:uniq!)
166
+ exclude
167
+ end
168
+
169
+ def extract_json(content)
170
+ match = content.match(/```(?:json)?\s*\n?(.*?)\n?```/m)
171
+ return match[1].strip if match
172
+
173
+ content.strip
174
+ end
175
+
176
+ def validate_review(parsed)
177
+ parsed[:verdict] = parsed[:verdict]&.downcase || 'reject'
178
+ parsed[:confidence] = (parsed[:confidence] || 0.0).to_f.clamp(0.0, 1.0)
179
+ parsed[:issues] = Array(parsed[:issues])
180
+ parsed[:summary] = parsed[:summary] || ''
181
+ parsed
182
+ end
183
+
184
+ def difficulty_to_capability_index(difficulty)
185
+ case difficulty
186
+ when 0.0...0.3 then 0 # :basic
187
+ when 0.3...0.6 then 1 # :moderate
188
+ else 2 # :reasoning
189
+ end
190
+ end
191
+ end
192
+ end
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Validator
6
+ module Helpers
7
+ module TestRunner
8
+ module_function
9
+
10
+ def run_tests(worktree_path:, **_kwargs)
11
+ # In production, delegates to:
12
+ # Legion::Extensions::Exec::Runners::Bundler.exec_rspec(path: worktree_path)
13
+ # Stubbed here — depends on lex-exec being available.
14
+ # Phase 1: Ruby only (bundle exec rspec).
15
+ {
16
+ success: true,
17
+ total: 0,
18
+ passed: 0,
19
+ failures: 0,
20
+ pass_rate: 1.0,
21
+ output: '',
22
+ worktree_path: worktree_path
23
+ }
24
+ end
25
+
26
+ def run_lint(worktree_path:, **_kwargs)
27
+ # In production, delegates to:
28
+ # Legion::Extensions::Exec::Runners::Bundler.exec_rubocop(path: worktree_path)
29
+ # Stubbed here — depends on lex-exec being available.
30
+ # Phase 1: Ruby only (bundle exec rubocop).
31
+ {
32
+ success: true,
33
+ offenses: 0,
34
+ clean: true,
35
+ output: '',
36
+ worktree_path: worktree_path
37
+ }
38
+ end
39
+
40
+ def run_security_scan(file_paths:, worktree_path:, **_kwargs)
41
+ # In production, delegates to:
42
+ # Legion::Extensions::Eval::Evaluators::SecurityEvaluator.new.check(code: content)
43
+ # for each changed file. Phase 1: Ruby-only patterns.
44
+ # For non-Ruby repos, skip and note in review.
45
+ {
46
+ success: true,
47
+ findings: [],
48
+ clean: true,
49
+ files_scanned: file_paths.size,
50
+ worktree_path: worktree_path
51
+ }
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,206 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Validator
6
+ module Runners
7
+ module Validator
8
+ extend self
9
+
10
+ def validate(results: nil, work_item: nil, args: nil, **)
11
+ results = json_load(results) if results.is_a?(String)
12
+ work_item ||= results&.dig(:work_item) || args&.dig(:work_item)
13
+ raise ArgumentError, "work_item is nil in #{__method__}" if work_item.nil?
14
+
15
+ started_at = Time.now.utc.iso8601
16
+ validation_config = work_item.dig(:config, :validation) || {}
17
+
18
+ if validation_config[:enabled] == false
19
+ return build_result(work_item: work_item, verdict: 'approved',
20
+ score: perfect_score, issues: [], merged_feedback: [],
21
+ skip_reason: 'validation disabled', started_at: started_at)
22
+ end
23
+
24
+ check_tests_lint_security(work_item: work_item, config: validation_config) => {
25
+ test_result:, lint_result:, security_result:
26
+ }
27
+
28
+ early = pr_guard_check(work_item: work_item, started_at: started_at)
29
+ return early if early
30
+
31
+ review_result = run_review_if_enabled(work_item: work_item, config: validation_config)
32
+ score = build_score(test_result: test_result, lint_result: lint_result,
33
+ security_result: security_result, review_result: review_result)
34
+ threshold = Legion::Settings.dig(:fleet, :validation, :quality_gate_threshold) || 0.8
35
+ verdict = determine_verdict(review_result: review_result, score: score, threshold: threshold)
36
+ merged_feedback = collect_feedback(review_result: review_result, test_result: test_result,
37
+ lint_result: lint_result, security_result: security_result)
38
+
39
+ build_result(work_item: work_item, verdict: verdict, score: score,
40
+ issues: merged_feedback, merged_feedback: merged_feedback, started_at: started_at)
41
+ end
42
+
43
+ private
44
+
45
+ def check_tests_lint_security(work_item:, config:)
46
+ {
47
+ test_result: run_tests_if_enabled(work_item: work_item, config: config),
48
+ lint_result: run_lint_if_enabled(work_item: work_item, config: config),
49
+ security_result: run_security_if_enabled(work_item: work_item, config: config)
50
+ }
51
+ end
52
+
53
+ def pr_guard_check(work_item:, started_at:)
54
+ zero_score = Helpers::QualityGate.score(completeness: 0.0, correctness: 0.0,
55
+ quality: 0.0, security: 0.0)
56
+ comment_result = check_pr_review_comments(work_item: work_item)
57
+ if comment_result[:unresolved_external].any?
58
+ msgs = comment_result[:unresolved_external].map { |c| c[:body] }
59
+ return build_result(work_item: work_item, verdict: 'rejected', score: zero_score,
60
+ issues: msgs, merged_feedback: msgs, started_at: started_at)
61
+ end
62
+
63
+ return if verify_pr_head_sha(work_item: work_item)
64
+
65
+ stale_msg = 'Stale diff: PR head SHA does not match developer push'
66
+ build_result(work_item: work_item, verdict: 'rejected', score: zero_score,
67
+ issues: [stale_msg], merged_feedback: [stale_msg], started_at: started_at)
68
+ end
69
+
70
+ def run_tests_if_enabled(work_item:, config:)
71
+ return { success: true, pass_rate: 1.0, total: 0, failures: 0 } unless config[:run_tests]
72
+
73
+ worktree = cache_get("fleet:worktree:#{work_item[:work_item_id]}") || '/tmp'
74
+ Helpers::TestRunner.run_tests(worktree_path: worktree)
75
+ end
76
+
77
+ def run_lint_if_enabled(work_item:, config:)
78
+ return { success: true, offenses: 0, clean: true } unless config[:run_lint]
79
+
80
+ worktree = cache_get("fleet:worktree:#{work_item[:work_item_id]}") || '/tmp'
81
+ Helpers::TestRunner.run_lint(worktree_path: worktree)
82
+ end
83
+
84
+ def run_security_if_enabled(work_item:, config:)
85
+ return { success: true, findings: [], clean: true } unless config[:security_scan]
86
+
87
+ file_paths = Array(work_item.dig(:pipeline, :changes))
88
+ worktree = cache_get("fleet:worktree:#{work_item[:work_item_id]}") || '/tmp'
89
+ Helpers::TestRunner.run_security_scan(file_paths: file_paths, worktree_path: worktree)
90
+ end
91
+
92
+ def check_pr_review_comments(work_item:) # rubocop:disable Lint/UnusedMethodArgument
93
+ # In production, fetches via lex-github. Stubbed pending WS-02.
94
+ { unresolved_external: [] }
95
+ end
96
+
97
+ def verify_pr_head_sha(work_item:) # rubocop:disable Lint/UnusedMethodArgument
98
+ # In production, polls list_pull_request_commits. Stubbed pending WS-02.
99
+ true
100
+ end
101
+
102
+ def run_review_if_enabled(work_item:, config:)
103
+ return { verdict: 'approved', reviews: [], merged_feedback: [] } unless config[:adversarial_review]
104
+
105
+ k = work_item.dig(:config, :implementation, :validators) || 1
106
+ diff = fetch_diff(work_item: work_item)
107
+ Helpers::ReviewOrchestrator.review(work_item: work_item, diff: diff, k: k)
108
+ end
109
+
110
+ def fetch_diff(work_item:) # rubocop:disable Lint/UnusedMethodArgument
111
+ # In production, calls list_pull_request_files. Stubbed pending WS-02.
112
+ ''
113
+ end
114
+
115
+ def cache_get(key)
116
+ Legion::Cache.get(key) # rubocop:disable Legion/HelperMigration/DirectCache
117
+ end
118
+
119
+ def json_load(str)
120
+ Legion::JSON.load(str) # rubocop:disable Legion/HelperMigration/DirectJson
121
+ end
122
+
123
+ def build_score(test_result:, lint_result:, security_result:, review_result:)
124
+ completeness = test_result[:pass_rate]
125
+ # security_result[:clean] is intentionally counted in both `correctness` and `security` dimensions.
126
+ # A security finding halves correctness (0.35 weight) AND zeros out security (0.10 weight),
127
+ # creating a strong "fail closed" signal. This is intentional design.
128
+ correctness = lint_result[:clean] && security_result[:clean] ? 1.0 : 0.5
129
+ quality = review_result[:verdict] == 'approved' ? 1.0 : 0.3
130
+ security = security_result[:clean] ? 1.0 : 0.0
131
+
132
+ Helpers::QualityGate.score(
133
+ completeness: completeness,
134
+ correctness: correctness,
135
+ quality: quality,
136
+ security: security
137
+ )
138
+ end
139
+
140
+ def determine_verdict(review_result:, score:, threshold:)
141
+ return 'rejected' if review_result[:verdict] == 'rejected'
142
+ return 'approved' if Helpers::QualityGate.passing?(score: score, threshold: threshold)
143
+
144
+ 'rejected'
145
+ end
146
+
147
+ def collect_feedback(review_result:, test_result:, lint_result:, security_result:)
148
+ feedback = Array(review_result[:merged_feedback])
149
+ feedback << 'Tests failing' if test_result[:failures].positive?
150
+ feedback << "Lint offenses: #{lint_result[:offenses]}" if lint_result[:offenses].positive?
151
+ feedback << "Security findings: #{security_result[:findings].size}" unless security_result[:clean]
152
+ feedback
153
+ end
154
+
155
+ def build_result(work_item:, verdict:, score:, issues:, merged_feedback:,
156
+ started_at:, skip_reason: nil)
157
+ review_result = { verdict: verdict, score: score, issues: issues,
158
+ merged_feedback: merged_feedback }
159
+ review_result[:skip_reason] = skip_reason if skip_reason
160
+
161
+ feedback_history = work_item.dig(:pipeline, :feedback_history) || []
162
+ if verdict == 'rejected'
163
+ feedback_history += [{
164
+ verdict: verdict,
165
+ issues: merged_feedback,
166
+ round: work_item.dig(:pipeline, :attempt) || 0
167
+ }]
168
+ end
169
+
170
+ work_item = work_item.merge(
171
+ pipeline: work_item[:pipeline].merge(
172
+ stage: 'validated',
173
+ review_result: review_result,
174
+ feedback_history: feedback_history,
175
+ trace: work_item[:pipeline][:trace] + [build_trace_entry(started_at: started_at)]
176
+ )
177
+ )
178
+
179
+ { success: true, work_item: work_item }
180
+ end
181
+
182
+ def perfect_score
183
+ Helpers::QualityGate.score(
184
+ completeness: 1.0,
185
+ correctness: 1.0,
186
+ quality: 1.0,
187
+ security: 1.0
188
+ )
189
+ end
190
+
191
+ def build_trace_entry(started_at: Time.now.utc.iso8601)
192
+ {
193
+ stage: 'validator',
194
+ node: Legion::Settings[:client][:name],
195
+ model: nil,
196
+ provider: nil,
197
+ started_at: started_at,
198
+ completed_at: Time.now.utc.iso8601,
199
+ token_usage: {}
200
+ }
201
+ end
202
+ end
203
+ end
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Validator
6
+ module Transport
7
+ module Exchanges
8
+ class Validator < Legion::Transport::Exchange
9
+ def exchange_name
10
+ 'lex.validator'
11
+ end
12
+
13
+ def exchange_options
14
+ { type: 'topic', durable: true }
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Validator
6
+ module Transport
7
+ module Queues
8
+ class Validator < Legion::Transport::Queue
9
+ def queue_name
10
+ 'lex.validator.runners.validator'
11
+ end
12
+
13
+ def queue_options
14
+ { durable: true }
15
+ end
16
+
17
+ def routing_key
18
+ 'lex.validator.runners.validator.#'
19
+ end
20
+
21
+ def exchange
22
+ Exchanges::Validator
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module Validator
6
+ VERSION = '0.1.0'
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'validator/version'
4
+ require_relative 'validator/helpers/quality_gate'
5
+ require_relative 'validator/helpers/test_runner'
6
+ require_relative 'validator/helpers/review_orchestrator'
7
+ require_relative 'validator/runners/validator'
8
+
9
+ if defined?(Legion::Transport::Exchange)
10
+ require_relative 'validator/transport/exchanges/validator'
11
+ require_relative 'validator/transport/queues/validator'
12
+ end
13
+
14
+ require_relative 'validator/actors/validator'
15
+
16
+ module Legion
17
+ module Extensions
18
+ module Validator
19
+ extend Legion::Extensions::Core if defined?(Legion::Extensions::Core)
20
+
21
+ def self.llm_required?
22
+ true
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,5 @@
1
+ ...............
2
+
3
+ Finished in 0.00422 seconds (files took 0.65089 seconds to load)
4
+ 15 examples, 0 failures
5
+
@@ -0,0 +1 @@
1
+ {"version":"3.13.6","examples":[{"id":"./spec/legion/extensions/validator/helpers/quality_gate_spec.rb[1:1:1]","description":"returns a score hash with aggregate","full_description":"Legion::Extensions::Validator::Helpers::QualityGate.score returns a score hash with aggregate","status":"passed","file_path":"./spec/legion/extensions/validator/helpers/quality_gate_spec.rb","line_number":5,"run_time":0.000491,"pending_message":null},{"id":"./spec/legion/extensions/validator/helpers/quality_gate_spec.rb[1:1:2]","description":"returns 1.0 aggregate for all perfect scores","full_description":"Legion::Extensions::Validator::Helpers::QualityGate.score returns 1.0 aggregate for all perfect scores","status":"passed","file_path":"./spec/legion/extensions/validator/helpers/quality_gate_spec.rb","line_number":15,"run_time":0.000373,"pending_message":null},{"id":"./spec/legion/extensions/validator/helpers/quality_gate_spec.rb[1:1:3]","description":"returns 0.0 aggregate for all zero scores","full_description":"Legion::Extensions::Validator::Helpers::QualityGate.score returns 0.0 aggregate for all zero scores","status":"passed","file_path":"./spec/legion/extensions/validator/helpers/quality_gate_spec.rb","line_number":25,"run_time":0.00003,"pending_message":null},{"id":"./spec/legion/extensions/validator/helpers/quality_gate_spec.rb[1:1:4]","description":"weights completeness at 35%","full_description":"Legion::Extensions::Validator::Helpers::QualityGate.score weights completeness at 35%","status":"passed","file_path":"./spec/legion/extensions/validator/helpers/quality_gate_spec.rb","line_number":35,"run_time":0.000493,"pending_message":null},{"id":"./spec/legion/extensions/validator/helpers/quality_gate_spec.rb[1:1:5]","description":"weights correctness at 35%","full_description":"Legion::Extensions::Validator::Helpers::QualityGate.score weights correctness at 35%","status":"passed","file_path":"./spec/legion/extensions/validator/helpers/quality_gate_spec.rb","line_number":45,"run_time":0.000035,"pending_message":null},{"id":"./spec/legion/extensions/validator/helpers/quality_gate_spec.rb[1:1:6]","description":"weights quality at 20%","full_description":"Legion::Extensions::Validator::Helpers::QualityGate.score weights quality at 20%","status":"passed","file_path":"./spec/legion/extensions/validator/helpers/quality_gate_spec.rb","line_number":55,"run_time":0.000027,"pending_message":null},{"id":"./spec/legion/extensions/validator/helpers/quality_gate_spec.rb[1:1:7]","description":"weights security at 10%","full_description":"Legion::Extensions::Validator::Helpers::QualityGate.score weights security at 10%","status":"passed","file_path":"./spec/legion/extensions/validator/helpers/quality_gate_spec.rb","line_number":65,"run_time":0.000024,"pending_message":null},{"id":"./spec/legion/extensions/validator/helpers/quality_gate_spec.rb[1:1:8]","description":"includes individual scores in the result","full_description":"Legion::Extensions::Validator::Helpers::QualityGate.score includes individual scores in the result","status":"passed","file_path":"./spec/legion/extensions/validator/helpers/quality_gate_spec.rb","line_number":75,"run_time":0.000025,"pending_message":null},{"id":"./spec/legion/extensions/validator/helpers/quality_gate_spec.rb[1:2:1]","description":"returns true when aggregate >= threshold","full_description":"Legion::Extensions::Validator::Helpers::QualityGate.passing? returns true when aggregate >= threshold","status":"passed","file_path":"./spec/legion/extensions/validator/helpers/quality_gate_spec.rb","line_number":90,"run_time":0.00056,"pending_message":null},{"id":"./spec/legion/extensions/validator/helpers/quality_gate_spec.rb[1:2:2]","description":"returns false when aggregate < threshold","full_description":"Legion::Extensions::Validator::Helpers::QualityGate.passing? returns false when aggregate < threshold","status":"passed","file_path":"./spec/legion/extensions/validator/helpers/quality_gate_spec.rb","line_number":95,"run_time":0.000028,"pending_message":null},{"id":"./spec/legion/extensions/validator/helpers/quality_gate_spec.rb[1:2:3]","description":"uses custom threshold when provided","full_description":"Legion::Extensions::Validator::Helpers::QualityGate.passing? uses custom threshold when provided","status":"passed","file_path":"./spec/legion/extensions/validator/helpers/quality_gate_spec.rb","line_number":100,"run_time":0.000023,"pending_message":null},{"id":"./spec/legion/extensions/validator/helpers/quality_gate_spec.rb[1:2:4]","description":"uses default threshold of 0.8","full_description":"Legion::Extensions::Validator::Helpers::QualityGate.passing? uses default threshold of 0.8","status":"passed","file_path":"./spec/legion/extensions/validator/helpers/quality_gate_spec.rb","line_number":105,"run_time":0.000023,"pending_message":null},{"id":"./spec/legion/extensions/validator/helpers/quality_gate_spec.rb[1:3:1]","description":"sums to 1.0","full_description":"Legion::Extensions::Validator::Helpers::QualityGate WEIGHTS sums to 1.0","status":"passed","file_path":"./spec/legion/extensions/validator/helpers/quality_gate_spec.rb","line_number":115,"run_time":0.000026,"pending_message":null},{"id":"./spec/legion/extensions/validator/version_spec.rb[1:1]","description":"is defined","full_description":"Legion::Extensions::Validator::VERSION is defined","status":"passed","file_path":"./spec/legion/extensions/validator/version_spec.rb","line_number":4,"run_time":0.000541,"pending_message":null},{"id":"./spec/legion/extensions/validator/version_spec.rb[1:2]","description":"is a valid semver string","full_description":"Legion::Extensions::Validator::VERSION is a valid semver string","status":"passed","file_path":"./spec/legion/extensions/validator/version_spec.rb","line_number":8,"run_time":0.000425,"pending_message":null}],"summary":{"duration":0.004223,"example_count":15,"failure_count":0,"pending_count":0,"errors_outside_of_examples_count":0},"summary_line":"15 examples, 0 failures"}
metadata ADDED
@@ -0,0 +1,262 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lex-validator
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Esity
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: concurrent-ruby
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '1.1'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '1.1'
26
+ - !ruby/object:Gem::Dependency
27
+ name: legion-cache
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 1.3.11
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: 1.3.11
40
+ - !ruby/object:Gem::Dependency
41
+ name: legion-crypt
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: 1.4.9
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: 1.4.9
54
+ - !ruby/object:Gem::Dependency
55
+ name: legion-data
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: 1.4.17
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: 1.4.17
68
+ - !ruby/object:Gem::Dependency
69
+ name: legion-json
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: 1.2.1
75
+ type: :runtime
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: 1.2.1
82
+ - !ruby/object:Gem::Dependency
83
+ name: legion-llm
84
+ requirement: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: '0.1'
89
+ type: :runtime
90
+ prerelease: false
91
+ version_requirements: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0.1'
96
+ - !ruby/object:Gem::Dependency
97
+ name: legion-logging
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: 1.3.2
103
+ type: :runtime
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: 1.3.2
110
+ - !ruby/object:Gem::Dependency
111
+ name: legion-settings
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - ">="
115
+ - !ruby/object:Gem::Version
116
+ version: 1.3.14
117
+ type: :runtime
118
+ prerelease: false
119
+ version_requirements: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: 1.3.14
124
+ - !ruby/object:Gem::Dependency
125
+ name: legion-transport
126
+ requirement: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - ">="
129
+ - !ruby/object:Gem::Version
130
+ version: 1.3.9
131
+ type: :runtime
132
+ prerelease: false
133
+ version_requirements: !ruby/object:Gem::Requirement
134
+ requirements:
135
+ - - ">="
136
+ - !ruby/object:Gem::Version
137
+ version: 1.3.9
138
+ - !ruby/object:Gem::Dependency
139
+ name: rspec
140
+ requirement: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - "~>"
143
+ - !ruby/object:Gem::Version
144
+ version: '3.13'
145
+ type: :development
146
+ prerelease: false
147
+ version_requirements: !ruby/object:Gem::Requirement
148
+ requirements:
149
+ - - "~>"
150
+ - !ruby/object:Gem::Version
151
+ version: '3.13'
152
+ - !ruby/object:Gem::Dependency
153
+ name: rspec_junit_formatter
154
+ requirement: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - ">="
157
+ - !ruby/object:Gem::Version
158
+ version: '0'
159
+ type: :development
160
+ prerelease: false
161
+ version_requirements: !ruby/object:Gem::Requirement
162
+ requirements:
163
+ - - ">="
164
+ - !ruby/object:Gem::Version
165
+ version: '0'
166
+ - !ruby/object:Gem::Dependency
167
+ name: rubocop
168
+ requirement: !ruby/object:Gem::Requirement
169
+ requirements:
170
+ - - "~>"
171
+ - !ruby/object:Gem::Version
172
+ version: '1.75'
173
+ type: :development
174
+ prerelease: false
175
+ version_requirements: !ruby/object:Gem::Requirement
176
+ requirements:
177
+ - - "~>"
178
+ - !ruby/object:Gem::Version
179
+ version: '1.75'
180
+ - !ruby/object:Gem::Dependency
181
+ name: rubocop-rspec
182
+ requirement: !ruby/object:Gem::Requirement
183
+ requirements:
184
+ - - ">="
185
+ - !ruby/object:Gem::Version
186
+ version: '0'
187
+ type: :development
188
+ prerelease: false
189
+ version_requirements: !ruby/object:Gem::Requirement
190
+ requirements:
191
+ - - ">="
192
+ - !ruby/object:Gem::Version
193
+ version: '0'
194
+ - !ruby/object:Gem::Dependency
195
+ name: simplecov
196
+ requirement: !ruby/object:Gem::Requirement
197
+ requirements:
198
+ - - ">="
199
+ - !ruby/object:Gem::Version
200
+ version: '0'
201
+ type: :development
202
+ prerelease: false
203
+ version_requirements: !ruby/object:Gem::Requirement
204
+ requirements:
205
+ - - ">="
206
+ - !ruby/object:Gem::Version
207
+ version: '0'
208
+ description: 'Fleet pipeline validation: tests, lint, security scan, adversarial LLM
209
+ review'
210
+ email:
211
+ - matthewdiverson@gmail.com
212
+ executables: []
213
+ extensions: []
214
+ extra_rdoc_files: []
215
+ files:
216
+ - ".github/workflows/ci.yml"
217
+ - ".gitignore"
218
+ - ".rspec"
219
+ - ".rspec_status"
220
+ - ".rubocop.yml"
221
+ - CLAUDE.md
222
+ - Gemfile
223
+ - lex-validator.gemspec
224
+ - lib/legion/extensions/validator.rb
225
+ - lib/legion/extensions/validator/actors/validator.rb
226
+ - lib/legion/extensions/validator/helpers/quality_gate.rb
227
+ - lib/legion/extensions/validator/helpers/review_orchestrator.rb
228
+ - lib/legion/extensions/validator/helpers/test_runner.rb
229
+ - lib/legion/extensions/validator/runners/validator.rb
230
+ - lib/legion/extensions/validator/transport/exchanges/validator.rb
231
+ - lib/legion/extensions/validator/transport/queues/validator.rb
232
+ - lib/legion/extensions/validator/version.rb
233
+ - tmp/rspec_progress.txt
234
+ - tmp/rspec_results.json
235
+ homepage: https://github.com/LegionIO/lex-validator
236
+ licenses:
237
+ - MIT
238
+ metadata:
239
+ homepage_uri: https://github.com/LegionIO/lex-validator
240
+ source_code_uri: https://github.com/LegionIO/lex-validator
241
+ changelog_uri: https://github.com/LegionIO/lex-validator
242
+ documentation_uri: https://github.com/LegionIO/lex-validator
243
+ bug_tracker_uri: https://github.com/LegionIO/lex-validator/issues
244
+ rubygems_mfa_required: 'true'
245
+ rdoc_options: []
246
+ require_paths:
247
+ - lib
248
+ required_ruby_version: !ruby/object:Gem::Requirement
249
+ requirements:
250
+ - - ">="
251
+ - !ruby/object:Gem::Version
252
+ version: '3.4'
253
+ required_rubygems_version: !ruby/object:Gem::Requirement
254
+ requirements:
255
+ - - ">="
256
+ - !ruby/object:Gem::Version
257
+ version: '0'
258
+ requirements: []
259
+ rubygems_version: 3.6.9
260
+ specification_version: 4
261
+ summary: Legion::Extensions::Validator
262
+ test_files: []