lex-agentic-language 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.
Files changed (126) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +6 -0
  3. data/Gemfile +5 -0
  4. data/LICENSE +21 -0
  5. data/README.md +13 -0
  6. data/lex-agentic-language.gemspec +30 -0
  7. data/lib/legion/extensions/agentic/language/conceptual_blending/client.rb +25 -0
  8. data/lib/legion/extensions/agentic/language/conceptual_blending/helpers/blend.rb +91 -0
  9. data/lib/legion/extensions/agentic/language/conceptual_blending/helpers/blending_engine.rb +171 -0
  10. data/lib/legion/extensions/agentic/language/conceptual_blending/helpers/constants.rb +35 -0
  11. data/lib/legion/extensions/agentic/language/conceptual_blending/helpers/mental_space.rb +51 -0
  12. data/lib/legion/extensions/agentic/language/conceptual_blending/runners/conceptual_blending.rb +106 -0
  13. data/lib/legion/extensions/agentic/language/conceptual_blending/version.rb +13 -0
  14. data/lib/legion/extensions/agentic/language/conceptual_blending.rb +20 -0
  15. data/lib/legion/extensions/agentic/language/conceptual_metaphor/client.rb +19 -0
  16. data/lib/legion/extensions/agentic/language/conceptual_metaphor/helpers/constants.rb +49 -0
  17. data/lib/legion/extensions/agentic/language/conceptual_metaphor/helpers/metaphor.rb +109 -0
  18. data/lib/legion/extensions/agentic/language/conceptual_metaphor/helpers/metaphor_engine.rb +154 -0
  19. data/lib/legion/extensions/agentic/language/conceptual_metaphor/runners/conceptual_metaphor.rb +107 -0
  20. data/lib/legion/extensions/agentic/language/conceptual_metaphor/version.rb +13 -0
  21. data/lib/legion/extensions/agentic/language/conceptual_metaphor.rb +19 -0
  22. data/lib/legion/extensions/agentic/language/frame_semantics/helpers/client.rb +23 -0
  23. data/lib/legion/extensions/agentic/language/frame_semantics/helpers/constants.rb +32 -0
  24. data/lib/legion/extensions/agentic/language/frame_semantics/helpers/frame.rb +109 -0
  25. data/lib/legion/extensions/agentic/language/frame_semantics/helpers/frame_engine.rb +139 -0
  26. data/lib/legion/extensions/agentic/language/frame_semantics/helpers/frame_instance.rb +51 -0
  27. data/lib/legion/extensions/agentic/language/frame_semantics/runners/frame_semantics.rb +108 -0
  28. data/lib/legion/extensions/agentic/language/frame_semantics/version.rb +13 -0
  29. data/lib/legion/extensions/agentic/language/frame_semantics.rb +20 -0
  30. data/lib/legion/extensions/agentic/language/grammar/client.rb +29 -0
  31. data/lib/legion/extensions/agentic/language/grammar/helpers/constants.rb +38 -0
  32. data/lib/legion/extensions/agentic/language/grammar/helpers/construal.rb +68 -0
  33. data/lib/legion/extensions/agentic/language/grammar/helpers/construction.rb +68 -0
  34. data/lib/legion/extensions/agentic/language/grammar/helpers/grammar_engine.rb +119 -0
  35. data/lib/legion/extensions/agentic/language/grammar/runners/cognitive_grammar.rb +100 -0
  36. data/lib/legion/extensions/agentic/language/grammar/version.rb +13 -0
  37. data/lib/legion/extensions/agentic/language/grammar.rb +20 -0
  38. data/lib/legion/extensions/agentic/language/inner_speech/client.rb +19 -0
  39. data/lib/legion/extensions/agentic/language/inner_speech/helpers/constants.rb +53 -0
  40. data/lib/legion/extensions/agentic/language/inner_speech/helpers/inner_voice.rb +141 -0
  41. data/lib/legion/extensions/agentic/language/inner_speech/helpers/speech_stream.rb +128 -0
  42. data/lib/legion/extensions/agentic/language/inner_speech/helpers/utterance.rb +90 -0
  43. data/lib/legion/extensions/agentic/language/inner_speech/runners/inner_speech.rb +87 -0
  44. data/lib/legion/extensions/agentic/language/inner_speech/version.rb +13 -0
  45. data/lib/legion/extensions/agentic/language/inner_speech.rb +20 -0
  46. data/lib/legion/extensions/agentic/language/language/client.rb +26 -0
  47. data/lib/legion/extensions/agentic/language/language/helpers/constants.rb +43 -0
  48. data/lib/legion/extensions/agentic/language/language/helpers/lexicon.rb +63 -0
  49. data/lib/legion/extensions/agentic/language/language/helpers/summarizer.rb +167 -0
  50. data/lib/legion/extensions/agentic/language/language/runners/language.rb +134 -0
  51. data/lib/legion/extensions/agentic/language/language/version.rb +13 -0
  52. data/lib/legion/extensions/agentic/language/language.rb +19 -0
  53. data/lib/legion/extensions/agentic/language/narrative_reasoning/client.rb +28 -0
  54. data/lib/legion/extensions/agentic/language/narrative_reasoning/helpers/narrative.rb +123 -0
  55. data/lib/legion/extensions/agentic/language/narrative_reasoning/helpers/narrative_engine.rb +122 -0
  56. data/lib/legion/extensions/agentic/language/narrative_reasoning/helpers/narrative_event.rb +41 -0
  57. data/lib/legion/extensions/agentic/language/narrative_reasoning/runners/narrative_reasoning.rb +122 -0
  58. data/lib/legion/extensions/agentic/language/narrative_reasoning/version.rb +13 -0
  59. data/lib/legion/extensions/agentic/language/narrative_reasoning.rb +18 -0
  60. data/lib/legion/extensions/agentic/language/narrator/client.rb +27 -0
  61. data/lib/legion/extensions/agentic/language/narrator/helpers/constants.rb +69 -0
  62. data/lib/legion/extensions/agentic/language/narrator/helpers/journal.rb +68 -0
  63. data/lib/legion/extensions/agentic/language/narrator/helpers/llm_enhancer.rb +105 -0
  64. data/lib/legion/extensions/agentic/language/narrator/helpers/prose.rb +122 -0
  65. data/lib/legion/extensions/agentic/language/narrator/helpers/synthesizer.rb +138 -0
  66. data/lib/legion/extensions/agentic/language/narrator/runners/narrator.rb +196 -0
  67. data/lib/legion/extensions/agentic/language/narrator/version.rb +13 -0
  68. data/lib/legion/extensions/agentic/language/narrator.rb +21 -0
  69. data/lib/legion/extensions/agentic/language/pragmatic_inference/client.rb +28 -0
  70. data/lib/legion/extensions/agentic/language/pragmatic_inference/helpers/constants.rb +52 -0
  71. data/lib/legion/extensions/agentic/language/pragmatic_inference/helpers/pragmatic_engine.rb +164 -0
  72. data/lib/legion/extensions/agentic/language/pragmatic_inference/helpers/utterance.rb +84 -0
  73. data/lib/legion/extensions/agentic/language/pragmatic_inference/runners/pragmatic_inference.rb +136 -0
  74. data/lib/legion/extensions/agentic/language/pragmatic_inference/version.rb +13 -0
  75. data/lib/legion/extensions/agentic/language/pragmatic_inference.rb +18 -0
  76. data/lib/legion/extensions/agentic/language/version.rb +11 -0
  77. data/lib/legion/extensions/agentic/language.rb +28 -0
  78. data/spec/legion/extensions/agentic/language/conceptual_blending/client_spec.rb +78 -0
  79. data/spec/legion/extensions/agentic/language/conceptual_blending/helpers/blend_spec.rb +141 -0
  80. data/spec/legion/extensions/agentic/language/conceptual_blending/helpers/blending_engine_spec.rb +211 -0
  81. data/spec/legion/extensions/agentic/language/conceptual_blending/helpers/mental_space_spec.rb +85 -0
  82. data/spec/legion/extensions/agentic/language/conceptual_blending/runners/conceptual_blending_spec.rb +162 -0
  83. data/spec/legion/extensions/agentic/language/conceptual_metaphor/client_spec.rb +29 -0
  84. data/spec/legion/extensions/agentic/language/conceptual_metaphor/helpers/metaphor_engine_spec.rb +166 -0
  85. data/spec/legion/extensions/agentic/language/conceptual_metaphor/helpers/metaphor_spec.rb +133 -0
  86. data/spec/legion/extensions/agentic/language/conceptual_metaphor/runners/conceptual_metaphor_spec.rb +133 -0
  87. data/spec/legion/extensions/agentic/language/frame_semantics/helpers/frame_engine_spec.rb +227 -0
  88. data/spec/legion/extensions/agentic/language/frame_semantics/helpers/frame_instance_spec.rb +83 -0
  89. data/spec/legion/extensions/agentic/language/frame_semantics/helpers/frame_spec.rb +213 -0
  90. data/spec/legion/extensions/agentic/language/frame_semantics/runners/frame_semantics_spec.rb +155 -0
  91. data/spec/legion/extensions/agentic/language/grammar/client_spec.rb +121 -0
  92. data/spec/legion/extensions/agentic/language/grammar/cognitive_grammar_spec.rb +18 -0
  93. data/spec/legion/extensions/agentic/language/grammar/helpers/constants_spec.rb +67 -0
  94. data/spec/legion/extensions/agentic/language/grammar/helpers/construal_spec.rb +124 -0
  95. data/spec/legion/extensions/agentic/language/grammar/helpers/construction_spec.rb +155 -0
  96. data/spec/legion/extensions/agentic/language/grammar/helpers/grammar_engine_spec.rb +206 -0
  97. data/spec/legion/extensions/agentic/language/grammar/runners/cognitive_grammar_spec.rb +189 -0
  98. data/spec/legion/extensions/agentic/language/inner_speech/client_spec.rb +39 -0
  99. data/spec/legion/extensions/agentic/language/inner_speech/helpers/inner_voice_spec.rb +185 -0
  100. data/spec/legion/extensions/agentic/language/inner_speech/helpers/speech_stream_spec.rb +158 -0
  101. data/spec/legion/extensions/agentic/language/inner_speech/helpers/utterance_spec.rb +121 -0
  102. data/spec/legion/extensions/agentic/language/inner_speech/runners/inner_speech_spec.rb +102 -0
  103. data/spec/legion/extensions/agentic/language/language/client_spec.rb +20 -0
  104. data/spec/legion/extensions/agentic/language/language/helpers/constants_spec.rb +31 -0
  105. data/spec/legion/extensions/agentic/language/language/helpers/lexicon_spec.rb +116 -0
  106. data/spec/legion/extensions/agentic/language/language/helpers/summarizer_spec.rb +224 -0
  107. data/spec/legion/extensions/agentic/language/language/runners/language_spec.rb +169 -0
  108. data/spec/legion/extensions/agentic/language/narrative_reasoning/client_spec.rb +19 -0
  109. data/spec/legion/extensions/agentic/language/narrative_reasoning/helpers/narrative_engine_spec.rb +182 -0
  110. data/spec/legion/extensions/agentic/language/narrative_reasoning/helpers/narrative_event_spec.rb +61 -0
  111. data/spec/legion/extensions/agentic/language/narrative_reasoning/helpers/narrative_spec.rb +168 -0
  112. data/spec/legion/extensions/agentic/language/narrative_reasoning/runners/narrative_reasoning_spec.rb +174 -0
  113. data/spec/legion/extensions/agentic/language/narrator/client_spec.rb +24 -0
  114. data/spec/legion/extensions/agentic/language/narrator/helpers/journal_spec.rb +95 -0
  115. data/spec/legion/extensions/agentic/language/narrator/helpers/llm_enhancer_spec.rb +107 -0
  116. data/spec/legion/extensions/agentic/language/narrator/helpers/prose_spec.rb +134 -0
  117. data/spec/legion/extensions/agentic/language/narrator/helpers/synthesizer_spec.rb +89 -0
  118. data/spec/legion/extensions/agentic/language/narrator/runners/narrator_llm_spec.rb +74 -0
  119. data/spec/legion/extensions/agentic/language/narrator/runners/narrator_spec.rb +126 -0
  120. data/spec/legion/extensions/agentic/language/pragmatic_inference/client_spec.rb +19 -0
  121. data/spec/legion/extensions/agentic/language/pragmatic_inference/helpers/constants_spec.rb +73 -0
  122. data/spec/legion/extensions/agentic/language/pragmatic_inference/helpers/pragmatic_engine_spec.rb +185 -0
  123. data/spec/legion/extensions/agentic/language/pragmatic_inference/helpers/utterance_spec.rb +111 -0
  124. data/spec/legion/extensions/agentic/language/pragmatic_inference/runners/pragmatic_inference_spec.rb +231 -0
  125. data/spec/spec_helper.rb +33 -0
  126. metadata +210 -0
@@ -0,0 +1,211 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Language::ConceptualBlending::Helpers::BlendingEngine do
4
+ subject(:engine) { described_class.new }
5
+
6
+ let(:space_a) do
7
+ engine.create_space(name: 'biology', domain: 'science').tap do |s|
8
+ s.add_element(name: 'virus', properties: { type: :pathogen })
9
+ s.add_element(name: 'host', properties: { type: :organism })
10
+ s.add_relation(from: 'virus', to: 'host', type: :infects)
11
+ s.add_relation(from: 'virus', to: 'host', type: :spreads)
12
+ end
13
+ end
14
+
15
+ let(:space_b) do
16
+ engine.create_space(name: 'computing', domain: 'technology').tap do |s|
17
+ s.add_element(name: 'software', properties: { type: :program })
18
+ s.add_element(name: 'network', properties: { type: :infrastructure })
19
+ s.add_relation(from: 'software', to: 'network', type: :corrupts)
20
+ s.add_relation(from: 'software', to: 'network', type: :replicates)
21
+ end
22
+ end
23
+
24
+ describe '#create_space' do
25
+ it 'returns a MentalSpace' do
26
+ space = engine.create_space(name: 'test', domain: 'misc')
27
+ expect(space).to be_a(Legion::Extensions::Agentic::Language::ConceptualBlending::Helpers::MentalSpace)
28
+ end
29
+
30
+ it 'stores space internally' do
31
+ space = engine.create_space(name: 'test', domain: 'misc')
32
+ expect(engine.to_h[:spaces_count]).to eq(1)
33
+ space # suppress unused warning
34
+ end
35
+ end
36
+
37
+ describe '#add_element_to_space' do
38
+ it 'adds element to the space' do
39
+ space = engine.create_space(name: 's', domain: 'd')
40
+ engine.add_element_to_space(space_id: space.id, name: 'elem', properties: { key: :val })
41
+ expect(space.elements['elem']).to eq({ key: :val })
42
+ end
43
+
44
+ it 'raises ArgumentError for unknown space_id' do
45
+ expect do
46
+ engine.add_element_to_space(space_id: 'bad-id', name: 'x', properties: {})
47
+ end.to raise_error(ArgumentError, /not found/)
48
+ end
49
+ end
50
+
51
+ describe '#add_relation_to_space' do
52
+ it 'adds relation to the space' do
53
+ space = engine.create_space(name: 's', domain: 'd')
54
+ engine.add_relation_to_space(space_id: space.id, from: 'a', to: 'b', type: :links)
55
+ expect(space.relations.first).to eq({ from: 'a', to: 'b', type: :links })
56
+ end
57
+
58
+ it 'raises ArgumentError for unknown space_id' do
59
+ expect do
60
+ engine.add_relation_to_space(space_id: 'bad-id', from: 'a', to: 'b', type: :x)
61
+ end.to raise_error(ArgumentError, /not found/)
62
+ end
63
+ end
64
+
65
+ describe '#blend' do
66
+ it 'creates a Blend from two spaces' do
67
+ blend = engine.blend(space_a_id: space_a.id, space_b_id: space_b.id)
68
+ expect(blend).to be_a(Legion::Extensions::Agentic::Language::ConceptualBlending::Helpers::Blend)
69
+ end
70
+
71
+ it 'sets both input space ids' do
72
+ blend = engine.blend(space_a_id: space_a.id, space_b_id: space_b.id)
73
+ expect(blend.input_space_ids).to contain_exactly(space_a.id, space_b.id)
74
+ end
75
+
76
+ it 'uses the given blend_type' do
77
+ blend = engine.blend(space_a_id: space_a.id, space_b_id: space_b.id, blend_type: :mirror)
78
+ expect(blend.blend_type).to eq(:mirror)
79
+ end
80
+
81
+ it 'extracts generic space with shared relation types' do
82
+ blend = engine.blend(space_a_id: space_a.id, space_b_id: space_b.id)
83
+ expect(blend.generic_space).to have_key(:shared_relation_types)
84
+ end
85
+
86
+ it 'merges elements from both spaces' do
87
+ blend = engine.blend(space_a_id: space_a.id, space_b_id: space_b.id)
88
+ merged = blend.blended_elements[:merged_elements]
89
+ expect(merged).to include('virus', 'software')
90
+ end
91
+
92
+ it 'generates emergent properties from cross-domain relations' do
93
+ blend = engine.blend(space_a_id: space_a.id, space_b_id: space_b.id)
94
+ expect(blend.blended_elements[:emergent_properties]).not_to be_empty
95
+ end
96
+
97
+ it 'raises ArgumentError for unknown space ids' do
98
+ expect do
99
+ engine.blend(space_a_id: 'bad', space_b_id: space_b.id)
100
+ end.to raise_error(ArgumentError, /not found/)
101
+ end
102
+ end
103
+
104
+ describe '#elaborate_blend' do
105
+ let(:blend) { engine.blend(space_a_id: space_a.id, space_b_id: space_b.id) }
106
+
107
+ it 'adds emergent property to blend' do
108
+ engine.elaborate_blend(blend_id: blend.id, emergent_property: 'computer_immune_system')
109
+ expect(blend.blended_elements[:emergent_properties]).to include('computer_immune_system')
110
+ end
111
+
112
+ it 'raises ArgumentError for unknown blend_id' do
113
+ expect do
114
+ engine.elaborate_blend(blend_id: 'bad', emergent_property: 'x')
115
+ end.to raise_error(ArgumentError, /not found/)
116
+ end
117
+ end
118
+
119
+ describe '#compress_blend' do
120
+ let(:blend) { engine.blend(space_a_id: space_a.id, space_b_id: space_b.id) }
121
+
122
+ it 'reduces blend strength' do
123
+ original_strength = blend.strength
124
+ engine.compress_blend(blend_id: blend.id, removed_element: 'virus')
125
+ expect(blend.strength).to be < original_strength
126
+ end
127
+
128
+ it 'raises ArgumentError for unknown blend_id' do
129
+ expect do
130
+ engine.compress_blend(blend_id: 'bad', removed_element: 'x')
131
+ end.to raise_error(ArgumentError, /not found/)
132
+ end
133
+ end
134
+
135
+ describe '#find_blends' do
136
+ it 'finds blends involving spaces from the given domain' do
137
+ engine.blend(space_a_id: space_a.id, space_b_id: space_b.id)
138
+ results = engine.find_blends(domain: 'science')
139
+ expect(results).not_to be_empty
140
+ end
141
+
142
+ it 'returns empty array when no matching domain' do
143
+ engine.blend(space_a_id: space_a.id, space_b_id: space_b.id)
144
+ results = engine.find_blends(domain: 'nonexistent')
145
+ expect(results).to eq([])
146
+ end
147
+ end
148
+
149
+ describe '#best_blends' do
150
+ it 'returns array of blends ordered by quality' do
151
+ engine.blend(space_a_id: space_a.id, space_b_id: space_b.id)
152
+ results = engine.best_blends(limit: 5)
153
+ expect(results).to be_an(Array)
154
+ end
155
+
156
+ it 'respects the limit' do
157
+ 3.times { engine.blend(space_a_id: space_a.id, space_b_id: space_b.id) }
158
+ results = engine.best_blends(limit: 2)
159
+ expect(results.size).to be <= 2
160
+ end
161
+ end
162
+
163
+ describe '#blend_quality' do
164
+ let(:blend) { engine.blend(space_a_id: space_a.id, space_b_id: space_b.id) }
165
+
166
+ it 'returns quality assessment hash' do
167
+ result = engine.blend_quality(blend_id: blend.id)
168
+ expect(result).to include(:blend_id, :quality_score, :quality_label, :strength, :use_count, :stale)
169
+ end
170
+
171
+ it 'raises ArgumentError for unknown blend_id' do
172
+ expect do
173
+ engine.blend_quality(blend_id: 'bad')
174
+ end.to raise_error(ArgumentError, /not found/)
175
+ end
176
+ end
177
+
178
+ describe '#decay_stale' do
179
+ it 'returns count of decayed blends' do
180
+ engine.blend(space_a_id: space_a.id, space_b_id: space_b.id)
181
+ count = engine.decay_stale
182
+ expect(count).to be_a(Integer)
183
+ end
184
+ end
185
+
186
+ describe '#prune_weak' do
187
+ it 'returns count of pruned blends' do
188
+ pruned = engine.prune_weak
189
+ expect(pruned).to be_a(Integer)
190
+ end
191
+
192
+ it 'removes blends with strength below 0.1' do
193
+ blend = engine.blend(space_a_id: space_a.id, space_b_id: space_b.id)
194
+ blend.instance_variable_set(:@strength, 0.05)
195
+ engine.prune_weak
196
+ expect(engine.to_h[:blends_count]).to eq(0)
197
+ end
198
+ end
199
+
200
+ describe '#to_h' do
201
+ it 'returns stats hash' do
202
+ result = engine.to_h
203
+ expect(result).to include(:spaces_count, :blends_count, :best_quality)
204
+ end
205
+
206
+ it 'reflects current state' do
207
+ engine.create_space(name: 's', domain: 'd')
208
+ expect(engine.to_h[:spaces_count]).to eq(1)
209
+ end
210
+ end
211
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Language::ConceptualBlending::Helpers::MentalSpace do
4
+ subject(:space) { described_class.new(name: 'biology', domain: 'science') }
5
+
6
+ describe '#initialize' do
7
+ it 'assigns a uuid id' do
8
+ expect(space.id).to match(/\A[0-9a-f-]{36}\z/)
9
+ end
10
+
11
+ it 'sets name and domain' do
12
+ expect(space.name).to eq('biology')
13
+ expect(space.domain).to eq('science')
14
+ end
15
+
16
+ it 'starts with empty elements and relations' do
17
+ expect(space.elements).to eq({})
18
+ expect(space.relations).to eq([])
19
+ end
20
+
21
+ it 'records created_at timestamp' do
22
+ expect(space.created_at).to be_a(Time)
23
+ end
24
+ end
25
+
26
+ describe '#add_element' do
27
+ it 'stores element with properties' do
28
+ space.add_element(name: 'virus', properties: { type: :pathogen })
29
+ expect(space.elements['virus']).to eq({ type: :pathogen })
30
+ end
31
+
32
+ it 'returns self for chaining' do
33
+ result = space.add_element(name: 'cell', properties: {})
34
+ expect(result).to eq(space)
35
+ end
36
+
37
+ it 'stores multiple elements' do
38
+ space.add_element(name: 'virus', properties: {})
39
+ space.add_element(name: 'host', properties: {})
40
+ expect(space.elements.size).to eq(2)
41
+ end
42
+ end
43
+
44
+ describe '#add_relation' do
45
+ it 'stores relation hash' do
46
+ space.add_relation(from: 'virus', to: 'host', type: :infects)
47
+ expect(space.relations.first).to eq({ from: 'virus', to: 'host', type: :infects })
48
+ end
49
+
50
+ it 'returns self for chaining' do
51
+ result = space.add_relation(from: 'a', to: 'b', type: :links)
52
+ expect(result).to eq(space)
53
+ end
54
+
55
+ it 'accumulates multiple relations' do
56
+ space.add_relation(from: 'a', to: 'b', type: :one)
57
+ space.add_relation(from: 'b', to: 'c', type: :two)
58
+ expect(space.relations.size).to eq(2)
59
+ end
60
+ end
61
+
62
+ describe '#element_names' do
63
+ it 'returns array of element name keys' do
64
+ space.add_element(name: 'alpha', properties: {})
65
+ space.add_element(name: 'beta', properties: {})
66
+ expect(space.element_names).to contain_exactly('alpha', 'beta')
67
+ end
68
+
69
+ it 'returns empty array when no elements' do
70
+ expect(space.element_names).to eq([])
71
+ end
72
+ end
73
+
74
+ describe '#to_h' do
75
+ it 'returns a hash with all fields' do
76
+ result = space.to_h
77
+ expect(result).to include(:id, :name, :domain, :elements, :relations, :created_at)
78
+ end
79
+
80
+ it 'reflects current state' do
81
+ space.add_element(name: 'x', properties: { val: 1 })
82
+ expect(space.to_h[:elements]['x']).to eq({ val: 1 })
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,162 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/agentic/language/conceptual_blending/client'
4
+
5
+ RSpec.describe Legion::Extensions::Agentic::Language::ConceptualBlending::Runners::ConceptualBlending do
6
+ let(:client) { Legion::Extensions::Agentic::Language::ConceptualBlending::Client.new }
7
+
8
+ let(:space_a_id) do
9
+ result = client.create_mental_space(name: 'biology', domain: 'science')
10
+ result[:space][:id]
11
+ end
12
+
13
+ let(:space_b_id) do
14
+ result = client.create_mental_space(name: 'computing', domain: 'technology')
15
+ result[:space][:id]
16
+ end
17
+
18
+ before do
19
+ client.add_space_element(space_id: space_a_id, name: 'virus', properties: { type: :pathogen })
20
+ client.add_space_relation(space_id: space_a_id, from: 'virus', to: 'host', type: :infects)
21
+ client.add_space_element(space_id: space_b_id, name: 'software', properties: { type: :program })
22
+ client.add_space_relation(space_id: space_b_id, from: 'software', to: 'network', type: :corrupts)
23
+ end
24
+
25
+ describe '#create_mental_space' do
26
+ it 'returns success with space hash' do
27
+ result = client.create_mental_space(name: 'test', domain: 'misc')
28
+ expect(result[:success]).to be true
29
+ expect(result[:space]).to include(:id, :name, :domain)
30
+ end
31
+ end
32
+
33
+ describe '#add_space_element' do
34
+ it 'returns success with space_id and element name' do
35
+ result = client.add_space_element(space_id: space_a_id, name: 'new_elem', properties: {})
36
+ expect(result[:success]).to be true
37
+ expect(result[:element]).to eq('new_elem')
38
+ end
39
+
40
+ it 'returns failure for unknown space_id' do
41
+ result = client.add_space_element(space_id: 'bad-id', name: 'x', properties: {})
42
+ expect(result[:success]).to be false
43
+ expect(result[:error]).to include('not found')
44
+ end
45
+ end
46
+
47
+ describe '#add_space_relation' do
48
+ it 'returns success with relation hash' do
49
+ result = client.add_space_relation(space_id: space_a_id, from: 'a', to: 'b', type: :links)
50
+ expect(result[:success]).to be true
51
+ expect(result[:relation]).to eq({ from: 'a', to: 'b', type: :links })
52
+ end
53
+
54
+ it 'returns failure for unknown space_id' do
55
+ result = client.add_space_relation(space_id: 'bad', from: 'a', to: 'b', type: :x)
56
+ expect(result[:success]).to be false
57
+ end
58
+ end
59
+
60
+ describe '#create_blend' do
61
+ it 'returns success with blend hash' do
62
+ result = client.create_blend(space_a_id: space_a_id, space_b_id: space_b_id)
63
+ expect(result[:success]).to be true
64
+ expect(result[:blend]).to include(:id, :blend_type, :strength)
65
+ end
66
+
67
+ it 'uses provided blend_type' do
68
+ result = client.create_blend(space_a_id: space_a_id, space_b_id: space_b_id, blend_type: :mirror)
69
+ expect(result[:blend][:blend_type]).to eq(:mirror)
70
+ end
71
+
72
+ it 'returns failure for unknown space ids' do
73
+ result = client.create_blend(space_a_id: 'bad', space_b_id: space_b_id)
74
+ expect(result[:success]).to be false
75
+ end
76
+ end
77
+
78
+ describe '#elaborate_blend' do
79
+ let(:blend_id) do
80
+ result = client.create_blend(space_a_id: space_a_id, space_b_id: space_b_id)
81
+ result[:blend][:id]
82
+ end
83
+
84
+ it 'returns success with updated blend' do
85
+ result = client.elaborate_blend(blend_id: blend_id, emergent_property: 'antivirus_software')
86
+ expect(result[:success]).to be true
87
+ expect(result[:blend][:blended_elements][:emergent_properties]).to include('antivirus_software')
88
+ end
89
+
90
+ it 'returns failure for unknown blend_id' do
91
+ result = client.elaborate_blend(blend_id: 'bad', emergent_property: 'x')
92
+ expect(result[:success]).to be false
93
+ end
94
+ end
95
+
96
+ describe '#compress_blend' do
97
+ let(:blend_id) do
98
+ result = client.create_blend(space_a_id: space_a_id, space_b_id: space_b_id)
99
+ result[:blend][:id]
100
+ end
101
+
102
+ it 'returns success with updated blend' do
103
+ result = client.compress_blend(blend_id: blend_id, removed_element: 'virus')
104
+ expect(result[:success]).to be true
105
+ end
106
+
107
+ it 'returns failure for unknown blend_id' do
108
+ result = client.compress_blend(blend_id: 'bad', removed_element: 'x')
109
+ expect(result[:success]).to be false
110
+ end
111
+ end
112
+
113
+ describe '#best_blends' do
114
+ it 'returns success with blends array' do
115
+ client.create_blend(space_a_id: space_a_id, space_b_id: space_b_id)
116
+ result = client.best_blends(limit: 5)
117
+ expect(result[:success]).to be true
118
+ expect(result[:blends]).to be_an(Array)
119
+ expect(result[:count]).to eq(result[:blends].size)
120
+ end
121
+
122
+ it 'returns empty array when no blends exist' do
123
+ fresh_client = Legion::Extensions::Agentic::Language::ConceptualBlending::Client.new
124
+ result = fresh_client.best_blends(limit: 5)
125
+ expect(result[:blends]).to eq([])
126
+ end
127
+ end
128
+
129
+ describe '#blend_quality' do
130
+ let(:blend_id) do
131
+ result = client.create_blend(space_a_id: space_a_id, space_b_id: space_b_id)
132
+ result[:blend][:id]
133
+ end
134
+
135
+ it 'returns quality assessment' do
136
+ result = client.blend_quality(blend_id: blend_id)
137
+ expect(result[:success]).to be true
138
+ expect(result).to include(:quality_score, :quality_label, :strength)
139
+ end
140
+
141
+ it 'returns failure for unknown blend_id' do
142
+ result = client.blend_quality(blend_id: 'bad')
143
+ expect(result[:success]).to be false
144
+ end
145
+ end
146
+
147
+ describe '#update_conceptual_blending' do
148
+ it 'returns success with decay and prune counts' do
149
+ result = client.update_conceptual_blending
150
+ expect(result[:success]).to be true
151
+ expect(result).to include(:decayed, :pruned)
152
+ end
153
+ end
154
+
155
+ describe '#conceptual_blending_stats' do
156
+ it 'returns success with engine stats' do
157
+ result = client.conceptual_blending_stats
158
+ expect(result[:success]).to be true
159
+ expect(result).to include(:spaces_count, :blends_count)
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Language::ConceptualMetaphor::Client do
4
+ subject(:client) { described_class.new }
5
+
6
+ it 'creates a metaphor' do
7
+ result = client.create_metaphor(
8
+ source_domain: :money, target_domain: :time,
9
+ metaphor_type: :structural, mappings: { spend: :waste }
10
+ )
11
+ expect(result[:success]).to be true
12
+ end
13
+
14
+ it 'applies a metaphor' do
15
+ created = client.create_metaphor(
16
+ source_domain: :money, target_domain: :time,
17
+ metaphor_type: :structural, mappings: { spend: :waste }
18
+ )
19
+ result = client.apply_metaphor(
20
+ metaphor_id: created[:metaphor_id], source_concept: :spend
21
+ )
22
+ expect(result[:target_concept]).to eq(:waste)
23
+ end
24
+
25
+ it 'returns stats' do
26
+ result = client.conceptual_metaphor_stats
27
+ expect(result[:success]).to be true
28
+ end
29
+ end
@@ -0,0 +1,166 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Language::ConceptualMetaphor::Helpers::MetaphorEngine do
4
+ subject(:engine) { described_class.new }
5
+
6
+ let(:metaphor) do
7
+ engine.create_metaphor(
8
+ source_domain: :money,
9
+ target_domain: :time,
10
+ metaphor_type: :structural,
11
+ mappings: { spend: :waste, save: :conserve }
12
+ )
13
+ end
14
+
15
+ describe '#create_metaphor' do
16
+ it 'creates and stores a metaphor' do
17
+ result = metaphor
18
+ expect(result).to be_a(Legion::Extensions::Agentic::Language::ConceptualMetaphor::Helpers::Metaphor)
19
+ expect(result.source_domain).to eq(:money)
20
+ end
21
+
22
+ it 'rejects invalid metaphor types' do
23
+ result = engine.create_metaphor(
24
+ source_domain: :a, target_domain: :b,
25
+ metaphor_type: :invalid, mappings: {}
26
+ )
27
+ expect(result[:success]).to be false
28
+ end
29
+
30
+ it 'records history' do
31
+ metaphor
32
+ expect(engine.history.size).to eq(1)
33
+ end
34
+ end
35
+
36
+ describe '#apply_metaphor' do
37
+ it 'maps a source concept to target' do
38
+ result = engine.apply_metaphor(metaphor_id: metaphor.id, source_concept: :spend)
39
+ expect(result[:mapped]).to be true
40
+ expect(result[:target_concept]).to eq(:waste)
41
+ end
42
+
43
+ it 'returns found: false for unknown metaphor' do
44
+ result = engine.apply_metaphor(metaphor_id: 'nonexistent', source_concept: :spend)
45
+ expect(result[:found]).to be false
46
+ end
47
+
48
+ it 'returns mapped: false for unmapped concept' do
49
+ result = engine.apply_metaphor(metaphor_id: metaphor.id, source_concept: :borrow)
50
+ expect(result[:mapped]).to be false
51
+ end
52
+ end
53
+
54
+ describe '#add_entailment' do
55
+ it 'adds entailment to metaphor' do
56
+ result = engine.add_entailment(metaphor_id: metaphor.id, entailment: 'time is valuable')
57
+ expect(result[:success]).to be true
58
+ expect(result[:entailment_count]).to eq(1)
59
+ end
60
+
61
+ it 'returns error for unknown metaphor' do
62
+ result = engine.add_entailment(metaphor_id: 'bad', entailment: 'test')
63
+ expect(result[:success]).to be false
64
+ end
65
+ end
66
+
67
+ describe '#find_by_domain' do
68
+ it 'finds metaphors involving the domain' do
69
+ metaphor
70
+ results = engine.find_by_domain(domain: :time)
71
+ expect(results.size).to eq(1)
72
+ end
73
+ end
74
+
75
+ describe '#find_by_source' do
76
+ it 'finds metaphors by source domain' do
77
+ metaphor
78
+ results = engine.find_by_source(source_domain: :money)
79
+ expect(results.size).to eq(1)
80
+ end
81
+ end
82
+
83
+ describe '#find_by_target' do
84
+ it 'finds metaphors by target domain' do
85
+ metaphor
86
+ results = engine.find_by_target(target_domain: :time)
87
+ expect(results.size).to eq(1)
88
+ end
89
+ end
90
+
91
+ describe '#conventional_metaphors' do
92
+ it 'returns metaphors with high conventionality' do
93
+ engine.create_metaphor(
94
+ source_domain: :war, target_domain: :argument,
95
+ metaphor_type: :structural, mappings: { attack: :criticize },
96
+ conventionality: 0.9
97
+ )
98
+ expect(engine.conventional_metaphors.size).to eq(1)
99
+ end
100
+ end
101
+
102
+ describe '#novel_metaphors' do
103
+ it 'returns metaphors with low conventionality' do
104
+ engine.create_metaphor(
105
+ source_domain: :ocean, target_domain: :emotion,
106
+ metaphor_type: :ontological, mappings: { depth: :intensity },
107
+ conventionality: 0.1
108
+ )
109
+ expect(engine.novel_metaphors.size).to eq(1)
110
+ end
111
+ end
112
+
113
+ describe '#strongest' do
114
+ it 'returns metaphors sorted by strength' do
115
+ metaphor
116
+ engine.create_metaphor(
117
+ source_domain: :war, target_domain: :argument,
118
+ metaphor_type: :structural, mappings: {}, strength: 0.9
119
+ )
120
+ results = engine.strongest(limit: 2)
121
+ expect(results.first.strength).to be >= results.last.strength
122
+ end
123
+ end
124
+
125
+ describe '#by_type' do
126
+ it 'filters by metaphor type' do
127
+ metaphor
128
+ engine.create_metaphor(
129
+ source_domain: :container, target_domain: :mind,
130
+ metaphor_type: :ontological, mappings: { full: :knowledgeable }
131
+ )
132
+ structural = engine.by_type(metaphor_type: :structural)
133
+ expect(structural.size).to eq(1)
134
+ end
135
+ end
136
+
137
+ describe '#decay_all' do
138
+ it 'reduces strength of all metaphors' do
139
+ original = metaphor.strength
140
+ engine.decay_all
141
+ expect(metaphor.strength).to be < original
142
+ end
143
+ end
144
+
145
+ describe '#prune_weak' do
146
+ it 'removes very weak metaphors' do
147
+ weak = engine.create_metaphor(
148
+ source_domain: :a, target_domain: :b,
149
+ metaphor_type: :structural, mappings: {}, strength: 0.03
150
+ )
151
+ 30.times { weak.decay! }
152
+ pruned = engine.prune_weak
153
+ expect(pruned).to be >= 1
154
+ end
155
+ end
156
+
157
+ describe '#to_h' do
158
+ it 'returns summary stats' do
159
+ metaphor
160
+ stats = engine.to_h
161
+ expect(stats[:total_metaphors]).to eq(1)
162
+ expect(stats[:total_domains]).to eq(2)
163
+ expect(stats).to include(:type_counts, :conventional_count, :novel_count)
164
+ end
165
+ end
166
+ end