lex-conflict 0.1.1
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/Gemfile +10 -0
- data/lex-conflict.gemspec +29 -0
- data/lib/legion/extensions/conflict/actors/stale_check.rb +41 -0
- data/lib/legion/extensions/conflict/client.rb +23 -0
- data/lib/legion/extensions/conflict/helpers/conflict_log.rb +66 -0
- data/lib/legion/extensions/conflict/helpers/llm_enhancer.rb +126 -0
- data/lib/legion/extensions/conflict/helpers/severity.rb +43 -0
- data/lib/legion/extensions/conflict/runners/conflict.rb +108 -0
- data/lib/legion/extensions/conflict/version.rb +9 -0
- data/lib/legion/extensions/conflict.rb +15 -0
- data/spec/legion/extensions/conflict/actors/stale_check_spec.rb +45 -0
- data/spec/legion/extensions/conflict/client_spec.rb +15 -0
- data/spec/legion/extensions/conflict/helpers/conflict_log_spec.rb +232 -0
- data/spec/legion/extensions/conflict/helpers/llm_enhancer_spec.rb +189 -0
- data/spec/legion/extensions/conflict/helpers/severity_spec.rb +215 -0
- data/spec/legion/extensions/conflict/runners/conflict_spec.rb +151 -0
- data/spec/spec_helper.rb +20 -0
- metadata +78 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 65f2f15e0488f24bca7c7c48246a8bc580907a00c182d79a8ae0ab37b5f71dd8
|
|
4
|
+
data.tar.gz: 8e104dc880a3d63fa9a4484b6d191e585db5beffea4187708cd4fd5f9724b8f2
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 8444218ce37f8cf5108e6eb9783dcad8a48f02971ed2ede1718e405712e9448998ee128b198fac613e3dafc56f67a941248de16819b8ec665996bc822c8582c3
|
|
7
|
+
data.tar.gz: ea7cd9b0ef64a4ee8e4eaaef37414a455e503676653524798cb5b1a90ccb796ec0cba9d563ba0000268680169b3943022c949523fea850cf993445bdfcd283ea
|
data/Gemfile
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'lib/legion/extensions/conflict/version'
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = 'lex-conflict'
|
|
7
|
+
spec.version = Legion::Extensions::Conflict::VERSION
|
|
8
|
+
spec.authors = ['Esity']
|
|
9
|
+
spec.email = ['matthewdiverson@gmail.com']
|
|
10
|
+
|
|
11
|
+
spec.summary = 'LEX Conflict'
|
|
12
|
+
spec.description = 'Conflict resolution with severity levels and postures for brain-modeled agentic AI'
|
|
13
|
+
spec.homepage = 'https://github.com/LegionIO/lex-conflict'
|
|
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-conflict'
|
|
19
|
+
spec.metadata['documentation_uri'] = 'https://github.com/LegionIO/lex-conflict'
|
|
20
|
+
spec.metadata['changelog_uri'] = 'https://github.com/LegionIO/lex-conflict'
|
|
21
|
+
spec.metadata['bug_tracker_uri'] = 'https://github.com/LegionIO/lex-conflict/issues'
|
|
22
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
23
|
+
|
|
24
|
+
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
25
|
+
Dir.glob('{lib,spec}/**/*') + %w[lex-conflict.gemspec Gemfile]
|
|
26
|
+
end
|
|
27
|
+
spec.require_paths = ['lib']
|
|
28
|
+
spec.add_development_dependency 'legion-gaia'
|
|
29
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/actors/every'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Extensions
|
|
7
|
+
module Conflict
|
|
8
|
+
module Actor
|
|
9
|
+
class StaleCheck < Legion::Extensions::Actors::Every
|
|
10
|
+
def runner_class
|
|
11
|
+
Legion::Extensions::Conflict::Runners::Conflict
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def runner_function
|
|
15
|
+
'check_stale_conflicts'
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def time
|
|
19
|
+
3600
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def run_now?
|
|
23
|
+
false
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def use_runner?
|
|
27
|
+
false
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def check_subtask?
|
|
31
|
+
false
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def generate_task?
|
|
35
|
+
false
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/conflict/helpers/severity'
|
|
4
|
+
require 'legion/extensions/conflict/helpers/conflict_log'
|
|
5
|
+
require 'legion/extensions/conflict/runners/conflict'
|
|
6
|
+
|
|
7
|
+
module Legion
|
|
8
|
+
module Extensions
|
|
9
|
+
module Conflict
|
|
10
|
+
class Client
|
|
11
|
+
include Runners::Conflict
|
|
12
|
+
|
|
13
|
+
def initialize(**)
|
|
14
|
+
@conflict_log = Helpers::ConflictLog.new
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
attr_reader :conflict_log
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
|
|
5
|
+
module Legion
|
|
6
|
+
module Extensions
|
|
7
|
+
module Conflict
|
|
8
|
+
module Helpers
|
|
9
|
+
class ConflictLog
|
|
10
|
+
attr_reader :conflicts
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
@conflicts = {}
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def record(parties:, severity:, description:, posture: nil)
|
|
17
|
+
id = SecureRandom.uuid
|
|
18
|
+
@conflicts[id] = {
|
|
19
|
+
conflict_id: id,
|
|
20
|
+
parties: parties,
|
|
21
|
+
severity: severity,
|
|
22
|
+
posture: posture || Severity.recommended_posture(severity),
|
|
23
|
+
description: description,
|
|
24
|
+
status: :active,
|
|
25
|
+
outcome: nil,
|
|
26
|
+
created_at: Time.now.utc,
|
|
27
|
+
resolved_at: nil,
|
|
28
|
+
exchanges: []
|
|
29
|
+
}
|
|
30
|
+
id
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def add_exchange(conflict_id, speaker:, message:)
|
|
34
|
+
conflict = @conflicts[conflict_id]
|
|
35
|
+
return nil unless conflict
|
|
36
|
+
|
|
37
|
+
conflict[:exchanges] << { speaker: speaker, message: message, at: Time.now.utc }
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def resolve(conflict_id, outcome:, resolution_notes: nil)
|
|
41
|
+
conflict = @conflicts[conflict_id]
|
|
42
|
+
return nil unless conflict
|
|
43
|
+
|
|
44
|
+
conflict[:status] = :resolved
|
|
45
|
+
conflict[:outcome] = outcome
|
|
46
|
+
conflict[:resolution_notes] = resolution_notes
|
|
47
|
+
conflict[:resolved_at] = Time.now.utc
|
|
48
|
+
conflict
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def active_conflicts
|
|
52
|
+
@conflicts.values.select { |c| c[:status] == :active }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def get(conflict_id)
|
|
56
|
+
@conflicts[conflict_id]
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def count
|
|
60
|
+
@conflicts.size
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Conflict
|
|
6
|
+
module Helpers
|
|
7
|
+
module LlmEnhancer
|
|
8
|
+
SYSTEM_PROMPT = <<~PROMPT
|
|
9
|
+
You are the conflict mediation processor for an autonomous AI agent built on LegionIO.
|
|
10
|
+
You analyze disagreements between the agent and human partners, then suggest resolution approaches.
|
|
11
|
+
Be neutral, constructive, and specific. Focus on finding common ground and actionable next steps.
|
|
12
|
+
Do not take sides. Identify the underlying needs behind each position.
|
|
13
|
+
PROMPT
|
|
14
|
+
|
|
15
|
+
module_function
|
|
16
|
+
|
|
17
|
+
def available?
|
|
18
|
+
defined?(Legion::LLM) && Legion::LLM.respond_to?(:started?) && Legion::LLM.started?
|
|
19
|
+
rescue StandardError
|
|
20
|
+
false
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def suggest_resolution(description:, severity:, exchanges:)
|
|
24
|
+
prompt = build_suggest_resolution_prompt(description: description, severity: severity, exchanges: exchanges)
|
|
25
|
+
response = llm_ask(prompt)
|
|
26
|
+
parse_suggest_resolution_response(response)
|
|
27
|
+
rescue StandardError => e
|
|
28
|
+
Legion::Logging.warn "[conflict:llm] suggest_resolution failed: #{e.message}"
|
|
29
|
+
nil
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def analyze_stale_conflict(description:, severity:, age_hours:, exchange_count:)
|
|
33
|
+
prompt = build_analyze_stale_conflict_prompt(
|
|
34
|
+
description: description,
|
|
35
|
+
severity: severity,
|
|
36
|
+
age_hours: age_hours,
|
|
37
|
+
exchange_count: exchange_count
|
|
38
|
+
)
|
|
39
|
+
response = llm_ask(prompt)
|
|
40
|
+
parse_analyze_stale_conflict_response(response)
|
|
41
|
+
rescue StandardError => e
|
|
42
|
+
Legion::Logging.warn "[conflict:llm] analyze_stale_conflict failed: #{e.message}"
|
|
43
|
+
nil
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# --- Private helpers ---
|
|
47
|
+
|
|
48
|
+
def llm_ask(prompt)
|
|
49
|
+
chat = Legion::LLM.chat
|
|
50
|
+
chat.with_instructions(SYSTEM_PROMPT)
|
|
51
|
+
chat.ask(prompt)
|
|
52
|
+
end
|
|
53
|
+
private_class_method :llm_ask
|
|
54
|
+
|
|
55
|
+
def build_suggest_resolution_prompt(description:, severity:, exchanges:)
|
|
56
|
+
exchange_lines = exchanges.map { |e| "[#{e[:speaker]}]: #{e[:message]}" }.join("\n")
|
|
57
|
+
|
|
58
|
+
<<~PROMPT
|
|
59
|
+
Analyze this conflict and suggest a resolution.
|
|
60
|
+
|
|
61
|
+
DESCRIPTION: #{description}
|
|
62
|
+
SEVERITY: #{severity}
|
|
63
|
+
EXCHANGE HISTORY (#{exchanges.size} exchanges):
|
|
64
|
+
#{exchange_lines}
|
|
65
|
+
|
|
66
|
+
Suggest a constructive resolution approach.
|
|
67
|
+
|
|
68
|
+
Format EXACTLY as:
|
|
69
|
+
OUTCOME: resolved | deferred | escalated
|
|
70
|
+
NOTES: <2-3 sentences describing the resolution approach and next steps>
|
|
71
|
+
PROMPT
|
|
72
|
+
end
|
|
73
|
+
private_class_method :build_suggest_resolution_prompt
|
|
74
|
+
|
|
75
|
+
def parse_suggest_resolution_response(response)
|
|
76
|
+
return nil unless response&.content
|
|
77
|
+
|
|
78
|
+
text = response.content
|
|
79
|
+
outcome_match = text.match(/OUTCOME:\s*(resolved|deferred|escalated)/i)
|
|
80
|
+
notes_match = text.match(/NOTES:\s*(.+)/im)
|
|
81
|
+
|
|
82
|
+
return nil unless outcome_match && notes_match
|
|
83
|
+
|
|
84
|
+
outcome = outcome_match.captures.first.strip.downcase.to_sym
|
|
85
|
+
notes = notes_match.captures.first.strip
|
|
86
|
+
|
|
87
|
+
{ resolution_notes: notes, suggested_outcome: outcome }
|
|
88
|
+
end
|
|
89
|
+
private_class_method :parse_suggest_resolution_response
|
|
90
|
+
|
|
91
|
+
def build_analyze_stale_conflict_prompt(description:, severity:, age_hours:, exchange_count:)
|
|
92
|
+
<<~PROMPT
|
|
93
|
+
A conflict has been unresolved for #{age_hours.round(1)} hours with #{exchange_count} exchanges.
|
|
94
|
+
|
|
95
|
+
DESCRIPTION: #{description}
|
|
96
|
+
SEVERITY: #{severity}
|
|
97
|
+
|
|
98
|
+
Recommend how to proceed with this stale conflict.
|
|
99
|
+
|
|
100
|
+
Format EXACTLY as:
|
|
101
|
+
RECOMMENDATION: escalate | retry | close
|
|
102
|
+
ANALYSIS: <2-3 sentences explaining the recommendation>
|
|
103
|
+
PROMPT
|
|
104
|
+
end
|
|
105
|
+
private_class_method :build_analyze_stale_conflict_prompt
|
|
106
|
+
|
|
107
|
+
def parse_analyze_stale_conflict_response(response)
|
|
108
|
+
return nil unless response&.content
|
|
109
|
+
|
|
110
|
+
text = response.content
|
|
111
|
+
rec_match = text.match(/RECOMMENDATION:\s*(escalate|retry|close)/i)
|
|
112
|
+
analysis_match = text.match(/ANALYSIS:\s*(.+)/im)
|
|
113
|
+
|
|
114
|
+
return nil unless rec_match && analysis_match
|
|
115
|
+
|
|
116
|
+
recommendation = rec_match.captures.first.strip.downcase.to_sym
|
|
117
|
+
analysis = analysis_match.captures.first.strip
|
|
118
|
+
|
|
119
|
+
{ analysis: analysis, recommendation: recommendation }
|
|
120
|
+
end
|
|
121
|
+
private_class_method :parse_analyze_stale_conflict_response
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Conflict
|
|
6
|
+
module Helpers
|
|
7
|
+
module Severity
|
|
8
|
+
LEVELS = %i[low medium high critical].freeze
|
|
9
|
+
POSTURES = %i[speak_once persistent_engagement stubborn_presence].freeze
|
|
10
|
+
|
|
11
|
+
# Posture selection thresholds
|
|
12
|
+
PERSISTENT_THRESHOLD = :high
|
|
13
|
+
STUBBORN_THRESHOLD = :critical
|
|
14
|
+
|
|
15
|
+
LEVEL_ORDER = { low: 0, medium: 1, high: 2, critical: 3 }.freeze
|
|
16
|
+
STALE_CONFLICT_TIMEOUT = 86_400 # 24 hours
|
|
17
|
+
|
|
18
|
+
module_function
|
|
19
|
+
|
|
20
|
+
def valid_level?(level)
|
|
21
|
+
LEVELS.include?(level)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def valid_posture?(posture)
|
|
25
|
+
POSTURES.include?(posture)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def recommended_posture(severity)
|
|
29
|
+
case severity
|
|
30
|
+
when :critical then :stubborn_presence
|
|
31
|
+
when :high then :persistent_engagement
|
|
32
|
+
else :speak_once
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def severity_gte?(left, right)
|
|
37
|
+
LEVEL_ORDER.fetch(left, 0) >= LEVEL_ORDER.fetch(right, 0)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Conflict
|
|
6
|
+
module Runners
|
|
7
|
+
module Conflict
|
|
8
|
+
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
|
|
9
|
+
Legion::Extensions::Helpers.const_defined?(:Lex)
|
|
10
|
+
|
|
11
|
+
def register_conflict(parties:, severity:, description:, **)
|
|
12
|
+
return { error: :invalid_severity, valid: Helpers::Severity::LEVELS } unless Helpers::Severity.valid_level?(severity)
|
|
13
|
+
|
|
14
|
+
id = conflict_log.record(parties: parties, severity: severity, description: description)
|
|
15
|
+
conflict = conflict_log.get(id)
|
|
16
|
+
Legion::Logging.info "[conflict] registered: id=#{id[0..7]} severity=#{severity} posture=#{conflict[:posture]} parties=#{parties.join(',')}"
|
|
17
|
+
{ conflict_id: id, severity: severity, posture: conflict[:posture] }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def add_exchange(conflict_id:, speaker:, message:, **)
|
|
21
|
+
result = conflict_log.add_exchange(conflict_id, speaker: speaker, message: message)
|
|
22
|
+
if result
|
|
23
|
+
Legion::Logging.debug "[conflict] exchange: id=#{conflict_id[0..7]} speaker=#{speaker}"
|
|
24
|
+
{ recorded: true }
|
|
25
|
+
else
|
|
26
|
+
Legion::Logging.debug "[conflict] exchange failed: id=#{conflict_id[0..7]} not found"
|
|
27
|
+
{ error: :not_found }
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def resolve_conflict(conflict_id:, outcome:, resolution_notes: nil, **)
|
|
32
|
+
conflict = conflict_log.get(conflict_id)
|
|
33
|
+
unless conflict
|
|
34
|
+
Legion::Logging.debug "[conflict] resolve failed: id=#{conflict_id[0..7]} not found"
|
|
35
|
+
return { error: :not_found }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
if resolution_notes.nil? && Helpers::LlmEnhancer.available?
|
|
39
|
+
llm_result = Helpers::LlmEnhancer.suggest_resolution(
|
|
40
|
+
description: conflict[:description],
|
|
41
|
+
severity: conflict[:severity],
|
|
42
|
+
exchanges: conflict[:exchanges]
|
|
43
|
+
)
|
|
44
|
+
resolution_notes = llm_result[:resolution_notes] if llm_result
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
result = conflict_log.resolve(conflict_id, outcome: outcome, resolution_notes: resolution_notes)
|
|
48
|
+
if result
|
|
49
|
+
Legion::Logging.info "[conflict] resolved: id=#{conflict_id[0..7]} outcome=#{outcome}"
|
|
50
|
+
{ resolved: true, outcome: outcome }
|
|
51
|
+
else
|
|
52
|
+
Legion::Logging.debug "[conflict] resolve failed: id=#{conflict_id[0..7]} not found"
|
|
53
|
+
{ error: :not_found }
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def get_conflict(conflict_id:, **)
|
|
58
|
+
conflict = conflict_log.get(conflict_id)
|
|
59
|
+
Legion::Logging.debug "[conflict] get: id=#{conflict_id[0..7]} found=#{!conflict.nil?}"
|
|
60
|
+
conflict ? { found: true, conflict: conflict } : { found: false }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def active_conflicts(**)
|
|
64
|
+
conflicts = conflict_log.active_conflicts
|
|
65
|
+
Legion::Logging.debug "[conflict] active: count=#{conflicts.size}"
|
|
66
|
+
{ conflicts: conflicts, count: conflicts.size }
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def check_stale_conflicts(**)
|
|
70
|
+
active = conflict_log.active_conflicts
|
|
71
|
+
stale = active.select { |c| Time.now.utc - c[:created_at] > Helpers::Severity::STALE_CONFLICT_TIMEOUT }
|
|
72
|
+
stale.each do |c|
|
|
73
|
+
message = 'conflict marked stale — no resolution after 24h'
|
|
74
|
+
|
|
75
|
+
if Helpers::LlmEnhancer.available?
|
|
76
|
+
age_hours = (Time.now.utc - c[:created_at]) / 3600.0
|
|
77
|
+
analysis = Helpers::LlmEnhancer.analyze_stale_conflict(
|
|
78
|
+
description: c[:description],
|
|
79
|
+
severity: c[:severity],
|
|
80
|
+
age_hours: age_hours,
|
|
81
|
+
exchange_count: c[:exchanges].size
|
|
82
|
+
)
|
|
83
|
+
message = "conflict marked stale — #{analysis[:analysis]} (recommendation: #{analysis[:recommendation]})" if analysis
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
conflict_log.add_exchange(c[:conflict_id], speaker: :system, message: message)
|
|
87
|
+
end
|
|
88
|
+
stale_ids = stale.map { |c| c[:conflict_id] }
|
|
89
|
+
Legion::Logging.debug "[conflict] stale check: active=#{active.size} stale=#{stale.size}"
|
|
90
|
+
{ checked: active.size, stale_count: stale.size, stale_ids: stale_ids }
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def recommended_posture(severity:, **)
|
|
94
|
+
posture = Helpers::Severity.recommended_posture(severity)
|
|
95
|
+
Legion::Logging.debug "[conflict] posture: severity=#{severity} posture=#{posture}"
|
|
96
|
+
{ severity: severity, posture: posture }
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
private
|
|
100
|
+
|
|
101
|
+
def conflict_log
|
|
102
|
+
@conflict_log ||= Helpers::ConflictLog.new
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/conflict/version'
|
|
4
|
+
require 'legion/extensions/conflict/helpers/severity'
|
|
5
|
+
require 'legion/extensions/conflict/helpers/conflict_log'
|
|
6
|
+
require 'legion/extensions/conflict/helpers/llm_enhancer'
|
|
7
|
+
require 'legion/extensions/conflict/runners/conflict'
|
|
8
|
+
|
|
9
|
+
module Legion
|
|
10
|
+
module Extensions
|
|
11
|
+
module Conflict
|
|
12
|
+
extend Legion::Extensions::Core if Legion::Extensions.const_defined? :Core
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module Actors
|
|
6
|
+
class Every; end # rubocop:disable Lint/EmptyClass
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
$LOADED_FEATURES << 'legion/extensions/actors/every'
|
|
12
|
+
|
|
13
|
+
require_relative '../../../../../lib/legion/extensions/conflict/actors/stale_check'
|
|
14
|
+
|
|
15
|
+
RSpec.describe Legion::Extensions::Conflict::Actor::StaleCheck do
|
|
16
|
+
subject(:actor) { described_class.new }
|
|
17
|
+
|
|
18
|
+
describe '#runner_class' do
|
|
19
|
+
it { expect(actor.runner_class).to eq Legion::Extensions::Conflict::Runners::Conflict }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
describe '#runner_function' do
|
|
23
|
+
it { expect(actor.runner_function).to eq 'check_stale_conflicts' }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
describe '#time' do
|
|
27
|
+
it { expect(actor.time).to eq 3600 }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
describe '#run_now?' do
|
|
31
|
+
it { expect(actor.run_now?).to be false }
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
describe '#use_runner?' do
|
|
35
|
+
it { expect(actor.use_runner?).to be false }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
describe '#check_subtask?' do
|
|
39
|
+
it { expect(actor.check_subtask?).to be false }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
describe '#generate_task?' do
|
|
43
|
+
it { expect(actor.generate_task?).to be false }
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'legion/extensions/conflict/client'
|
|
4
|
+
|
|
5
|
+
RSpec.describe Legion::Extensions::Conflict::Client do
|
|
6
|
+
it 'responds to conflict runner methods' do
|
|
7
|
+
client = described_class.new
|
|
8
|
+
expect(client).to respond_to(:register_conflict)
|
|
9
|
+
expect(client).to respond_to(:add_exchange)
|
|
10
|
+
expect(client).to respond_to(:resolve_conflict)
|
|
11
|
+
expect(client).to respond_to(:get_conflict)
|
|
12
|
+
expect(client).to respond_to(:active_conflicts)
|
|
13
|
+
expect(client).to respond_to(:recommended_posture)
|
|
14
|
+
end
|
|
15
|
+
end
|