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,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Language::Language::Helpers::Lexicon do
4
+ subject(:lexicon) { described_class.new }
5
+
6
+ let(:summary) do
7
+ {
8
+ domain: :networking,
9
+ knowledge_level: :moderate,
10
+ trace_count: 7,
11
+ key_facts: [{ content: 'fact 1' }]
12
+ }
13
+ end
14
+
15
+ describe '#store_summary' do
16
+ it 'stores a summary keyed by domain symbol' do
17
+ lexicon.store_summary(:networking, summary)
18
+ expect(lexicon.get_summary(:networking)).to include(domain: :networking)
19
+ end
20
+
21
+ it 'adds a cached_at timestamp' do
22
+ lexicon.store_summary('networking', summary)
23
+ stored = lexicon.get_summary(:networking)
24
+ expect(stored[:cached_at]).to be_a(Time)
25
+ end
26
+
27
+ it 'converts string domain to symbol' do
28
+ lexicon.store_summary('security', summary)
29
+ expect(lexicon.get_summary(:security)).not_to be_nil
30
+ end
31
+ end
32
+
33
+ describe '#get_summary' do
34
+ it 'returns nil for unknown domain' do
35
+ expect(lexicon.get_summary(:unknown)).to be_nil
36
+ end
37
+
38
+ it 'returns stored summary' do
39
+ lexicon.store_summary(:networking, summary)
40
+ result = lexicon.get_summary(:networking)
41
+ expect(result[:knowledge_level]).to eq(:moderate)
42
+ end
43
+ end
44
+
45
+ describe '#known_domains' do
46
+ it 'returns empty array initially' do
47
+ expect(lexicon.known_domains).to eq([])
48
+ end
49
+
50
+ it 'returns stored domain keys' do
51
+ lexicon.store_summary(:networking, summary)
52
+ lexicon.store_summary(:security, summary)
53
+ expect(lexicon.known_domains).to contain_exactly(:networking, :security)
54
+ end
55
+ end
56
+
57
+ describe '#knowledge_map' do
58
+ it 'returns empty hash initially' do
59
+ expect(lexicon.knowledge_map).to eq({})
60
+ end
61
+
62
+ it 'returns condensed view of all summaries' do
63
+ lexicon.store_summary(:networking, summary)
64
+ map = lexicon.knowledge_map
65
+ expect(map[:networking]).to include(knowledge_level: :moderate, trace_count: 7)
66
+ expect(map[:networking]).to have_key(:cached_at)
67
+ end
68
+ end
69
+
70
+ describe '#stale?' do
71
+ it 'returns true for unknown domain' do
72
+ expect(lexicon.stale?(:unknown)).to be true
73
+ end
74
+
75
+ it 'returns false for recently stored summary' do
76
+ lexicon.store_summary(:networking, summary)
77
+ expect(lexicon.stale?(:networking)).to be false
78
+ end
79
+
80
+ it 'returns true when max_age exceeded' do
81
+ lexicon.store_summary(:networking, summary)
82
+ stored = lexicon.get_summary(:networking)
83
+ stored[:cached_at] = Time.now.utc - 600
84
+ expect(lexicon.stale?(:networking, max_age: 300)).to be true
85
+ end
86
+ end
87
+
88
+ describe '#clear' do
89
+ before do
90
+ lexicon.store_summary(:networking, summary)
91
+ lexicon.store_summary(:security, summary)
92
+ end
93
+
94
+ it 'clears a specific domain' do
95
+ lexicon.clear(:networking)
96
+ expect(lexicon.get_summary(:networking)).to be_nil
97
+ expect(lexicon.get_summary(:security)).not_to be_nil
98
+ end
99
+
100
+ it 'clears all domains when called without argument' do
101
+ lexicon.clear
102
+ expect(lexicon.size).to eq(0)
103
+ end
104
+ end
105
+
106
+ describe '#size' do
107
+ it 'returns 0 initially' do
108
+ expect(lexicon.size).to eq(0)
109
+ end
110
+
111
+ it 'reflects stored summary count' do
112
+ lexicon.store_summary(:networking, summary)
113
+ expect(lexicon.size).to eq(1)
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,224 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Language::Language::Helpers::Summarizer do
4
+ let(:now) { Time.now.utc }
5
+
6
+ let(:base_trace) do
7
+ {
8
+ trace_id: 'trace-1',
9
+ trace_type: :semantic,
10
+ content_payload: 'Networking uses TCP/IP',
11
+ strength: 0.7,
12
+ confidence: 0.8,
13
+ domain_tags: [:networking],
14
+ emotional_valence: 0.2,
15
+ emotional_intensity: 0.3,
16
+ last_reinforced: now,
17
+ created_at: now
18
+ }
19
+ end
20
+
21
+ def make_traces(count, overrides = {})
22
+ count.times.map do |i|
23
+ base_trace.merge(trace_id: "trace-#{i}").merge(overrides)
24
+ end
25
+ end
26
+
27
+ describe '.summarize_domain' do
28
+ it 'returns empty summary for no traces' do
29
+ result = described_class.summarize_domain([], domain: :networking)
30
+ expect(result[:knowledge_level]).to eq(:none)
31
+ expect(result[:trace_count]).to eq(0)
32
+ expect(result[:key_facts]).to eq([])
33
+ end
34
+
35
+ it 'summarizes traces into structured result' do
36
+ traces = make_traces(3)
37
+ result = described_class.summarize_domain(traces, domain: :networking)
38
+
39
+ expect(result[:domain]).to eq(:networking)
40
+ expect(result[:knowledge_level]).to eq(:sparse)
41
+ expect(result[:trace_count]).to eq(3)
42
+ expect(result).to have_key(:type_breakdown)
43
+ expect(result).to have_key(:key_facts)
44
+ expect(result).to have_key(:strength_stats)
45
+ expect(result).to have_key(:emotional_tone)
46
+ expect(result).to have_key(:freshness)
47
+ end
48
+
49
+ it 'classifies rich knowledge at 10+ traces' do
50
+ traces = make_traces(12)
51
+ result = described_class.summarize_domain(traces, domain: :networking)
52
+ expect(result[:knowledge_level]).to eq(:rich)
53
+ end
54
+
55
+ it 'classifies moderate knowledge at 5-9 traces' do
56
+ traces = make_traces(7)
57
+ result = described_class.summarize_domain(traces, domain: :networking)
58
+ expect(result[:knowledge_level]).to eq(:moderate)
59
+ end
60
+
61
+ it 'classifies sparse knowledge at 1-4 traces' do
62
+ traces = make_traces(2)
63
+ result = described_class.summarize_domain(traces, domain: :networking)
64
+ expect(result[:knowledge_level]).to eq(:sparse)
65
+ end
66
+ end
67
+
68
+ describe '.group_by_type' do
69
+ it 'groups traces by trace_type' do
70
+ traces = [
71
+ base_trace.merge(trace_type: :firmware),
72
+ base_trace.merge(trace_type: :semantic),
73
+ base_trace.merge(trace_type: :firmware)
74
+ ]
75
+ grouped = described_class.group_by_type(traces)
76
+ expect(grouped[:firmware].size).to eq(2)
77
+ expect(grouped[:semantic].size).to eq(1)
78
+ end
79
+
80
+ it 'orders by TYPE_PRIORITY' do
81
+ traces = [
82
+ base_trace.merge(trace_type: :sensory),
83
+ base_trace.merge(trace_type: :firmware)
84
+ ]
85
+ grouped = described_class.group_by_type(traces)
86
+ expect(grouped.keys.first).to eq(:firmware)
87
+ end
88
+ end
89
+
90
+ describe '.extract_key_facts' do
91
+ let(:grouped) do
92
+ { semantic: make_traces(10) }
93
+ end
94
+
95
+ it 'limits facts by depth :brief' do
96
+ facts = described_class.extract_key_facts(grouped, depth: :brief)
97
+ expect(facts.size).to be <= 3
98
+ end
99
+
100
+ it 'limits facts by depth :standard' do
101
+ facts = described_class.extract_key_facts(grouped, depth: :standard)
102
+ expect(facts.size).to be <= 7
103
+ end
104
+
105
+ it 'limits facts by depth :detailed' do
106
+ facts = described_class.extract_key_facts(grouped, depth: :detailed)
107
+ expect(facts.size).to be <= 15
108
+ end
109
+
110
+ it 'returns structured fact hashes' do
111
+ facts = described_class.extract_key_facts(grouped)
112
+ fact = facts.first
113
+ expect(fact).to include(:trace_id, :type, :content, :strength, :confidence, :domain_tags)
114
+ end
115
+ end
116
+
117
+ describe '.extract_content_text' do
118
+ it 'handles String payload' do
119
+ expect(described_class.extract_content_text('hello')).to eq('hello')
120
+ end
121
+
122
+ it 'handles Hash with :text key' do
123
+ expect(described_class.extract_content_text({ text: 'hello' })).to eq('hello')
124
+ end
125
+
126
+ it 'handles Hash with :content key' do
127
+ expect(described_class.extract_content_text({ content: 'world' })).to eq('world')
128
+ end
129
+
130
+ it 'handles Hash with :summary key' do
131
+ expect(described_class.extract_content_text({ summary: 'sum' })).to eq('sum')
132
+ end
133
+
134
+ it 'handles Array payload' do
135
+ expect(described_class.extract_content_text(%w[first second])).to eq('first')
136
+ end
137
+
138
+ it 'converts other types to string' do
139
+ expect(described_class.extract_content_text(42)).to eq('42')
140
+ end
141
+ end
142
+
143
+ describe '.strength_stats' do
144
+ it 'computes mean, max, min, strong, weak counts' do
145
+ traces = [
146
+ base_trace.merge(strength: 0.8),
147
+ base_trace.merge(strength: 0.2),
148
+ base_trace.merge(strength: 0.5)
149
+ ]
150
+ stats = described_class.strength_stats(traces)
151
+ expect(stats[:mean]).to eq(0.5)
152
+ expect(stats[:max]).to eq(0.8)
153
+ expect(stats[:min]).to eq(0.2)
154
+ expect(stats[:strong]).to eq(2) # 0.8 and 0.5
155
+ expect(stats[:weak]).to eq(1) # 0.2
156
+ end
157
+ end
158
+
159
+ describe '.emotional_tone' do
160
+ it 'computes average valence and intensity' do
161
+ traces = [
162
+ base_trace.merge(emotional_valence: 0.5, emotional_intensity: 0.6),
163
+ base_trace.merge(emotional_valence: 0.3, emotional_intensity: 0.4)
164
+ ]
165
+ tone = described_class.emotional_tone(traces)
166
+ expect(tone[:avg_valence]).to eq(0.4)
167
+ expect(tone[:avg_intensity]).to eq(0.5)
168
+ end
169
+
170
+ it 'classifies neutral tone for low intensity' do
171
+ traces = [base_trace.merge(emotional_valence: 0.5, emotional_intensity: 0.1)]
172
+ tone = described_class.emotional_tone(traces)
173
+ expect(tone[:tone]).to eq(:neutral)
174
+ end
175
+
176
+ it 'classifies positive tone for high valence' do
177
+ traces = [base_trace.merge(emotional_valence: 0.5, emotional_intensity: 0.5)]
178
+ tone = described_class.emotional_tone(traces)
179
+ expect(tone[:tone]).to eq(:positive)
180
+ end
181
+
182
+ it 'classifies negative tone for low valence' do
183
+ traces = [base_trace.merge(emotional_valence: -0.5, emotional_intensity: 0.5)]
184
+ tone = described_class.emotional_tone(traces)
185
+ expect(tone[:tone]).to eq(:negative)
186
+ end
187
+
188
+ it 'classifies mixed tone for moderate valence' do
189
+ traces = [base_trace.merge(emotional_valence: 0.0, emotional_intensity: 0.5)]
190
+ tone = described_class.emotional_tone(traces)
191
+ expect(tone[:tone]).to eq(:mixed)
192
+ end
193
+ end
194
+
195
+ describe '.freshness_assessment' do
196
+ it 'classifies very_fresh traces' do
197
+ traces = [base_trace.merge(last_reinforced: Time.now.utc)]
198
+ result = described_class.freshness_assessment(traces)
199
+ expect(result[:label]).to eq(:very_fresh)
200
+ end
201
+
202
+ it 'classifies stale traces' do
203
+ traces = [base_trace.merge(last_reinforced: Time.now.utc - 100_000)]
204
+ result = described_class.freshness_assessment(traces)
205
+ expect(result[:label]).to eq(:stale)
206
+ end
207
+
208
+ it 'returns avg_age_seconds, freshest, stalest' do
209
+ result = described_class.freshness_assessment([base_trace])
210
+ expect(result).to have_key(:avg_age_seconds)
211
+ expect(result).to have_key(:freshest)
212
+ expect(result).to have_key(:stalest)
213
+ end
214
+ end
215
+
216
+ describe '.empty_summary' do
217
+ it 'returns zeroed-out summary for domain' do
218
+ result = described_class.empty_summary(:networking)
219
+ expect(result[:domain]).to eq(:networking)
220
+ expect(result[:knowledge_level]).to eq(:none)
221
+ expect(result[:trace_count]).to eq(0)
222
+ end
223
+ end
224
+ end
@@ -0,0 +1,169 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Language::Language::Runners::Language do
4
+ let(:lexicon) { Legion::Extensions::Agentic::Language::Language::Helpers::Lexicon.new }
5
+ let(:client) { Legion::Extensions::Agentic::Language::Language::Client.new(lexicon: lexicon) }
6
+
7
+ let(:now) { Time.now.utc }
8
+ let(:base_trace) do
9
+ {
10
+ trace_id: 'trace-1',
11
+ trace_type: :semantic,
12
+ content_payload: 'Networking uses TCP/IP',
13
+ strength: 0.7,
14
+ confidence: 0.8,
15
+ domain_tags: [:networking],
16
+ emotional_valence: 0.2,
17
+ emotional_intensity: 0.3,
18
+ last_reinforced: now,
19
+ created_at: now
20
+ }
21
+ end
22
+
23
+ def make_traces(count, domain: :networking)
24
+ count.times.map do |i|
25
+ base_trace.merge(
26
+ trace_id: "trace-#{i}",
27
+ domain_tags: [domain],
28
+ strength: 0.3 + (rand * 0.5)
29
+ )
30
+ end
31
+ end
32
+
33
+ describe '#summarize' do
34
+ it 'returns a summary hash for the domain' do
35
+ traces = make_traces(5)
36
+ result = client.summarize(domain: :networking, traces: traces)
37
+ expect(result[:domain]).to eq(:networking)
38
+ expect(result[:knowledge_level]).to eq(:moderate)
39
+ end
40
+
41
+ it 'stores the summary in the lexicon' do
42
+ traces = make_traces(5)
43
+ client.summarize(domain: :networking, traces: traces)
44
+ expect(lexicon.get_summary(:networking)).not_to be_nil
45
+ end
46
+
47
+ it 'filters traces by domain' do
48
+ traces = make_traces(5, domain: :networking) + make_traces(3, domain: :security)
49
+ result = client.summarize(domain: :networking, traces: traces)
50
+ expect(result[:trace_count]).to eq(5)
51
+ end
52
+
53
+ it 'respects minimum strength threshold' do
54
+ weak_traces = make_traces(3).map { |t| t.merge(strength: 0.05) }
55
+ strong_traces = make_traces(2)
56
+ result = client.summarize(domain: :networking, traces: weak_traces + strong_traces)
57
+ expect(result[:trace_count]).to eq(2)
58
+ end
59
+
60
+ it 'accepts string domain and converts to symbol' do
61
+ traces = make_traces(3)
62
+ result = client.summarize(domain: 'networking', traces: traces)
63
+ expect(result[:domain]).to eq(:networking)
64
+ end
65
+ end
66
+
67
+ describe '#what_do_i_know' do
68
+ it 'returns structured knowledge with prose' do
69
+ traces = make_traces(5)
70
+ result = client.what_do_i_know(domain: :networking, traces: traces)
71
+ expect(result[:domain]).to eq(:networking)
72
+ expect(result[:knowledge_level]).to be_a(Symbol)
73
+ expect(result[:prose]).to be_a(String)
74
+ expect(result[:fact_count]).to be >= 0
75
+ expect(result).to have_key(:summary)
76
+ end
77
+
78
+ it 'uses cached summary when not stale' do
79
+ traces = make_traces(5)
80
+ client.summarize(domain: :networking, traces: traces)
81
+
82
+ result = client.what_do_i_know(domain: :networking, traces: [])
83
+ expect(result[:knowledge_level]).to eq(:moderate)
84
+ end
85
+
86
+ it 're-summarizes when cache is stale' do
87
+ traces = make_traces(5)
88
+ client.summarize(domain: :networking, traces: traces)
89
+
90
+ stored = lexicon.get_summary(:networking)
91
+ stored[:cached_at] = Time.now.utc - 600
92
+
93
+ new_traces = make_traces(12)
94
+ result = client.what_do_i_know(domain: :networking, traces: new_traces)
95
+ expect(result[:knowledge_level]).to eq(:rich)
96
+ end
97
+
98
+ it 'includes key facts in prose when available' do
99
+ traces = make_traces(5)
100
+ result = client.what_do_i_know(domain: :networking, traces: traces)
101
+ expect(result[:prose]).to include('Key facts')
102
+ end
103
+
104
+ it 'handles no-facts prose' do
105
+ result = client.what_do_i_know(domain: :empty, traces: [])
106
+ expect(result[:prose]).to include('No specific facts')
107
+ end
108
+ end
109
+
110
+ describe '#can_answer_wonder?' do
111
+ it 'returns answerable when enough traces exist' do
112
+ traces = make_traces(5)
113
+ result = client.can_answer_wonder?(wonder: { domain: :networking }, traces: traces)
114
+ expect(result[:answerable]).to be true
115
+ expect(result[:confidence]).to be > 0.0
116
+ end
117
+
118
+ it 'returns not answerable when traces below threshold' do
119
+ traces = make_traces(2)
120
+ result = client.can_answer_wonder?(wonder: { domain: :networking }, traces: traces)
121
+ expect(result[:answerable]).to be false
122
+ expect(result[:confidence]).to eq(0.0)
123
+ end
124
+
125
+ it 'extracts domain from wonder hash' do
126
+ traces = make_traces(5, domain: :security)
127
+ result = client.can_answer_wonder?(wonder: { domain: :security }, traces: traces)
128
+ expect(result[:domain]).to eq(:security)
129
+ end
130
+
131
+ it 'defaults to :general when wonder is not a hash' do
132
+ result = client.can_answer_wonder?(wonder: 'what is life?', traces: [])
133
+ expect(result[:domain]).to eq(:general)
134
+ end
135
+
136
+ it 'includes trace_count and threshold' do
137
+ traces = make_traces(4)
138
+ result = client.can_answer_wonder?(wonder: { domain: :networking }, traces: traces)
139
+ expect(result[:trace_count]).to eq(4)
140
+ expect(result[:threshold]).to eq(3)
141
+ end
142
+ end
143
+
144
+ describe '#knowledge_map' do
145
+ it 'returns domains, known_domains, and total_domains' do
146
+ traces = make_traces(5)
147
+ client.summarize(domain: :networking, traces: traces)
148
+ result = client.knowledge_map
149
+ expect(result[:known_domains]).to include(:networking)
150
+ expect(result[:total_domains]).to eq(1)
151
+ expect(result[:domains]).to have_key(:networking)
152
+ end
153
+
154
+ it 'returns empty map with no summaries' do
155
+ result = client.knowledge_map
156
+ expect(result[:total_domains]).to eq(0)
157
+ expect(result[:known_domains]).to eq([])
158
+ end
159
+ end
160
+
161
+ describe '#language_stats' do
162
+ it 'returns cached_domains, known_domains, knowledge_map' do
163
+ result = client.language_stats
164
+ expect(result).to have_key(:cached_domains)
165
+ expect(result).to have_key(:known_domains)
166
+ expect(result).to have_key(:knowledge_map)
167
+ end
168
+ end
169
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/agentic/language/narrative_reasoning/client'
4
+
5
+ RSpec.describe Legion::Extensions::Agentic::Language::NarrativeReasoning::Client do
6
+ it 'responds to all runner methods' do
7
+ client = described_class.new
8
+ expect(client).to respond_to(:create_narrative)
9
+ expect(client).to respond_to(:add_narrative_event)
10
+ expect(client).to respond_to(:add_narrative_theme)
11
+ expect(client).to respond_to(:advance_narrative_arc)
12
+ expect(client).to respond_to(:trace_narrative_causes)
13
+ expect(client).to respond_to(:complete_narratives)
14
+ expect(client).to respond_to(:domain_narratives)
15
+ expect(client).to respond_to(:most_coherent_narratives)
16
+ expect(client).to respond_to(:update_narrative_reasoning)
17
+ expect(client).to respond_to(:narrative_reasoning_stats)
18
+ end
19
+ end