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,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Language::ConceptualMetaphor::Helpers::Metaphor do
4
+ subject(:metaphor) do
5
+ described_class.new(
6
+ source_domain: :money,
7
+ target_domain: :time,
8
+ metaphor_type: :structural,
9
+ mappings: { spend: :waste, save: :conserve, invest: :dedicate },
10
+ strength: 0.6,
11
+ conventionality: 0.8
12
+ )
13
+ end
14
+
15
+ describe '#initialize' do
16
+ it 'assigns a UUID id' do
17
+ expect(metaphor.id).to match(/\A[0-9a-f-]{36}\z/)
18
+ end
19
+
20
+ it 'stores source and target domains' do
21
+ expect(metaphor.source_domain).to eq(:money)
22
+ expect(metaphor.target_domain).to eq(:time)
23
+ end
24
+
25
+ it 'stores metaphor type' do
26
+ expect(metaphor.metaphor_type).to eq(:structural)
27
+ end
28
+
29
+ it 'stores mappings' do
30
+ expect(metaphor.mappings).to eq({ spend: :waste, save: :conserve, invest: :dedicate })
31
+ end
32
+
33
+ it 'clamps strength within bounds' do
34
+ over = described_class.new(source_domain: :a, target_domain: :b, metaphor_type: :structural,
35
+ mappings: {}, strength: 2.0)
36
+ expect(over.strength).to eq(1.0)
37
+ end
38
+ end
39
+
40
+ describe '#use!' do
41
+ it 'increments use count' do
42
+ expect { metaphor.use! }.to change(metaphor, :use_count).by(1)
43
+ end
44
+
45
+ it 'boosts strength' do
46
+ original = metaphor.strength
47
+ metaphor.use!
48
+ expect(metaphor.strength).to be > original
49
+ end
50
+
51
+ it 'increases conventionality' do
52
+ original = metaphor.conventionality
53
+ metaphor.use!
54
+ expect(metaphor.conventionality).to be >= original
55
+ end
56
+ end
57
+
58
+ describe '#map_concept' do
59
+ it 'returns mapped target concept' do
60
+ expect(metaphor.map_concept(:spend)).to eq(:waste)
61
+ end
62
+
63
+ it 'returns nil for unmapped concept' do
64
+ expect(metaphor.map_concept(:borrow)).to be_nil
65
+ end
66
+ end
67
+
68
+ describe '#coverage' do
69
+ it 'returns ratio of mapped concepts' do
70
+ expect(metaphor.coverage).to eq(1.0)
71
+ end
72
+
73
+ it 'returns 0.0 for empty mappings' do
74
+ empty = described_class.new(source_domain: :a, target_domain: :b,
75
+ metaphor_type: :structural, mappings: {})
76
+ expect(empty.coverage).to eq(0.0)
77
+ end
78
+ end
79
+
80
+ describe '#conventional?' do
81
+ it 'returns true when conventionality is high' do
82
+ expect(metaphor).to be_conventional
83
+ end
84
+ end
85
+
86
+ describe '#novel?' do
87
+ it 'returns false when conventionality is high' do
88
+ expect(metaphor).not_to be_novel
89
+ end
90
+
91
+ it 'returns true when conventionality is low' do
92
+ fresh = described_class.new(source_domain: :a, target_domain: :b,
93
+ metaphor_type: :structural, mappings: {},
94
+ conventionality: 0.2)
95
+ expect(fresh).to be_novel
96
+ end
97
+ end
98
+
99
+ describe '#conventionality_label' do
100
+ it 'returns a label symbol' do
101
+ expect(metaphor.conventionality_label).to eq(:dead)
102
+ end
103
+ end
104
+
105
+ describe '#strength_label' do
106
+ it 'returns a label symbol' do
107
+ expect(metaphor.strength_label).to be_a(Symbol)
108
+ end
109
+ end
110
+
111
+ describe '#decay!' do
112
+ it 'reduces strength' do
113
+ original = metaphor.strength
114
+ metaphor.decay!
115
+ expect(metaphor.strength).to be < original
116
+ end
117
+ end
118
+
119
+ describe '#add_entailment' do
120
+ it 'adds entailment to the list' do
121
+ metaphor.add_entailment('wasting time is losing money')
122
+ expect(metaphor.entailments).to include('wasting time is losing money')
123
+ end
124
+ end
125
+
126
+ describe '#to_h' do
127
+ it 'returns a hash representation' do
128
+ hash = metaphor.to_h
129
+ expect(hash).to include(:id, :source_domain, :target_domain, :mappings,
130
+ :strength, :conventionality, :coverage)
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Language::ConceptualMetaphor::Runners::ConceptualMetaphor do
4
+ let(:runner_host) do
5
+ obj = Object.new
6
+ obj.extend(described_class)
7
+ obj
8
+ end
9
+
10
+ describe '#create_metaphor' do
11
+ it 'creates a metaphor successfully' do
12
+ result = runner_host.create_metaphor(
13
+ source_domain: :money, target_domain: :time,
14
+ metaphor_type: :structural, mappings: { spend: :waste }
15
+ )
16
+ expect(result[:success]).to be true
17
+ expect(result[:metaphor_id]).to be_a(String)
18
+ end
19
+
20
+ it 'rejects invalid metaphor type' do
21
+ result = runner_host.create_metaphor(
22
+ source_domain: :a, target_domain: :b,
23
+ metaphor_type: :invalid, mappings: {}
24
+ )
25
+ expect(result[:success]).to be false
26
+ end
27
+ end
28
+
29
+ describe '#apply_metaphor' do
30
+ it 'maps a concept through a metaphor' do
31
+ created = runner_host.create_metaphor(
32
+ source_domain: :money, target_domain: :time,
33
+ metaphor_type: :structural, mappings: { spend: :waste }
34
+ )
35
+ result = runner_host.apply_metaphor(
36
+ metaphor_id: created[:metaphor_id], source_concept: :spend
37
+ )
38
+ expect(result[:success]).to be true
39
+ expect(result[:target_concept]).to eq(:waste)
40
+ end
41
+ end
42
+
43
+ describe '#add_metaphor_entailment' do
44
+ it 'adds an entailment' do
45
+ created = runner_host.create_metaphor(
46
+ source_domain: :money, target_domain: :time,
47
+ metaphor_type: :structural, mappings: {}
48
+ )
49
+ result = runner_host.add_metaphor_entailment(
50
+ metaphor_id: created[:metaphor_id],
51
+ entailment: 'wasting time is losing money'
52
+ )
53
+ expect(result[:success]).to be true
54
+ end
55
+ end
56
+
57
+ describe '#find_metaphors_for' do
58
+ it 'finds metaphors for a domain' do
59
+ runner_host.create_metaphor(
60
+ source_domain: :money, target_domain: :time,
61
+ metaphor_type: :structural, mappings: {}
62
+ )
63
+ result = runner_host.find_metaphors_for(domain: :time)
64
+ expect(result[:success]).to be true
65
+ expect(result[:count]).to eq(1)
66
+ end
67
+ end
68
+
69
+ describe '#conventional_metaphors' do
70
+ it 'returns conventional metaphors' do
71
+ runner_host.create_metaphor(
72
+ source_domain: :war, target_domain: :argument,
73
+ metaphor_type: :structural, mappings: {},
74
+ conventionality: 0.9
75
+ )
76
+ result = runner_host.conventional_metaphors
77
+ expect(result[:success]).to be true
78
+ expect(result[:count]).to eq(1)
79
+ end
80
+ end
81
+
82
+ describe '#novel_metaphors' do
83
+ it 'returns novel metaphors' do
84
+ runner_host.create_metaphor(
85
+ source_domain: :ocean, target_domain: :emotion,
86
+ metaphor_type: :ontological, mappings: {},
87
+ conventionality: 0.1
88
+ )
89
+ result = runner_host.novel_metaphors
90
+ expect(result[:success]).to be true
91
+ expect(result[:count]).to eq(1)
92
+ end
93
+ end
94
+
95
+ describe '#strongest_metaphors' do
96
+ it 'returns strongest metaphors' do
97
+ runner_host.create_metaphor(
98
+ source_domain: :a, target_domain: :b,
99
+ metaphor_type: :structural, mappings: {}, strength: 0.9
100
+ )
101
+ result = runner_host.strongest_metaphors(limit: 3)
102
+ expect(result[:success]).to be true
103
+ end
104
+ end
105
+
106
+ describe '#metaphors_by_type' do
107
+ it 'filters by type' do
108
+ runner_host.create_metaphor(
109
+ source_domain: :a, target_domain: :b,
110
+ metaphor_type: :orientational, mappings: {}
111
+ )
112
+ result = runner_host.metaphors_by_type(metaphor_type: :orientational)
113
+ expect(result[:success]).to be true
114
+ expect(result[:count]).to eq(1)
115
+ end
116
+ end
117
+
118
+ describe '#update_conceptual_metaphor' do
119
+ it 'runs decay and prune cycle' do
120
+ result = runner_host.update_conceptual_metaphor
121
+ expect(result[:success]).to be true
122
+ expect(result).to include(:pruned)
123
+ end
124
+ end
125
+
126
+ describe '#conceptual_metaphor_stats' do
127
+ it 'returns stats' do
128
+ result = runner_host.conceptual_metaphor_stats
129
+ expect(result[:success]).to be true
130
+ expect(result).to include(:total_metaphors, :total_domains)
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,227 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Language::FrameSemantics::Helpers::FrameEngine do
4
+ subject(:engine) { described_class.new }
5
+
6
+ let(:frame) { engine.create_frame(name: :commercial_transaction, domain: :commerce) }
7
+
8
+ before do
9
+ frame.add_slot(name: :buyer)
10
+ frame.add_slot(name: :seller)
11
+ frame.add_slot(name: :goods)
12
+ end
13
+
14
+ describe '#create_frame' do
15
+ it 'returns a Frame object' do
16
+ f = engine.create_frame(name: :motion, domain: :physics)
17
+ expect(f).to be_a(Legion::Extensions::Agentic::Language::FrameSemantics::Helpers::Frame)
18
+ end
19
+
20
+ it 'creates frame with slots from hash' do
21
+ f = engine.create_frame(
22
+ name: :competition,
23
+ domain: :sports,
24
+ slots: { winner: { type: :core, required: true }, loser: { type: :peripheral, required: false } }
25
+ )
26
+ expect(f.slots[:winner][:type]).to eq(:core)
27
+ expect(f.slots[:loser][:type]).to eq(:peripheral)
28
+ end
29
+ end
30
+
31
+ describe '#define_slot' do
32
+ it 'adds a slot to an existing frame' do
33
+ result = engine.define_slot(frame_id: frame.id, name: :price)
34
+ expect(result).to be_a(Legion::Extensions::Agentic::Language::FrameSemantics::Helpers::Frame)
35
+ expect(frame.slots[:price]).not_to be_nil
36
+ end
37
+
38
+ it 'returns nil for unknown frame_id' do
39
+ expect(engine.define_slot(frame_id: 'bogus', name: :price)).to be_nil
40
+ end
41
+ end
42
+
43
+ describe '#fill_slot' do
44
+ it 'fills a slot and returns true' do
45
+ expect(engine.fill_slot(frame_id: frame.id, slot_name: :buyer, filler: 'Alice')).to be true
46
+ end
47
+
48
+ it 'returns false for unknown frame' do
49
+ expect(engine.fill_slot(frame_id: 'bogus', slot_name: :buyer, filler: 'x')).to be false
50
+ end
51
+
52
+ it 'activates the frame when slot is filled' do
53
+ initial = frame.activation
54
+ engine.fill_slot(frame_id: frame.id, slot_name: :buyer, filler: 'Alice')
55
+ expect(frame.activation_count).to be >= 1
56
+ expect(frame.activation).to be >= initial
57
+ end
58
+ end
59
+
60
+ describe '#instantiate_frame' do
61
+ before do
62
+ engine.fill_slot(frame_id: frame.id, slot_name: :buyer, filler: 'Alice')
63
+ engine.fill_slot(frame_id: frame.id, slot_name: :seller, filler: 'Bob')
64
+ end
65
+
66
+ it 'creates a FrameInstance snapshot' do
67
+ inst = engine.instantiate_frame(frame_id: frame.id, context: 'coffee shop')
68
+ expect(inst).to be_a(Legion::Extensions::Agentic::Language::FrameSemantics::Helpers::FrameInstance)
69
+ expect(inst.frame_id).to eq(frame.id)
70
+ expect(inst.context).to eq('coffee shop')
71
+ end
72
+
73
+ it 'returns nil for unknown frame' do
74
+ expect(engine.instantiate_frame(frame_id: 'bogus', context: 'x')).to be_nil
75
+ end
76
+
77
+ it 'snapshots current slot fillers' do
78
+ inst = engine.instantiate_frame(frame_id: frame.id, context: 'test')
79
+ expect(inst.slot_fillers[:buyer]).to eq('Alice')
80
+ end
81
+ end
82
+
83
+ describe '#add_frame_relation' do
84
+ it 'adds a valid relation' do
85
+ target = engine.create_frame(name: :transfer, domain: :commerce)
86
+ result = engine.add_frame_relation(
87
+ frame_id: frame.id,
88
+ relation: :has_subframe,
89
+ target_frame_id: target.id
90
+ )
91
+ expect(result).to be true
92
+ expect(frame.relations.size).to eq(1)
93
+ end
94
+
95
+ it 'rejects invalid relation type' do
96
+ result = engine.add_frame_relation(
97
+ frame_id: frame.id,
98
+ relation: :invalid_relation,
99
+ target_frame_id: SecureRandom.uuid
100
+ )
101
+ expect(result).to be false
102
+ end
103
+
104
+ it 'returns false for unknown frame_id' do
105
+ expect(
106
+ engine.add_frame_relation(frame_id: 'bogus', relation: :uses, target_frame_id: SecureRandom.uuid)
107
+ ).to be false
108
+ end
109
+ end
110
+
111
+ describe '#activate_frame' do
112
+ it 'activates an existing frame' do
113
+ initial_count = frame.activation_count
114
+ engine.activate_frame(frame_id: frame.id)
115
+ expect(frame.activation_count).to eq(initial_count + 1)
116
+ end
117
+
118
+ it 'returns false for unknown frame' do
119
+ expect(engine.activate_frame(frame_id: 'bogus')).to be false
120
+ end
121
+ end
122
+
123
+ describe '#active_frames' do
124
+ it 'returns frames with activation > 0.5' do
125
+ 5.times { engine.activate_frame(frame_id: frame.id) }
126
+ expect(engine.active_frames).to include(frame)
127
+ end
128
+
129
+ it 'excludes frames below threshold' do
130
+ new_engine = described_class.new
131
+ f = new_engine.create_frame(name: :cold, domain: :test)
132
+ 100.times { f.decay! }
133
+ expect(new_engine.active_frames).not_to include(f)
134
+ end
135
+ end
136
+
137
+ describe '#frames_by_domain' do
138
+ before do
139
+ engine.create_frame(name: :motion, domain: :physics)
140
+ engine.create_frame(name: :competition, domain: :sports)
141
+ end
142
+
143
+ it 'returns only frames matching the domain' do
144
+ result = engine.frames_by_domain(domain: :physics)
145
+ expect(result.map(&:domain)).to all(eq(:physics))
146
+ end
147
+ end
148
+
149
+ describe '#related_frames' do
150
+ it 'traverses relations to return related frames' do
151
+ target = engine.create_frame(name: :payment, domain: :commerce)
152
+ engine.add_frame_relation(frame_id: frame.id, relation: :has_subframe, target_frame_id: target.id)
153
+ related = engine.related_frames(frame_id: frame.id)
154
+ expect(related).to include(target)
155
+ end
156
+
157
+ it 'returns empty array for unknown frame' do
158
+ expect(engine.related_frames(frame_id: 'bogus')).to eq([])
159
+ end
160
+ end
161
+
162
+ describe '#most_activated' do
163
+ it 'returns top N frames sorted by activation desc' do
164
+ f2 = engine.create_frame(name: :motion, domain: :physics)
165
+ 5.times { engine.activate_frame(frame_id: frame.id) }
166
+ result = engine.most_activated(limit: 2)
167
+ expect(result.first).to eq(frame)
168
+ expect(result).not_to include(f2) if result.size < 2
169
+ end
170
+ end
171
+
172
+ describe '#instances_for_frame' do
173
+ it 'returns all instances of a specific frame' do
174
+ engine.instantiate_frame(frame_id: frame.id, context: 'ctx1')
175
+ engine.instantiate_frame(frame_id: frame.id, context: 'ctx2')
176
+ expect(engine.instances_for_frame(frame_id: frame.id).size).to eq(2)
177
+ end
178
+ end
179
+
180
+ describe '#complete_frames' do
181
+ it 'returns only complete frames' do
182
+ engine.fill_slot(frame_id: frame.id, slot_name: :buyer, filler: 'Alice')
183
+ engine.fill_slot(frame_id: frame.id, slot_name: :seller, filler: 'Bob')
184
+ engine.fill_slot(frame_id: frame.id, slot_name: :goods, filler: 'coffee')
185
+ expect(engine.complete_frames).to include(frame)
186
+ end
187
+
188
+ it 'excludes incomplete frames' do
189
+ engine.fill_slot(frame_id: frame.id, slot_name: :buyer, filler: 'Alice')
190
+ expect(engine.complete_frames).not_to include(frame)
191
+ end
192
+ end
193
+
194
+ describe '#decay_all' do
195
+ it 'decays all frame activations' do
196
+ initial = frame.activation
197
+ engine.decay_all
198
+ expect(frame.activation).to be < initial
199
+ end
200
+ end
201
+
202
+ describe '#prune_inactive' do
203
+ it 'removes frames with activation <= 0.05' do
204
+ f = engine.create_frame(name: :ghost, domain: :test)
205
+ 100.times { f.decay! }
206
+ engine.prune_inactive
207
+ expect(engine.instances_for_frame(frame_id: f.id)).to be_empty
208
+ end
209
+
210
+ it 'retains frames with activation above threshold' do
211
+ 5.times { engine.activate_frame(frame_id: frame.id) }
212
+ engine.prune_inactive
213
+ expect(engine.active_frames).to include(frame)
214
+ end
215
+ end
216
+
217
+ describe '#to_h' do
218
+ it 'returns stats hash with expected keys' do
219
+ h = engine.to_h
220
+ expect(h).to include(:frame_count, :instance_count, :active_count, :complete_count, :domains)
221
+ end
222
+
223
+ it 'reflects current state' do
224
+ expect(engine.to_h[:frame_count]).to eq(1)
225
+ end
226
+ end
227
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Language::FrameSemantics::Helpers::FrameInstance do
4
+ let(:frame_id) { SecureRandom.uuid }
5
+ let(:frame_name) { :commercial_transaction }
6
+ let(:slot_fillers) do
7
+ { buyer: 'Alice', seller: 'Bob', goods: nil }
8
+ end
9
+
10
+ subject(:instance) do
11
+ described_class.new(
12
+ frame_id: frame_id,
13
+ frame_name: frame_name,
14
+ slot_fillers: slot_fillers,
15
+ context: 'buying coffee',
16
+ confidence: 0.85
17
+ )
18
+ end
19
+
20
+ describe '#initialize' do
21
+ it 'assigns a uuid id' do
22
+ expect(instance.id).to match(/\A[0-9a-f-]{36}\z/)
23
+ end
24
+
25
+ it 'stores frame metadata' do
26
+ expect(instance.frame_id).to eq(frame_id)
27
+ expect(instance.frame_name).to eq(frame_name)
28
+ end
29
+
30
+ it 'duplicates slot_fillers' do
31
+ inst = instance
32
+ slot_fillers[:buyer] = 'Charlie'
33
+ expect(inst.slot_fillers[:buyer]).to eq('Alice')
34
+ end
35
+
36
+ it 'stores context and confidence' do
37
+ expect(instance.context).to eq('buying coffee')
38
+ expect(instance.confidence).to eq(0.85)
39
+ end
40
+ end
41
+
42
+ describe '#filled_count' do
43
+ it 'counts non-nil fillers' do
44
+ expect(instance.filled_count).to eq(2)
45
+ end
46
+
47
+ it 'returns 0 when all fillers are nil' do
48
+ empty = described_class.new(
49
+ frame_id: frame_id, frame_name: frame_name,
50
+ slot_fillers: { buyer: nil }, context: 'x'
51
+ )
52
+ expect(empty.filled_count).to eq(0)
53
+ end
54
+ end
55
+
56
+ describe '#complete?' do
57
+ it 'returns true when at least one slot is filled' do
58
+ expect(instance.complete?).to be true
59
+ end
60
+
61
+ it 'returns false when all slots are nil' do
62
+ empty = described_class.new(
63
+ frame_id: frame_id, frame_name: frame_name,
64
+ slot_fillers: { buyer: nil }, context: 'x'
65
+ )
66
+ expect(empty.complete?).to be false
67
+ end
68
+ end
69
+
70
+ describe '#to_h' do
71
+ it 'returns expected keys' do
72
+ h = instance.to_h
73
+ expect(h).to include(:id, :frame_id, :frame_name, :slot_fillers,
74
+ :context, :confidence, :filled_count, :complete, :created_at)
75
+ end
76
+
77
+ it 'includes filled_count and complete flag' do
78
+ h = instance.to_h
79
+ expect(h[:filled_count]).to eq(2)
80
+ expect(h[:complete]).to be true
81
+ end
82
+ end
83
+ end