lex-reflection 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,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Reflection::Helpers::ReflectionFactory do
4
+ describe '.new_reflection' do
5
+ it 'creates a reflection with required fields' do
6
+ r = described_class.new_reflection(
7
+ category: :prediction_calibration,
8
+ observation: 'Low accuracy detected'
9
+ )
10
+ expect(r[:reflection_id]).to be_a(String)
11
+ expect(r[:category]).to eq(:prediction_calibration)
12
+ expect(r[:observation]).to eq('Low accuracy detected')
13
+ expect(r[:severity]).to eq(:notable)
14
+ expect(r[:recommendation]).to eq(:no_action)
15
+ expect(r[:acted_on]).to be false
16
+ end
17
+
18
+ it 'raises on invalid category' do
19
+ expect { described_class.new_reflection(category: :invalid, observation: 'x') }
20
+ .to raise_error(ArgumentError, /invalid category/)
21
+ end
22
+
23
+ it 'raises on invalid severity' do
24
+ expect { described_class.new_reflection(category: :trust_drift, observation: 'x', severity: :invalid) }
25
+ .to raise_error(ArgumentError, /invalid severity/)
26
+ end
27
+ end
28
+
29
+ describe '.severity_weight' do
30
+ it 'returns 1.0 for critical' do
31
+ expect(described_class.severity_weight(:critical)).to eq(1.0)
32
+ end
33
+
34
+ it 'returns lower weights for lower severities' do
35
+ expect(described_class.severity_weight(:trivial)).to be < described_class.severity_weight(:notable)
36
+ expect(described_class.severity_weight(:notable)).to be < described_class.severity_weight(:significant)
37
+ end
38
+ end
39
+
40
+ describe '.severity_for_drop' do
41
+ it 'returns critical for large drops' do
42
+ expect(described_class.severity_for_drop(0.5)).to eq(:critical)
43
+ end
44
+
45
+ it 'returns trivial for small drops' do
46
+ expect(described_class.severity_for_drop(0.05)).to eq(:trivial)
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Reflection::Helpers::ReflectionStore do
4
+ subject(:store) { described_class.new }
5
+
6
+ let(:reflection) do
7
+ Legion::Extensions::Reflection::Helpers::ReflectionFactory.new_reflection(
8
+ category: :prediction_calibration,
9
+ observation: 'Low accuracy',
10
+ severity: :significant,
11
+ recommendation: :increase_curiosity
12
+ )
13
+ end
14
+
15
+ describe '#store and #get' do
16
+ it 'stores and retrieves a reflection' do
17
+ store.store(reflection)
18
+ expect(store.get(reflection[:reflection_id])).to eq(reflection)
19
+ end
20
+
21
+ it 'increments total_generated' do
22
+ store.store(reflection)
23
+ expect(store.total_generated).to eq(1)
24
+ end
25
+ end
26
+
27
+ describe '#recent' do
28
+ it 'returns reflections in reverse chronological order' do
29
+ r1 = Legion::Extensions::Reflection::Helpers::ReflectionFactory.new_reflection(
30
+ category: :trust_drift, observation: 'first'
31
+ )
32
+ store.store(r1)
33
+ r2 = Legion::Extensions::Reflection::Helpers::ReflectionFactory.new_reflection(
34
+ category: :memory_health, observation: 'second'
35
+ )
36
+ store.store(r2)
37
+
38
+ recent = store.recent(limit: 2)
39
+ expect(recent.first[:observation]).to eq('second')
40
+ end
41
+ end
42
+
43
+ describe '#by_category' do
44
+ it 'filters by category' do
45
+ store.store(reflection)
46
+ other = Legion::Extensions::Reflection::Helpers::ReflectionFactory.new_reflection(
47
+ category: :trust_drift, observation: 'other'
48
+ )
49
+ store.store(other)
50
+
51
+ results = store.by_category(:prediction_calibration)
52
+ expect(results.size).to eq(1)
53
+ expect(results.first[:category]).to eq(:prediction_calibration)
54
+ end
55
+ end
56
+
57
+ describe '#mark_acted_on' do
58
+ it 'marks a reflection as acted on' do
59
+ store.store(reflection)
60
+ store.mark_acted_on(reflection[:reflection_id])
61
+ expect(store.get(reflection[:reflection_id])[:acted_on]).to be true
62
+ end
63
+
64
+ it 'returns nil for unknown id' do
65
+ expect(store.mark_acted_on('nonexistent')).to be_nil
66
+ end
67
+ end
68
+
69
+ describe '#cognitive_health' do
70
+ it 'returns 1.0 when all scores are at default' do
71
+ expect(store.cognitive_health).to eq(1.0)
72
+ end
73
+
74
+ it 'decreases when category scores drop' do
75
+ store.update_category_score(:prediction_calibration, 0.3)
76
+ expect(store.cognitive_health).to be < 1.0
77
+ end
78
+ end
79
+
80
+ describe '#severity_counts' do
81
+ it 'counts reflections by severity' do
82
+ store.store(reflection)
83
+ critical = Legion::Extensions::Reflection::Helpers::ReflectionFactory.new_reflection(
84
+ category: :cognitive_load, observation: 'overload', severity: :critical
85
+ )
86
+ store.store(critical)
87
+
88
+ counts = store.severity_counts
89
+ expect(counts[:significant]).to eq(1)
90
+ expect(counts[:critical]).to eq(1)
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,204 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Reflection::Runners::Reflection do
4
+ let(:client) { Legion::Extensions::Reflection::Client.new }
5
+
6
+ describe '#reflect' do
7
+ it 'returns results with no tick data' do
8
+ result = client.reflect(tick_results: {})
9
+ expect(result[:reflections_generated]).to eq(0)
10
+ expect(result[:cognitive_health]).to eq(1.0)
11
+ end
12
+
13
+ it 'generates reflections from problematic tick results' do
14
+ result = client.reflect(tick_results: {
15
+ prediction_engine: { confidence: 0.2 },
16
+ emotional_evaluation: { stability: 0.1 },
17
+ memory_consolidation: { pruned: 90, total: 100 }
18
+ })
19
+ expect(result[:reflections_generated]).to be >= 2
20
+ expect(result[:cognitive_health]).to be < 1.0
21
+ end
22
+
23
+ it 'accumulates metric history over multiple calls' do
24
+ 5.times { client.reflect(tick_results: { prediction_engine: { confidence: 0.9 } }) }
25
+ 5.times { client.reflect(tick_results: { prediction_engine: { confidence: 0.4 } }) }
26
+
27
+ # Should detect trend
28
+ result = client.reflect(tick_results: { prediction_engine: { confidence: 0.3 } })
29
+ predictions = result[:new_reflections].select { |r| r[:category] == :prediction_calibration }
30
+ expect(predictions).not_to be_empty
31
+ end
32
+ end
33
+
34
+ describe '#cognitive_health' do
35
+ it 'returns full health with no data' do
36
+ result = client.cognitive_health
37
+ expect(result[:health]).to eq(1.0)
38
+ end
39
+
40
+ it 'degrades with bad tick results' do
41
+ client.reflect(tick_results: {
42
+ prediction_engine: { confidence: 0.2 },
43
+ emotional_evaluation: { stability: 0.1 }
44
+ })
45
+ result = client.cognitive_health
46
+ expect(result[:health]).to be < 1.0
47
+ expect(result[:category_scores][:prediction_calibration]).to eq(0.2)
48
+ end
49
+ end
50
+
51
+ describe '#recent_reflections' do
52
+ it 'returns recent reflections' do
53
+ client.reflect(tick_results: { prediction_engine: { confidence: 0.1 } })
54
+ result = client.recent_reflections(limit: 5)
55
+ expect(result[:reflections]).not_to be_empty
56
+ end
57
+ end
58
+
59
+ describe '#reflections_by_category' do
60
+ it 'filters by category' do
61
+ client.reflect(tick_results: {
62
+ prediction_engine: { confidence: 0.1 },
63
+ emotional_evaluation: { stability: 0.1 }
64
+ })
65
+ result = client.reflections_by_category(category: :emotional_stability)
66
+ result[:reflections].each do |r|
67
+ expect(r[:category]).to eq(:emotional_stability)
68
+ end
69
+ end
70
+ end
71
+
72
+ describe '#adapt' do
73
+ it 'marks a reflection as acted upon' do
74
+ client.reflect(tick_results: { prediction_engine: { confidence: 0.1 } })
75
+ reflections = client.recent_reflections[:reflections]
76
+ id = reflections.first[:reflection_id]
77
+
78
+ result = client.adapt(reflection_id: id)
79
+ expect(result[:adapted]).to be true
80
+ end
81
+
82
+ it 'returns error for unknown id' do
83
+ result = client.adapt(reflection_id: 'nonexistent')
84
+ expect(result[:error]).to eq(:not_found)
85
+ end
86
+ end
87
+
88
+ describe '#reflection_stats' do
89
+ it 'returns comprehensive stats' do
90
+ client.reflect(tick_results: { prediction_engine: { confidence: 0.1 } })
91
+ stats = client.reflection_stats
92
+ expect(stats[:total_generated]).to be >= 1
93
+ expect(stats[:cognitive_health]).to be_a(Float)
94
+ expect(stats[:severity_counts]).to be_a(Hash)
95
+ end
96
+ end
97
+
98
+ describe '#reflect with LLM enhancement' do
99
+ let(:fake_chat) { double }
100
+ let(:llm_observation) { 'My prediction confidence is critically low at 10%, strongly correlated with declining memory health.' }
101
+ let(:fake_response) do
102
+ double(content: <<~TEXT)
103
+ EMOTION: Emotional state appears stable this tick.
104
+ PREDICTION: #{llm_observation}
105
+ MEMORY: Memory health is nominal.
106
+ TRUST: Trust scores unchanged.
107
+ CURIOSITY: Curiosity levels normal.
108
+ IDENTITY: Identity entropy within bounds.
109
+ TEXT
110
+ end
111
+
112
+ before do
113
+ stub_const('Legion::LLM', double(respond_to?: true, started?: true))
114
+ allow(Legion::LLM).to receive(:chat).and_return(fake_chat)
115
+ allow(fake_chat).to receive(:with_instructions)
116
+ allow(fake_chat).to receive(:ask).and_return(fake_response)
117
+ end
118
+
119
+ it 'replaces mechanical observation text with LLM-generated text' do
120
+ result = client.reflect(tick_results: { prediction_engine: { confidence: 0.1 } })
121
+ prediction_reflection = result[:new_reflections].find { |r| r[:category] == :prediction_calibration }
122
+ expect(prediction_reflection).not_to be_nil
123
+ expect(prediction_reflection[:observation]).to include('critically low')
124
+ end
125
+
126
+ it 'does not change recommendation symbols when using LLM' do
127
+ result = client.reflect(tick_results: { prediction_engine: { confidence: 0.1 } })
128
+ prediction_reflection = result[:new_reflections].find { |r| r[:category] == :prediction_calibration }
129
+ expect(prediction_reflection).not_to be_nil
130
+ expect(prediction_reflection[:recommendation]).to eq(:increase_curiosity)
131
+ end
132
+
133
+ it 'falls back to mechanical observations when LLM returns nil' do
134
+ allow(fake_chat).to receive(:ask).and_return(double(content: nil))
135
+
136
+ result = client.reflect(tick_results: {
137
+ prediction_engine: { confidence: 0.2 },
138
+ emotional_evaluation: { stability: 0.1 }
139
+ })
140
+ expect(result[:reflections_generated]).to be >= 1
141
+ # Mechanical observations still present (LLM returned nil)
142
+ result[:new_reflections].each do |r|
143
+ expect(r[:observation]).to be_a(String)
144
+ expect(r[:observation]).not_to be_empty
145
+ end
146
+ end
147
+ end
148
+
149
+ describe '#reflect_on_dream' do
150
+ context 'without LLM' do
151
+ it 'uses mechanical fallback for empty dream results' do
152
+ result = client.reflect_on_dream(dream_results: {})
153
+ expect(result[:reflection]).to eq('Dream cycle completed.')
154
+ expect(result[:source]).to eq(:mechanical)
155
+ end
156
+
157
+ it 'builds mechanical reflection from dream phase data' do
158
+ result = client.reflect_on_dream(dream_results: {
159
+ memory_audit: { decayed: 3, unresolved_count: 2 },
160
+ contradiction_resolution: { detected: 1, resolved: 1 },
161
+ agenda_formation: { agenda_items: 3 }
162
+ })
163
+ expect(result[:reflection]).to include('Memory audit')
164
+ expect(result[:source]).to eq(:mechanical)
165
+ end
166
+ end
167
+
168
+ context 'with LLM available' do
169
+ let(:fake_chat) { double }
170
+ let(:fake_response) do
171
+ double(content: <<~TEXT)
172
+ REFLECTION: The dream cycle consolidated 3 traces and resolved a contradiction in the identity domain. I notice a strengthening of procedural memory pathways.
173
+ TEXT
174
+ end
175
+
176
+ before do
177
+ stub_const('Legion::LLM', double(respond_to?: true, started?: true))
178
+ allow(Legion::LLM).to receive(:chat).and_return(fake_chat)
179
+ allow(fake_chat).to receive(:with_instructions)
180
+ allow(fake_chat).to receive(:ask).and_return(fake_response)
181
+ end
182
+
183
+ it 'uses LLM-generated reflection when available' do
184
+ result = client.reflect_on_dream(dream_results: {
185
+ memory_audit: { decayed: 3, unresolved_count: 2 },
186
+ contradiction_resolution: { detected: 1, resolved: 1 }
187
+ })
188
+ expect(result[:reflection]).to include('consolidated 3 traces')
189
+ expect(result[:source]).to eq(:llm)
190
+ end
191
+
192
+ it 'falls back to mechanical when LLM returns nil' do
193
+ allow(fake_chat).to receive(:ask).and_return(double(content: nil))
194
+
195
+ result = client.reflect_on_dream(dream_results: {
196
+ memory_audit: { decayed: 1, unresolved_count: 0 }
197
+ })
198
+ expect(result[:source]).to eq(:mechanical)
199
+ expect(result[:reflection]).to be_a(String)
200
+ expect(result[:reflection]).not_to be_empty
201
+ end
202
+ end
203
+ end
204
+ 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/reflection'
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,80 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: lex-reflection
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: Metacognitive self-monitoring for brain-modeled agentic AI
27
+ email:
28
+ - matthewdiverson@gmail.com
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files: []
32
+ files:
33
+ - Gemfile
34
+ - LICENSE
35
+ - README.md
36
+ - lex-reflection.gemspec
37
+ - lib/legion/extensions/reflection.rb
38
+ - lib/legion/extensions/reflection/client.rb
39
+ - lib/legion/extensions/reflection/helpers/constants.rb
40
+ - lib/legion/extensions/reflection/helpers/llm_enhancer.rb
41
+ - lib/legion/extensions/reflection/helpers/monitors.rb
42
+ - lib/legion/extensions/reflection/helpers/reflection.rb
43
+ - lib/legion/extensions/reflection/helpers/reflection_store.rb
44
+ - lib/legion/extensions/reflection/runners/reflection.rb
45
+ - lib/legion/extensions/reflection/version.rb
46
+ - spec/legion/extensions/reflection/client_spec.rb
47
+ - spec/legion/extensions/reflection/helpers/llm_enhancer_spec.rb
48
+ - spec/legion/extensions/reflection/helpers/monitors_spec.rb
49
+ - spec/legion/extensions/reflection/helpers/reflection_spec.rb
50
+ - spec/legion/extensions/reflection/helpers/reflection_store_spec.rb
51
+ - spec/legion/extensions/reflection/runners/reflection_spec.rb
52
+ - spec/spec_helper.rb
53
+ homepage: https://github.com/LegionIO/lex-reflection
54
+ licenses:
55
+ - MIT
56
+ metadata:
57
+ homepage_uri: https://github.com/LegionIO/lex-reflection
58
+ source_code_uri: https://github.com/LegionIO/lex-reflection
59
+ documentation_uri: https://github.com/LegionIO/lex-reflection
60
+ changelog_uri: https://github.com/LegionIO/lex-reflection
61
+ bug_tracker_uri: https://github.com/LegionIO/lex-reflection/issues
62
+ rubygems_mfa_required: 'true'
63
+ rdoc_options: []
64
+ require_paths:
65
+ - lib
66
+ required_ruby_version: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ version: '3.4'
71
+ required_rubygems_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ requirements: []
77
+ rubygems_version: 3.6.9
78
+ specification_version: 4
79
+ summary: LEX Reflection
80
+ test_files: []