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.
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/conflict/client'
4
+
5
+ RSpec.describe Legion::Extensions::Conflict::Runners::Conflict do
6
+ let(:client) { Legion::Extensions::Conflict::Client.new }
7
+
8
+ describe '#register_conflict' do
9
+ it 'creates a conflict with recommended posture' do
10
+ result = client.register_conflict(parties: %w[agent human], severity: :high, description: 'disagreement')
11
+ expect(result[:conflict_id]).to match(/\A[0-9a-f-]{36}\z/)
12
+ expect(result[:posture]).to eq(:persistent_engagement)
13
+ end
14
+
15
+ it 'recommends stubborn_presence for critical' do
16
+ result = client.register_conflict(parties: %w[agent human], severity: :critical, description: 'safety issue')
17
+ expect(result[:posture]).to eq(:stubborn_presence)
18
+ end
19
+
20
+ it 'rejects invalid severity' do
21
+ result = client.register_conflict(parties: [], severity: :invalid, description: 'test')
22
+ expect(result[:error]).to eq(:invalid_severity)
23
+ end
24
+ end
25
+
26
+ describe '#add_exchange' do
27
+ it 'records an exchange' do
28
+ c = client.register_conflict(parties: %w[a b], severity: :medium, description: 'test')
29
+ result = client.add_exchange(conflict_id: c[:conflict_id], speaker: 'a', message: 'I disagree')
30
+ expect(result[:recorded]).to be true
31
+ end
32
+ end
33
+
34
+ describe '#resolve_conflict' do
35
+ it 'resolves a conflict' do
36
+ c = client.register_conflict(parties: %w[a b], severity: :low, description: 'test')
37
+ result = client.resolve_conflict(conflict_id: c[:conflict_id], outcome: :compromise)
38
+ expect(result[:resolved]).to be true
39
+ end
40
+ end
41
+
42
+ describe '#active_conflicts' do
43
+ it 'lists active conflicts' do
44
+ client.register_conflict(parties: %w[a b], severity: :low, description: 'test')
45
+ result = client.active_conflicts
46
+ expect(result[:count]).to eq(1)
47
+ end
48
+ end
49
+
50
+ describe '#recommended_posture' do
51
+ it 'returns posture for severity' do
52
+ result = client.recommended_posture(severity: :critical)
53
+ expect(result[:posture]).to eq(:stubborn_presence)
54
+ end
55
+ end
56
+
57
+ describe '#check_stale_conflicts' do
58
+ it 'returns zero stale when no conflicts exist' do
59
+ result = client.check_stale_conflicts
60
+ expect(result[:checked]).to eq(0)
61
+ expect(result[:stale_count]).to eq(0)
62
+ expect(result[:stale_ids]).to eq([])
63
+ end
64
+
65
+ it 'returns zero stale for recently created conflicts' do
66
+ client.register_conflict(parties: %w[a b], severity: :low, description: 'fresh')
67
+ result = client.check_stale_conflicts
68
+ expect(result[:stale_count]).to eq(0)
69
+ end
70
+
71
+ it 'detects stale conflicts older than STALE_CONFLICT_TIMEOUT' do
72
+ c = client.register_conflict(parties: %w[a b], severity: :medium, description: 'old')
73
+ # Backdate the created_at timestamp
74
+ conflict = client.instance_variable_get(:@conflict_log).conflicts[c[:conflict_id]]
75
+ conflict[:created_at] = Time.now.utc - (Legion::Extensions::Conflict::Helpers::Severity::STALE_CONFLICT_TIMEOUT + 1)
76
+ result = client.check_stale_conflicts
77
+ expect(result[:stale_count]).to eq(1)
78
+ expect(result[:stale_ids]).to include(c[:conflict_id])
79
+ end
80
+
81
+ it 'does not include resolved conflicts in stale check' do
82
+ c = client.register_conflict(parties: %w[a b], severity: :low, description: 'resolved')
83
+ conflict = client.instance_variable_get(:@conflict_log).conflicts[c[:conflict_id]]
84
+ conflict[:created_at] = Time.now.utc - (Legion::Extensions::Conflict::Helpers::Severity::STALE_CONFLICT_TIMEOUT + 1)
85
+ client.resolve_conflict(conflict_id: c[:conflict_id], outcome: :closed)
86
+ result = client.check_stale_conflicts
87
+ expect(result[:stale_count]).to eq(0)
88
+ end
89
+
90
+ context 'with LLM available' do
91
+ let(:fake_chat) { double }
92
+ let(:fake_analysis_response) do
93
+ double(content: "RECOMMENDATION: escalate\nANALYSIS: The conflict has stalled and needs governance intervention.")
94
+ end
95
+
96
+ before do
97
+ stub_const('Legion::LLM', double(respond_to?: true, started?: true))
98
+ allow(Legion::LLM).to receive(:chat).and_return(fake_chat)
99
+ allow(fake_chat).to receive(:with_instructions)
100
+ allow(fake_chat).to receive(:ask).and_return(fake_analysis_response)
101
+ end
102
+
103
+ it 'includes LLM analysis in the stale system exchange message' do
104
+ c = client.register_conflict(parties: %w[a b], severity: :high, description: 'ongoing issue')
105
+ conflict = client.instance_variable_get(:@conflict_log).conflicts[c[:conflict_id]]
106
+ conflict[:created_at] = Time.now.utc - (Legion::Extensions::Conflict::Helpers::Severity::STALE_CONFLICT_TIMEOUT + 1)
107
+
108
+ client.check_stale_conflicts
109
+
110
+ exchanges = client.instance_variable_get(:@conflict_log).conflicts[c[:conflict_id]][:exchanges]
111
+ expect(exchanges).not_to be_empty
112
+ system_msg = exchanges.find { |e| e[:speaker] == :system }
113
+ expect(system_msg).not_to be_nil
114
+ expect(system_msg[:message]).to include('governance intervention')
115
+ end
116
+ end
117
+ end
118
+
119
+ describe '#resolve_conflict with LLM' do
120
+ let(:fake_chat) { double }
121
+ let(:fake_resolution_response) do
122
+ double(content: <<~TEXT)
123
+ OUTCOME: resolved
124
+ NOTES: Both parties reached a compromise after reviewing the evidence. Next steps include documenting the outcome and monitoring for recurrence.
125
+ TEXT
126
+ end
127
+
128
+ before do
129
+ stub_const('Legion::LLM', double(respond_to?: true, started?: true))
130
+ allow(Legion::LLM).to receive(:chat).and_return(fake_chat)
131
+ allow(fake_chat).to receive(:with_instructions)
132
+ allow(fake_chat).to receive(:ask).and_return(fake_resolution_response)
133
+ end
134
+
135
+ it 'uses LLM-generated notes when no resolution_notes provided' do
136
+ c = client.register_conflict(parties: %w[a b], severity: :medium, description: 'test conflict')
137
+ client.resolve_conflict(conflict_id: c[:conflict_id], outcome: :compromise)
138
+
139
+ conflict = client.instance_variable_get(:@conflict_log).conflicts[c[:conflict_id]]
140
+ expect(conflict[:resolution_notes]).to include('compromise')
141
+ end
142
+
143
+ it 'preserves caller-provided resolution_notes over LLM' do
144
+ c = client.register_conflict(parties: %w[a b], severity: :medium, description: 'test conflict')
145
+ client.resolve_conflict(conflict_id: c[:conflict_id], outcome: :compromise, resolution_notes: 'manual notes')
146
+
147
+ conflict = client.instance_variable_get(:@conflict_log).conflicts[c[:conflict_id]]
148
+ expect(conflict[:resolution_notes]).to eq('manual notes')
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+
5
+ module Legion
6
+ module Logging
7
+ def self.debug(_msg); end
8
+ def self.info(_msg); end
9
+ def self.warn(_msg); end
10
+ def self.error(_msg); end
11
+ end
12
+ end
13
+
14
+ require 'legion/extensions/conflict'
15
+
16
+ RSpec.configure do |config|
17
+ config.example_status_persistence_file_path = '.rspec_status'
18
+ config.disable_monkey_patching!
19
+ config.expect_with(:rspec) { |c| c.syntax = :expect }
20
+ end
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lex-conflict
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
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: legion-gaia
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :development
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
26
+ description: Conflict resolution with severity levels and postures for brain-modeled
27
+ agentic AI
28
+ email:
29
+ - matthewdiverson@gmail.com
30
+ executables: []
31
+ extensions: []
32
+ extra_rdoc_files: []
33
+ files:
34
+ - Gemfile
35
+ - lex-conflict.gemspec
36
+ - lib/legion/extensions/conflict.rb
37
+ - lib/legion/extensions/conflict/actors/stale_check.rb
38
+ - lib/legion/extensions/conflict/client.rb
39
+ - lib/legion/extensions/conflict/helpers/conflict_log.rb
40
+ - lib/legion/extensions/conflict/helpers/llm_enhancer.rb
41
+ - lib/legion/extensions/conflict/helpers/severity.rb
42
+ - lib/legion/extensions/conflict/runners/conflict.rb
43
+ - lib/legion/extensions/conflict/version.rb
44
+ - spec/legion/extensions/conflict/actors/stale_check_spec.rb
45
+ - spec/legion/extensions/conflict/client_spec.rb
46
+ - spec/legion/extensions/conflict/helpers/conflict_log_spec.rb
47
+ - spec/legion/extensions/conflict/helpers/llm_enhancer_spec.rb
48
+ - spec/legion/extensions/conflict/helpers/severity_spec.rb
49
+ - spec/legion/extensions/conflict/runners/conflict_spec.rb
50
+ - spec/spec_helper.rb
51
+ homepage: https://github.com/LegionIO/lex-conflict
52
+ licenses:
53
+ - MIT
54
+ metadata:
55
+ homepage_uri: https://github.com/LegionIO/lex-conflict
56
+ source_code_uri: https://github.com/LegionIO/lex-conflict
57
+ documentation_uri: https://github.com/LegionIO/lex-conflict
58
+ changelog_uri: https://github.com/LegionIO/lex-conflict
59
+ bug_tracker_uri: https://github.com/LegionIO/lex-conflict/issues
60
+ rubygems_mfa_required: 'true'
61
+ rdoc_options: []
62
+ require_paths:
63
+ - lib
64
+ required_ruby_version: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '3.4'
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ requirements: []
75
+ rubygems_version: 3.6.9
76
+ specification_version: 4
77
+ summary: LEX Conflict
78
+ test_files: []