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 +7 -0
- data/.github/workflows/ci.yml +34 -0
- data/.gitignore +6 -0
- data/.rspec +3 -0
- data/.rspec_status +50 -0
- data/.rubocop.yml +27 -0
- data/CLAUDE.md +43 -0
- data/Gemfile +14 -0
- data/lex-validator.gemspec +44 -0
- data/lib/legion/extensions/validator/actors/validator.rb +25 -0
- data/lib/legion/extensions/validator/helpers/quality_gate.rb +46 -0
- data/lib/legion/extensions/validator/helpers/review_orchestrator.rb +195 -0
- data/lib/legion/extensions/validator/helpers/test_runner.rb +57 -0
- data/lib/legion/extensions/validator/runners/validator.rb +206 -0
- data/lib/legion/extensions/validator/transport/exchanges/validator.rb +21 -0
- data/lib/legion/extensions/validator/transport/queues/validator.rb +29 -0
- data/lib/legion/extensions/validator/version.rb +9 -0
- data/lib/legion/extensions/validator.rb +26 -0
- data/tmp/rspec_progress.txt +5 -0
- data/tmp/rspec_results.json +1 -0
- metadata +262 -0
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
data/.rspec
ADDED
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,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 @@
|
|
|
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: []
|