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,213 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Language::FrameSemantics::Helpers::Frame do
4
+ subject(:frame) { described_class.new(name: :commercial_transaction, domain: :commerce) }
5
+
6
+ describe '#initialize' do
7
+ it 'assigns a uuid id' do
8
+ expect(frame.id).to match(/\A[0-9a-f-]{36}\z/)
9
+ end
10
+
11
+ it 'sets name and domain' do
12
+ expect(frame.name).to eq(:commercial_transaction)
13
+ expect(frame.domain).to eq(:commerce)
14
+ end
15
+
16
+ it 'starts with default activation' do
17
+ expect(frame.activation).to eq(Legion::Extensions::Agentic::Language::FrameSemantics::Helpers::DEFAULT_ACTIVATION)
18
+ end
19
+
20
+ it 'starts with zero activation_count' do
21
+ expect(frame.activation_count).to eq(0)
22
+ end
23
+
24
+ it 'starts with empty slots and relations' do
25
+ expect(frame.slots).to be_empty
26
+ expect(frame.relations).to be_empty
27
+ end
28
+ end
29
+
30
+ describe '#add_slot' do
31
+ it 'adds a core slot by default' do
32
+ frame.add_slot(name: :buyer)
33
+ expect(frame.slots[:buyer][:type]).to eq(:core)
34
+ expect(frame.slots[:buyer][:required]).to be true
35
+ expect(frame.slots[:buyer][:filler]).to be_nil
36
+ end
37
+
38
+ it 'adds a peripheral slot when specified' do
39
+ frame.add_slot(name: :payment_method, slot_type: :peripheral, required: false)
40
+ expect(frame.slots[:payment_method][:type]).to eq(:peripheral)
41
+ expect(frame.slots[:payment_method][:required]).to be false
42
+ end
43
+
44
+ it 'returns self for chaining' do
45
+ result = frame.add_slot(name: :seller)
46
+ expect(result).to be(frame)
47
+ end
48
+ end
49
+
50
+ describe '#fill_slot' do
51
+ before { frame.add_slot(name: :buyer) }
52
+
53
+ it 'fills a slot and returns true' do
54
+ result = frame.fill_slot(name: :buyer, filler: 'Alice')
55
+ expect(result).to be true
56
+ expect(frame.slots[:buyer][:filler]).to eq('Alice')
57
+ end
58
+
59
+ it 'boosts activation when slot is filled' do
60
+ initial = frame.activation
61
+ frame.fill_slot(name: :buyer, filler: 'Alice')
62
+ expect(frame.activation).to be > initial
63
+ end
64
+
65
+ it 'returns false for unknown slot' do
66
+ expect(frame.fill_slot(name: :unknown, filler: 'x')).to be false
67
+ end
68
+ end
69
+
70
+ describe '#clear_slot' do
71
+ before do
72
+ frame.add_slot(name: :buyer)
73
+ frame.fill_slot(name: :buyer, filler: 'Alice')
74
+ end
75
+
76
+ it 'clears the slot filler' do
77
+ frame.clear_slot(name: :buyer)
78
+ expect(frame.slots[:buyer][:filler]).to be_nil
79
+ end
80
+
81
+ it 'returns true for existing slot' do
82
+ expect(frame.clear_slot(name: :buyer)).to be true
83
+ end
84
+
85
+ it 'returns false for unknown slot' do
86
+ expect(frame.clear_slot(name: :ghost)).to be false
87
+ end
88
+ end
89
+
90
+ describe '#core_slots' do
91
+ before do
92
+ frame.add_slot(name: :buyer, slot_type: :core)
93
+ frame.add_slot(name: :seller, slot_type: :core)
94
+ frame.add_slot(name: :note, slot_type: :peripheral)
95
+ end
96
+
97
+ it 'returns only core slots' do
98
+ expect(frame.core_slots.keys).to contain_exactly(:buyer, :seller)
99
+ end
100
+ end
101
+
102
+ describe '#filled_slots' do
103
+ before do
104
+ frame.add_slot(name: :buyer)
105
+ frame.add_slot(name: :seller)
106
+ frame.fill_slot(name: :buyer, filler: 'Alice')
107
+ end
108
+
109
+ it 'returns only filled slots' do
110
+ expect(frame.filled_slots.keys).to eq([:buyer])
111
+ end
112
+ end
113
+
114
+ describe '#completion_ratio' do
115
+ it 'returns 0.0 with no slots' do
116
+ expect(frame.completion_ratio).to eq(0.0)
117
+ end
118
+
119
+ it 'returns correct fraction of filled core slots' do
120
+ frame.add_slot(name: :buyer)
121
+ frame.add_slot(name: :seller)
122
+ frame.add_slot(name: :goods)
123
+ frame.fill_slot(name: :buyer, filler: 'Alice')
124
+ expect(frame.completion_ratio).to be_within(0.001).of(1.0 / 3.0)
125
+ end
126
+
127
+ it 'ignores peripheral slots in ratio' do
128
+ frame.add_slot(name: :buyer, slot_type: :core)
129
+ frame.add_slot(name: :note, slot_type: :peripheral)
130
+ frame.fill_slot(name: :buyer, filler: 'Alice')
131
+ expect(frame.completion_ratio).to eq(1.0)
132
+ end
133
+ end
134
+
135
+ describe '#complete?' do
136
+ it 'returns false when completion is below threshold' do
137
+ frame.add_slot(name: :buyer)
138
+ frame.add_slot(name: :seller)
139
+ frame.add_slot(name: :goods)
140
+ frame.fill_slot(name: :buyer, filler: 'Alice')
141
+ expect(frame.complete?).to be false
142
+ end
143
+
144
+ it 'returns true when all core slots are filled' do
145
+ frame.add_slot(name: :buyer)
146
+ frame.fill_slot(name: :buyer, filler: 'Alice')
147
+ expect(frame.complete?).to be true
148
+ end
149
+ end
150
+
151
+ describe '#activate!' do
152
+ it 'boosts activation and increments count' do
153
+ initial = frame.activation
154
+ frame.activate!
155
+ expect(frame.activation).to be > initial
156
+ expect(frame.activation_count).to eq(1)
157
+ end
158
+
159
+ it 'clamps activation at 1.0' do
160
+ 10.times { frame.activate! }
161
+ expect(frame.activation).to eq(1.0)
162
+ end
163
+ end
164
+
165
+ describe '#decay!' do
166
+ it 'reduces activation' do
167
+ initial = frame.activation
168
+ frame.decay!
169
+ expect(frame.activation).to be < initial
170
+ end
171
+
172
+ it 'clamps activation at 0.0' do
173
+ 100.times { frame.decay! }
174
+ expect(frame.activation).to eq(0.0)
175
+ end
176
+ end
177
+
178
+ describe '#activation_label' do
179
+ it 'returns :dominant for high activation' do
180
+ 10.times { frame.activate! }
181
+ expect(frame.activation_label).to eq(:dominant)
182
+ end
183
+
184
+ it 'returns :inactive for very low activation' do
185
+ 100.times { frame.decay! }
186
+ expect(frame.activation_label).to eq(:inactive)
187
+ end
188
+
189
+ it 'returns :primed for mid activation around 0.5' do
190
+ frame.instance_variable_set(:@activation, 0.5)
191
+ expect(frame.activation_label).to eq(:primed)
192
+ end
193
+ end
194
+
195
+ describe '#add_relation' do
196
+ it 'appends a relation entry' do
197
+ other_id = SecureRandom.uuid
198
+ frame.add_relation(relation: :inherits_from, target_frame_id: other_id)
199
+ expect(frame.relations.size).to eq(1)
200
+ expect(frame.relations.first[:relation]).to eq(:inherits_from)
201
+ expect(frame.relations.first[:target_frame_id]).to eq(other_id)
202
+ end
203
+ end
204
+
205
+ describe '#to_h' do
206
+ it 'returns a hash with expected keys' do
207
+ h = frame.to_h
208
+ expect(h).to include(:id, :name, :domain, :slots, :relations,
209
+ :activation, :activation_label, :activation_count,
210
+ :completion_ratio, :complete, :created_at)
211
+ end
212
+ end
213
+ end
@@ -0,0 +1,155 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/agentic/language/frame_semantics/helpers/client'
4
+
5
+ RSpec.describe Legion::Extensions::Agentic::Language::FrameSemantics::Runners::FrameSemantics do
6
+ let(:client) { Legion::Extensions::Agentic::Language::FrameSemantics::Helpers::Client.new }
7
+
8
+ describe '#create_semantic_frame' do
9
+ it 'creates a frame and returns its id' do
10
+ result = client.create_semantic_frame(name: :commercial_transaction, domain: :commerce)
11
+ expect(result[:created]).to be true
12
+ expect(result[:frame_id]).to match(/\A[0-9a-f-]{36}\z/)
13
+ expect(result[:name]).to eq(:commercial_transaction)
14
+ end
15
+ end
16
+
17
+ describe '#define_frame_slot' do
18
+ let(:frame_id) { client.create_semantic_frame(name: :motion, domain: :physics)[:frame_id] }
19
+
20
+ it 'adds a slot to a known frame' do
21
+ result = client.define_frame_slot(frame_id: frame_id, name: :mover)
22
+ expect(result[:defined]).to be true
23
+ expect(result[:slot_name]).to eq(:mover)
24
+ end
25
+
26
+ it 'returns not found for unknown frame' do
27
+ result = client.define_frame_slot(frame_id: 'bogus', name: :mover)
28
+ expect(result[:defined]).to be false
29
+ expect(result[:reason]).to eq(:frame_not_found)
30
+ end
31
+ end
32
+
33
+ describe '#fill_frame_slot' do
34
+ let(:frame_id) do
35
+ id = client.create_semantic_frame(name: :motion, domain: :physics)[:frame_id]
36
+ client.define_frame_slot(frame_id: id, name: :mover)
37
+ id
38
+ end
39
+
40
+ it 'fills an existing slot' do
41
+ result = client.fill_frame_slot(frame_id: frame_id, slot_name: :mover, filler: 'the car')
42
+ expect(result[:filled]).to be true
43
+ expect(result[:filler]).to eq('the car')
44
+ end
45
+
46
+ it 'returns error for unknown slot' do
47
+ result = client.fill_frame_slot(frame_id: frame_id, slot_name: :nonexistent, filler: 'x')
48
+ expect(result[:filled]).to be false
49
+ end
50
+ end
51
+
52
+ describe '#instantiate_semantic_frame' do
53
+ let(:frame_id) do
54
+ id = client.create_semantic_frame(name: :motion, domain: :physics)[:frame_id]
55
+ client.define_frame_slot(frame_id: id, name: :mover)
56
+ client.fill_frame_slot(frame_id: id, slot_name: :mover, filler: 'rocket')
57
+ id
58
+ end
59
+
60
+ it 'creates an instance snapshot' do
61
+ result = client.instantiate_semantic_frame(frame_id: frame_id, context: 'test context')
62
+ expect(result[:instantiated]).to be true
63
+ expect(result[:instance_id]).to match(/\A[0-9a-f-]{36}\z/)
64
+ expect(result[:filled_count]).to eq(1)
65
+ end
66
+
67
+ it 'returns error for unknown frame' do
68
+ result = client.instantiate_semantic_frame(frame_id: 'bogus', context: 'x')
69
+ expect(result[:instantiated]).to be false
70
+ end
71
+ end
72
+
73
+ describe '#add_frame_relation' do
74
+ let(:frame_id) { client.create_semantic_frame(name: :transfer, domain: :commerce)[:frame_id] }
75
+ let(:target_id) { client.create_semantic_frame(name: :payment, domain: :commerce)[:frame_id] }
76
+
77
+ it 'adds a valid frame relation' do
78
+ result = client.add_frame_relation(
79
+ frame_id: frame_id, relation: :has_subframe, target_frame_id: target_id
80
+ )
81
+ expect(result[:added]).to be true
82
+ end
83
+
84
+ it 'rejects an invalid relation' do
85
+ result = client.add_frame_relation(
86
+ frame_id: frame_id, relation: :nonsense, target_frame_id: target_id
87
+ )
88
+ expect(result[:added]).to be false
89
+ end
90
+ end
91
+
92
+ describe '#activate_semantic_frame' do
93
+ let(:frame_id) { client.create_semantic_frame(name: :competition, domain: :sports)[:frame_id] }
94
+
95
+ it 'activates the frame' do
96
+ result = client.activate_semantic_frame(frame_id: frame_id)
97
+ expect(result[:activated]).to be true
98
+ end
99
+
100
+ it 'returns error for unknown frame' do
101
+ result = client.activate_semantic_frame(frame_id: 'bogus')
102
+ expect(result[:activated]).to be false
103
+ end
104
+ end
105
+
106
+ describe '#active_frames_report' do
107
+ it 'returns list of active frames' do
108
+ id = client.create_semantic_frame(name: :competition, domain: :sports)[:frame_id]
109
+ 4.times { client.activate_semantic_frame(frame_id: id) }
110
+ result = client.active_frames_report
111
+ expect(result).to have_key(:frames)
112
+ expect(result).to have_key(:count)
113
+ end
114
+ end
115
+
116
+ describe '#related_frames_report' do
117
+ let(:frame_id) { client.create_semantic_frame(name: :buying, domain: :commerce)[:frame_id] }
118
+ let(:target_id) { client.create_semantic_frame(name: :paying, domain: :commerce)[:frame_id] }
119
+
120
+ before { client.add_frame_relation(frame_id: frame_id, relation: :has_subframe, target_frame_id: target_id) }
121
+
122
+ it 'returns related frames' do
123
+ result = client.related_frames_report(frame_id: frame_id)
124
+ expect(result[:count]).to eq(1)
125
+ expect(result[:frames].first[:id]).to eq(target_id)
126
+ end
127
+ end
128
+
129
+ describe '#complete_frames_report' do
130
+ it 'returns complete frames' do
131
+ id = client.create_semantic_frame(name: :simple, domain: :test)[:frame_id]
132
+ client.define_frame_slot(frame_id: id, name: :actor)
133
+ client.fill_frame_slot(frame_id: id, slot_name: :actor, filler: 'someone')
134
+ result = client.complete_frames_report
135
+ expect(result[:count]).to be >= 1
136
+ end
137
+ end
138
+
139
+ describe '#update_frame_semantics' do
140
+ it 'runs decay and prune, returning updated status' do
141
+ result = client.update_frame_semantics
142
+ expect(result[:updated]).to be true
143
+ expect(result[:stats]).to have_key(:frame_count)
144
+ end
145
+ end
146
+
147
+ describe '#frame_semantics_stats' do
148
+ it 'returns engine stats' do
149
+ client.create_semantic_frame(name: :motion, domain: :physics)
150
+ result = client.frame_semantics_stats
151
+ expect(result[:frame_count]).to be >= 1
152
+ expect(result).to have_key(:domains)
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Language::Grammar::Client do
4
+ subject(:client) { described_class.new }
5
+
6
+ describe 'runner method availability' do
7
+ it 'responds to create_grammar_construction' do
8
+ expect(client).to respond_to(:create_grammar_construction)
9
+ end
10
+
11
+ it 'responds to create_grammar_construal' do
12
+ expect(client).to respond_to(:create_grammar_construal)
13
+ end
14
+
15
+ it 'responds to use_grammar_construction' do
16
+ expect(client).to respond_to(:use_grammar_construction)
17
+ end
18
+
19
+ it 'responds to construals_for_scene_report' do
20
+ expect(client).to respond_to(:construals_for_scene_report)
21
+ end
22
+
23
+ it 'responds to entrenched_constructions_report' do
24
+ expect(client).to respond_to(:entrenched_constructions_report)
25
+ end
26
+
27
+ it 'responds to constructions_by_domain_report' do
28
+ expect(client).to respond_to(:constructions_by_domain_report)
29
+ end
30
+
31
+ it 'responds to most_used_constructions' do
32
+ expect(client).to respond_to(:most_used_constructions)
33
+ end
34
+
35
+ it 'responds to update_cognitive_grammar' do
36
+ expect(client).to respond_to(:update_cognitive_grammar)
37
+ end
38
+
39
+ it 'responds to cognitive_grammar_stats' do
40
+ expect(client).to respond_to(:cognitive_grammar_stats)
41
+ end
42
+ end
43
+
44
+ describe 'full lifecycle' do
45
+ let(:construction_params) do
46
+ { form: 'the house', meaning: 'definite dwelling', expression_type: :nominal, domain: 'architecture' }
47
+ end
48
+
49
+ it 'creates a construction and retrieves stats' do
50
+ client.create_grammar_construction(**construction_params)
51
+ stats = client.cognitive_grammar_stats
52
+ expect(stats[:constructions_count]).to eq(1)
53
+ end
54
+
55
+ it 'creates a construal and retrieves it by scene' do
56
+ client.create_grammar_construal(
57
+ scene: 'house on hill', perspective: 'valley', figure: 'house', ground: 'hill'
58
+ )
59
+ result = client.construals_for_scene_report(scene: 'house on hill')
60
+ expect(result[:count]).to eq(1)
61
+ expect(result[:construals].first[:figure]).to eq('house')
62
+ end
63
+
64
+ it 'entrenchment: construction becomes entrenched after 5 uses from default activation' do
65
+ created = client.create_grammar_construction(**construction_params)
66
+ expect(client.entrenched_constructions_report[:count]).to eq(0)
67
+
68
+ 5.times { client.use_grammar_construction(construction_id: created[:id]) }
69
+
70
+ report = client.entrenched_constructions_report
71
+ expect(report[:count]).to eq(1)
72
+ expect(report[:constructions].first[:form]).to eq('the house')
73
+ end
74
+
75
+ it 'domain report scopes to correct domain' do
76
+ client.create_grammar_construction(**construction_params)
77
+ client.create_grammar_construction(form: 'fast', meaning: 'speed', expression_type: :relational, domain: 'motion')
78
+
79
+ result = client.constructions_by_domain_report(domain: 'architecture')
80
+ expect(result[:count]).to eq(1)
81
+ expect(result[:constructions].first[:domain]).to eq('architecture')
82
+ end
83
+
84
+ it 'most_used_constructions tracks usage correctly' do
85
+ c1 = client.create_grammar_construction(**construction_params)
86
+ c2 = client.create_grammar_construction(form: 'run', meaning: 'motion', expression_type: :relational,
87
+ domain: 'motion')
88
+
89
+ 2.times { client.use_grammar_construction(construction_id: c1[:id]) }
90
+ 5.times { client.use_grammar_construction(construction_id: c2[:id]) }
91
+
92
+ top = client.most_used_constructions(limit: 1)
93
+ expect(top[:constructions].first[:form]).to eq('run')
94
+ end
95
+
96
+ it 'update_cognitive_grammar decays activation' do
97
+ created = client.create_grammar_construction(**construction_params)
98
+ result = client.update_cognitive_grammar
99
+ expect(result[:stats][:constructions_count]).to be >= 0
100
+ # After decay, activation should be lower than initial 0.3
101
+ engine = client.send(:engine)
102
+ const = engine.instance_variable_get(:@constructions)[created[:id]]
103
+ expect(const.activation).to be < 0.3
104
+ end
105
+
106
+ it 'construal figure/ground prominence model' do
107
+ client.create_grammar_construal(
108
+ scene: 'cup on table',
109
+ perspective: 'human-scale',
110
+ figure: 'cup',
111
+ ground: 'table',
112
+ specificity: :intermediate,
113
+ scope: :local
114
+ )
115
+ report = client.construals_for_scene_report(scene: 'cup on table')
116
+ construal_h = report[:construals].first
117
+ expect(construal_h[:figure]).to eq('cup')
118
+ expect(construal_h[:ground]).to eq('table')
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Language::Grammar do
4
+ it 'has a version' do
5
+ expect(Legion::Extensions::Agentic::Language::Grammar::VERSION).to eq('0.1.0')
6
+ end
7
+
8
+ it 'defines CONSTRUAL_OPERATIONS' do
9
+ expect(Legion::Extensions::Agentic::Language::Grammar::Helpers::Constants::CONSTRUAL_OPERATIONS).to be_frozen
10
+ expect(Legion::Extensions::Agentic::Language::Grammar::Helpers::Constants::CONSTRUAL_OPERATIONS).to include(:perspective, :prominence, :specificity, :scope,
11
+ :dynamicity)
12
+ end
13
+
14
+ it 'defines EXPRESSION_TYPES' do
15
+ expect(Legion::Extensions::Agentic::Language::Grammar::Helpers::Constants::EXPRESSION_TYPES).to include(:nominal,
16
+ :relational, :clausal)
17
+ end
18
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Language::Grammar::Helpers::Constants do
4
+ subject(:mod) { described_class }
5
+
6
+ it 'freezes CONSTRUAL_OPERATIONS' do
7
+ expect(mod::CONSTRUAL_OPERATIONS).to be_frozen
8
+ expect(mod::CONSTRUAL_OPERATIONS).to eq(%i[perspective prominence specificity scope dynamicity])
9
+ end
10
+
11
+ it 'freezes PROMINENCE_TYPES' do
12
+ expect(mod::PROMINENCE_TYPES).to be_frozen
13
+ expect(mod::PROMINENCE_TYPES).to eq(%i[figure ground])
14
+ end
15
+
16
+ it 'freezes SPECIFICITY_LEVELS' do
17
+ expect(mod::SPECIFICITY_LEVELS).to eq(%i[schematic intermediate detailed])
18
+ end
19
+
20
+ it 'freezes SCOPE_LEVELS' do
21
+ expect(mod::SCOPE_LEVELS).to eq(%i[immediate local global])
22
+ end
23
+
24
+ it 'freezes EXPRESSION_TYPES' do
25
+ expect(mod::EXPRESSION_TYPES).to eq(%i[nominal relational clausal])
26
+ end
27
+
28
+ describe 'ACTIVATION_LABELS' do
29
+ it 'maps 0.9 to :entrenched' do
30
+ label = mod::ACTIVATION_LABELS.find { |range, _| range.cover?(0.9) }&.last
31
+ expect(label).to eq(:entrenched)
32
+ end
33
+
34
+ it 'maps 0.7 to :conventional' do
35
+ label = mod::ACTIVATION_LABELS.find { |range, _| range.cover?(0.7) }&.last
36
+ expect(label).to eq(:conventional)
37
+ end
38
+
39
+ it 'maps 0.5 to :familiar' do
40
+ label = mod::ACTIVATION_LABELS.find { |range, _| range.cover?(0.5) }&.last
41
+ expect(label).to eq(:familiar)
42
+ end
43
+
44
+ it 'maps 0.3 to :novel' do
45
+ label = mod::ACTIVATION_LABELS.find { |range, _| range.cover?(0.3) }&.last
46
+ expect(label).to eq(:novel)
47
+ end
48
+
49
+ it 'maps 0.1 to :ad_hoc' do
50
+ label = mod::ACTIVATION_LABELS.find { |range, _| range.cover?(0.1) }&.last
51
+ expect(label).to eq(:ad_hoc)
52
+ end
53
+ end
54
+
55
+ it 'sets numeric limits' do
56
+ expect(mod::MAX_CONSTRUCTIONS).to eq(200)
57
+ expect(mod::MAX_CONSTRUALS).to eq(500)
58
+ expect(mod::MAX_HISTORY).to eq(500)
59
+ end
60
+
61
+ it 'sets activation constants' do
62
+ expect(mod::DEFAULT_ACTIVATION).to eq(0.3)
63
+ expect(mod::ACTIVATION_BOOST).to eq(0.1)
64
+ expect(mod::ACTIVATION_DECAY).to eq(0.02)
65
+ expect(mod::ENTRENCHMENT_THRESHOLD).to eq(0.8)
66
+ end
67
+ end
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Language::Grammar::Helpers::Construal do
4
+ subject(:construal) do
5
+ described_class.new(
6
+ scene: 'cat on mat',
7
+ perspective: 'observer',
8
+ figure: 'cat',
9
+ ground: 'mat'
10
+ )
11
+ end
12
+
13
+ describe '#initialize' do
14
+ it 'assigns a UUID id' do
15
+ expect(construal.id).to match(/\A[0-9a-f-]{36}\z/)
16
+ end
17
+
18
+ it 'stores scene, perspective, figure, ground' do
19
+ expect(construal.scene).to eq('cat on mat')
20
+ expect(construal.perspective).to eq('observer')
21
+ expect(construal.figure).to eq('cat')
22
+ expect(construal.ground).to eq('mat')
23
+ end
24
+
25
+ it 'defaults specificity to :intermediate' do
26
+ expect(construal.specificity).to eq(:intermediate)
27
+ end
28
+
29
+ it 'defaults scope to :local' do
30
+ expect(construal.scope).to eq(:local)
31
+ end
32
+
33
+ it 'defaults dynamicity to 0.5' do
34
+ expect(construal.dynamicity).to eq(0.5)
35
+ end
36
+
37
+ it 'defaults construction_id to nil' do
38
+ expect(construal.construction_id).to be_nil
39
+ end
40
+
41
+ it 'sets created_at' do
42
+ expect(construal.created_at).to be_a(Time)
43
+ end
44
+
45
+ it 'accepts optional construction_id' do
46
+ c = described_class.new(
47
+ scene: 's', perspective: 'p', figure: 'f', ground: 'g',
48
+ construction_id: 'abc-123'
49
+ )
50
+ expect(c.construction_id).to eq('abc-123')
51
+ end
52
+
53
+ it 'clamps dynamicity to 0..1' do
54
+ c = described_class.new(scene: 's', perspective: 'p', figure: 'f', ground: 'g', dynamicity: 1.5)
55
+ expect(c.dynamicity).to eq(1.0)
56
+ end
57
+
58
+ it 'symbolizes specificity' do
59
+ c = described_class.new(scene: 's', perspective: 'p', figure: 'f', ground: 'g', specificity: 'detailed')
60
+ expect(c.specificity).to eq(:detailed)
61
+ end
62
+
63
+ it 'symbolizes scope' do
64
+ c = described_class.new(scene: 's', perspective: 'p', figure: 'f', ground: 'g', scope: 'global')
65
+ expect(c.scope).to eq(:global)
66
+ end
67
+ end
68
+
69
+ describe '#prominent_element' do
70
+ it 'returns the figure' do
71
+ expect(construal.prominent_element).to eq('cat')
72
+ end
73
+ end
74
+
75
+ describe '#background_element' do
76
+ it 'returns the ground' do
77
+ expect(construal.background_element).to eq('mat')
78
+ end
79
+ end
80
+
81
+ describe '#detailed?' do
82
+ it 'returns false for default :intermediate specificity' do
83
+ expect(construal.detailed?).to be false
84
+ end
85
+
86
+ it 'returns true when specificity is :detailed' do
87
+ c = described_class.new(scene: 's', perspective: 'p', figure: 'f', ground: 'g', specificity: :detailed)
88
+ expect(c.detailed?).to be true
89
+ end
90
+
91
+ it 'returns false for :schematic' do
92
+ c = described_class.new(scene: 's', perspective: 'p', figure: 'f', ground: 'g', specificity: :schematic)
93
+ expect(c.detailed?).to be false
94
+ end
95
+ end
96
+
97
+ describe '#global_scope?' do
98
+ it 'returns false for default :local scope' do
99
+ expect(construal.global_scope?).to be false
100
+ end
101
+
102
+ it 'returns true when scope is :global' do
103
+ c = described_class.new(scene: 's', perspective: 'p', figure: 'f', ground: 'g', scope: :global)
104
+ expect(c.global_scope?).to be true
105
+ end
106
+ end
107
+
108
+ describe '#to_h' do
109
+ it 'includes all expected keys' do
110
+ h = construal.to_h
111
+ expect(h).to include(:id, :scene, :perspective, :figure, :ground, :specificity, :scope, :dynamicity,
112
+ :construction_id, :created_at)
113
+ end
114
+
115
+ it 'reflects assigned values' do
116
+ h = construal.to_h
117
+ expect(h[:scene]).to eq('cat on mat')
118
+ expect(h[:figure]).to eq('cat')
119
+ expect(h[:ground]).to eq('mat')
120
+ expect(h[:specificity]).to eq(:intermediate)
121
+ expect(h[:scope]).to eq(:local)
122
+ end
123
+ end
124
+ end