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,155 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Language::Grammar::Helpers::Construction do
4
+ subject(:construction) do
5
+ described_class.new(
6
+ form: 'the cat',
7
+ meaning: 'definite feline entity',
8
+ expression_type: :nominal,
9
+ domain: 'linguistics'
10
+ )
11
+ end
12
+
13
+ describe '#initialize' do
14
+ it 'assigns a UUID id' do
15
+ expect(construction.id).to match(/\A[0-9a-f-]{36}\z/)
16
+ end
17
+
18
+ it 'stores form and meaning' do
19
+ expect(construction.form).to eq('the cat')
20
+ expect(construction.meaning).to eq('definite feline entity')
21
+ end
22
+
23
+ it 'symbolizes expression_type' do
24
+ expect(construction.expression_type).to eq(:nominal)
25
+ end
26
+
27
+ it 'defaults activation to DEFAULT_ACTIVATION' do
28
+ expect(construction.activation).to eq(0.3)
29
+ end
30
+
31
+ it 'starts with zero usage_count' do
32
+ expect(construction.usage_count).to eq(0)
33
+ end
34
+
35
+ it 'sets domain' do
36
+ expect(construction.domain).to eq('linguistics')
37
+ end
38
+
39
+ it 'sets created_at to a Time' do
40
+ expect(construction.created_at).to be_a(Time)
41
+ end
42
+ end
43
+
44
+ describe '#use!' do
45
+ it 'increments usage_count' do
46
+ construction.use!
47
+ expect(construction.usage_count).to eq(1)
48
+ end
49
+
50
+ it 'boosts activation by ACTIVATION_BOOST' do
51
+ construction.use!
52
+ expect(construction.activation).to be_within(0.001).of(0.4)
53
+ end
54
+
55
+ it 'caps activation at 1.0' do
56
+ c = described_class.new(form: 'x', meaning: 'y', expression_type: :nominal, domain: 'd', activation: 0.95)
57
+ c.use!
58
+ expect(c.activation).to eq(1.0)
59
+ end
60
+
61
+ it 'returns self for chaining' do
62
+ expect(construction.use!).to eq(construction)
63
+ end
64
+
65
+ it 'accumulates usage on repeated calls' do
66
+ 5.times { construction.use! }
67
+ expect(construction.usage_count).to eq(5)
68
+ end
69
+ end
70
+
71
+ describe '#decay!' do
72
+ it 'reduces activation by ACTIVATION_DECAY' do
73
+ construction.decay!
74
+ expect(construction.activation).to be_within(0.001).of(0.28)
75
+ end
76
+
77
+ it 'floors activation at 0.0' do
78
+ c = described_class.new(form: 'x', meaning: 'y', expression_type: :nominal, domain: 'd', activation: 0.01)
79
+ c.decay!
80
+ expect(c.activation).to eq(0.0)
81
+ end
82
+
83
+ it 'returns self for chaining' do
84
+ expect(construction.decay!).to eq(construction)
85
+ end
86
+ end
87
+
88
+ describe '#entrenched?' do
89
+ it 'returns false when activation is below threshold' do
90
+ expect(construction.entrenched?).to be false
91
+ end
92
+
93
+ it 'returns true when activation reaches ENTRENCHMENT_THRESHOLD' do
94
+ c = described_class.new(form: 'x', meaning: 'y', expression_type: :nominal, domain: 'd', activation: 0.8)
95
+ expect(c.entrenched?).to be true
96
+ end
97
+
98
+ it 'returns true when activation exceeds threshold' do
99
+ c = described_class.new(form: 'x', meaning: 'y', expression_type: :nominal, domain: 'd', activation: 0.95)
100
+ expect(c.entrenched?).to be true
101
+ end
102
+
103
+ it 'becomes entrenched after enough use! calls' do
104
+ c = described_class.new(form: 'run', meaning: 'motion', expression_type: :relational, domain: 'motion')
105
+ # Start at 0.3, each use! adds 0.1 → need 5 uses to reach 0.8
106
+ 5.times { c.use! }
107
+ expect(c.entrenched?).to be true
108
+ end
109
+ end
110
+
111
+ describe '#activation_label' do
112
+ it 'returns :ad_hoc for low activation' do
113
+ c = described_class.new(form: 'x', meaning: 'y', expression_type: :nominal, domain: 'd', activation: 0.1)
114
+ expect(c.activation_label).to eq(:ad_hoc)
115
+ end
116
+
117
+ it 'returns :novel for activation 0.3' do
118
+ expect(construction.activation_label).to eq(:novel)
119
+ end
120
+
121
+ it 'returns :familiar for activation 0.5' do
122
+ c = described_class.new(form: 'x', meaning: 'y', expression_type: :nominal, domain: 'd', activation: 0.5)
123
+ expect(c.activation_label).to eq(:familiar)
124
+ end
125
+
126
+ it 'returns :conventional for activation 0.7' do
127
+ c = described_class.new(form: 'x', meaning: 'y', expression_type: :nominal, domain: 'd', activation: 0.7)
128
+ expect(c.activation_label).to eq(:conventional)
129
+ end
130
+
131
+ it 'returns :entrenched for activation >= 0.8' do
132
+ c = described_class.new(form: 'x', meaning: 'y', expression_type: :nominal, domain: 'd', activation: 0.8)
133
+ expect(c.activation_label).to eq(:entrenched)
134
+ end
135
+ end
136
+
137
+ describe '#to_h' do
138
+ it 'returns a hash with all expected keys' do
139
+ h = construction.to_h
140
+ expect(h).to include(:id, :form, :meaning, :expression_type, :activation, :usage_count, :domain, :entrenched,
141
+ :activation_label, :created_at)
142
+ end
143
+
144
+ it 'reflects current state' do
145
+ construction.use!
146
+ h = construction.to_h
147
+ expect(h[:usage_count]).to eq(1)
148
+ expect(h[:activation]).to be > 0.3
149
+ end
150
+
151
+ it 'includes entrenched flag' do
152
+ expect(construction.to_h[:entrenched]).to be false
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,206 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Language::Grammar::Helpers::GrammarEngine do
4
+ subject(:engine) { described_class.new }
5
+
6
+ let(:construction_params) do
7
+ { form: 'the bird', meaning: 'definite avian', expression_type: :nominal, domain: 'nature' }
8
+ end
9
+
10
+ let(:construal_params) do
11
+ { scene: 'bird on branch', perspective: 'ground-level', figure: 'bird', ground: 'branch' }
12
+ end
13
+
14
+ describe '#create_construction' do
15
+ it 'returns a Construction instance' do
16
+ c = engine.create_construction(**construction_params)
17
+ expect(c).to be_a(Legion::Extensions::Agentic::Language::Grammar::Helpers::Construction)
18
+ end
19
+
20
+ it 'stores the construction internally' do
21
+ engine.create_construction(**construction_params)
22
+ expect(engine.to_h[:constructions_count]).to eq(1)
23
+ end
24
+
25
+ it 'assigns correct attributes' do
26
+ c = engine.create_construction(**construction_params)
27
+ expect(c.form).to eq('the bird')
28
+ expect(c.meaning).to eq('definite avian')
29
+ expect(c.expression_type).to eq(:nominal)
30
+ expect(c.domain).to eq('nature')
31
+ end
32
+ end
33
+
34
+ describe '#create_construal' do
35
+ it 'returns a Construal instance' do
36
+ c = engine.create_construal(**construal_params)
37
+ expect(c).to be_a(Legion::Extensions::Agentic::Language::Grammar::Helpers::Construal)
38
+ end
39
+
40
+ it 'stores the construal' do
41
+ engine.create_construal(**construal_params)
42
+ expect(engine.to_h[:construals_count]).to eq(1)
43
+ end
44
+
45
+ it 'forwards all keyword args' do
46
+ c = engine.create_construal(**construal_params, specificity: :detailed, scope: :global, dynamicity: 0.8)
47
+ expect(c.specificity).to eq(:detailed)
48
+ expect(c.scope).to eq(:global)
49
+ expect(c.dynamicity).to eq(0.8)
50
+ end
51
+ end
52
+
53
+ describe '#use_construction' do
54
+ it 'returns nil for unknown id' do
55
+ expect(engine.use_construction(construction_id: 'nonexistent')).to be_nil
56
+ end
57
+
58
+ it 'increments usage_count' do
59
+ c = engine.create_construction(**construction_params)
60
+ engine.use_construction(construction_id: c.id)
61
+ expect(c.usage_count).to eq(1)
62
+ end
63
+
64
+ it 'returns the updated construction' do
65
+ c = engine.create_construction(**construction_params)
66
+ result = engine.use_construction(construction_id: c.id)
67
+ expect(result).to eq(c)
68
+ end
69
+ end
70
+
71
+ describe '#construals_for_scene' do
72
+ it 'returns empty array when no construals exist' do
73
+ expect(engine.construals_for_scene(scene: 'nothing')).to eq([])
74
+ end
75
+
76
+ it 'returns only construals matching the scene' do
77
+ engine.create_construal(**construal_params)
78
+ engine.create_construal(scene: 'other scene', perspective: 'p', figure: 'f', ground: 'g')
79
+ results = engine.construals_for_scene(scene: 'bird on branch')
80
+ expect(results.size).to eq(1)
81
+ expect(results.first.scene).to eq('bird on branch')
82
+ end
83
+
84
+ it 'returns multiple construals for same scene (different perspectives)' do
85
+ engine.create_construal(**construal_params)
86
+ engine.create_construal(scene: 'bird on branch', perspective: 'aerial', figure: 'bird', ground: 'branch')
87
+ results = engine.construals_for_scene(scene: 'bird on branch')
88
+ expect(results.size).to eq(2)
89
+ end
90
+ end
91
+
92
+ describe '#entrenched_constructions' do
93
+ it 'returns empty when none are entrenched' do
94
+ engine.create_construction(**construction_params)
95
+ expect(engine.entrenched_constructions).to be_empty
96
+ end
97
+
98
+ it 'returns constructions once they reach entrenchment threshold via use!' do
99
+ c = engine.create_construction(**construction_params)
100
+ 5.times { engine.use_construction(construction_id: c.id) }
101
+ expect(engine.entrenched_constructions).to include(c)
102
+ end
103
+ end
104
+
105
+ describe '#constructions_by_domain' do
106
+ it 'returns only constructions in the specified domain' do
107
+ engine.create_construction(**construction_params)
108
+ engine.create_construction(form: 'run', meaning: 'motion', expression_type: :relational, domain: 'motion')
109
+ results = engine.constructions_by_domain(domain: 'nature')
110
+ expect(results.size).to eq(1)
111
+ expect(results.first.domain).to eq('nature')
112
+ end
113
+ end
114
+
115
+ describe '#constructions_by_type' do
116
+ it 'returns only constructions with the specified expression_type' do
117
+ engine.create_construction(**construction_params) # :nominal
118
+ engine.create_construction(form: 'runs', meaning: 'action', expression_type: :relational, domain: 'action')
119
+ results = engine.constructions_by_type(expression_type: :nominal)
120
+ expect(results.size).to eq(1)
121
+ expect(results.first.expression_type).to eq(:nominal)
122
+ end
123
+
124
+ it 'accepts string type and symbolizes it' do
125
+ engine.create_construction(**construction_params)
126
+ results = engine.constructions_by_type(expression_type: 'nominal')
127
+ expect(results.size).to eq(1)
128
+ end
129
+ end
130
+
131
+ describe '#most_used' do
132
+ it 'returns constructions sorted by usage_count desc' do
133
+ c1 = engine.create_construction(**construction_params)
134
+ c2 = engine.create_construction(form: 'runs', meaning: 'action', expression_type: :relational, domain: 'action')
135
+ 3.times { engine.use_construction(construction_id: c1.id) }
136
+ engine.use_construction(construction_id: c2.id)
137
+ top = engine.most_used(limit: 2)
138
+ expect(top.first).to eq(c1)
139
+ expect(top.last).to eq(c2)
140
+ end
141
+
142
+ it 'respects the limit' do
143
+ 5.times do |i|
144
+ engine.create_construction(form: "form#{i}", meaning: "m#{i}", expression_type: :nominal, domain: 'd')
145
+ end
146
+ expect(engine.most_used(limit: 3).size).to eq(3)
147
+ end
148
+ end
149
+
150
+ describe '#most_activated' do
151
+ it 'returns constructions sorted by activation desc' do
152
+ c1 = engine.create_construction(**construction_params)
153
+ c2 = engine.create_construction(form: 'x', meaning: 'y', expression_type: :nominal, domain: 'd', activation: 0.8)
154
+ top = engine.most_activated(limit: 2)
155
+ expect(top.first).to eq(c2)
156
+ expect(top.last).to eq(c1)
157
+ end
158
+ end
159
+
160
+ describe '#decay_all' do
161
+ it 'reduces activation of all constructions' do
162
+ c = engine.create_construction(**construction_params)
163
+ original = c.activation
164
+ engine.decay_all
165
+ expect(c.activation).to be < original
166
+ end
167
+ end
168
+
169
+ describe '#prune_inactive' do
170
+ it 'removes constructions with activation <= 0.05' do
171
+ c1 = engine.create_construction(**construction_params) # activation 0.3 — kept
172
+ c2 = engine.create_construction(form: 'x', meaning: 'y', expression_type: :nominal, domain: 'd', activation: 0.05)
173
+ engine.prune_inactive
174
+ expect(engine.to_h[:constructions_count]).to eq(1)
175
+ expect(engine.constructions_by_domain(domain: 'nature')).to include(c1)
176
+ expect(engine.constructions_by_domain(domain: 'd')).not_to include(c2)
177
+ end
178
+
179
+ it 'does not remove constructions with activation above 0.05' do
180
+ engine.create_construction(**construction_params)
181
+ engine.prune_inactive
182
+ expect(engine.to_h[:constructions_count]).to eq(1)
183
+ end
184
+ end
185
+
186
+ describe '#to_h' do
187
+ it 'returns counts' do
188
+ engine.create_construction(**construction_params)
189
+ engine.create_construal(**construal_params)
190
+ h = engine.to_h
191
+ expect(h[:constructions_count]).to eq(1)
192
+ expect(h[:construals_count]).to eq(1)
193
+ expect(h[:entrenched_count]).to eq(0)
194
+ end
195
+ end
196
+
197
+ describe 'entrenchment via repeated use' do
198
+ it 'tracks a construction becoming entrenched after 5 uses from default activation' do
199
+ c = engine.create_construction(form: 'be', meaning: 'existence', expression_type: :relational, domain: 'core')
200
+ expect(c.entrenched?).to be false
201
+ 5.times { engine.use_construction(construction_id: c.id) }
202
+ expect(c.entrenched?).to be true
203
+ expect(engine.entrenched_constructions).to include(c)
204
+ end
205
+ end
206
+ end
@@ -0,0 +1,189 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Language::Grammar::Runners::CognitiveGrammar do
4
+ subject(:runner) do
5
+ Class.new do
6
+ include Legion::Extensions::Agentic::Language::Grammar::Runners::CognitiveGrammar
7
+
8
+ def engine
9
+ @engine ||= Legion::Extensions::Agentic::Language::Grammar::Helpers::GrammarEngine.new
10
+ end
11
+ end.new
12
+ end
13
+
14
+ let(:construction_params) do
15
+ { form: 'she runs', meaning: 'agent motion predication', expression_type: :clausal, domain: 'motion' }
16
+ end
17
+
18
+ describe '#create_grammar_construction' do
19
+ it 'returns a hash with construction data' do
20
+ result = runner.create_grammar_construction(**construction_params)
21
+ expect(result).to include(:id, :form, :meaning, :expression_type, :activation, :usage_count)
22
+ end
23
+
24
+ it 'includes the provided form' do
25
+ result = runner.create_grammar_construction(**construction_params)
26
+ expect(result[:form]).to eq('she runs')
27
+ end
28
+
29
+ it 'starts with default activation' do
30
+ result = runner.create_grammar_construction(**construction_params)
31
+ expect(result[:activation]).to eq(0.3)
32
+ end
33
+ end
34
+
35
+ describe '#create_grammar_construal' do
36
+ it 'returns a hash with construal data' do
37
+ result = runner.create_grammar_construal(
38
+ scene: 'bird on branch', perspective: 'worm-eye', figure: 'bird', ground: 'branch'
39
+ )
40
+ expect(result).to include(:id, :scene, :perspective, :figure, :ground, :specificity, :scope, :dynamicity)
41
+ end
42
+
43
+ it 'uses defaults for optional params' do
44
+ result = runner.create_grammar_construal(
45
+ scene: 's', perspective: 'p', figure: 'f', ground: 'g'
46
+ )
47
+ expect(result[:specificity]).to eq(:intermediate)
48
+ expect(result[:scope]).to eq(:local)
49
+ expect(result[:dynamicity]).to eq(0.5)
50
+ end
51
+
52
+ it 'respects provided specificity, scope, dynamicity' do
53
+ result = runner.create_grammar_construal(
54
+ scene: 's', perspective: 'p', figure: 'f', ground: 'g',
55
+ specificity: :detailed, scope: :global, dynamicity: 0.9
56
+ )
57
+ expect(result[:specificity]).to eq(:detailed)
58
+ expect(result[:scope]).to eq(:global)
59
+ expect(result[:dynamicity]).to eq(0.9)
60
+ end
61
+ end
62
+
63
+ describe '#use_grammar_construction' do
64
+ it 'returns found: false for unknown id' do
65
+ result = runner.use_grammar_construction(construction_id: 'nonexistent')
66
+ expect(result[:found]).to be false
67
+ end
68
+
69
+ it 'returns found: true with updated construction' do
70
+ created = runner.create_grammar_construction(**construction_params)
71
+ result = runner.use_grammar_construction(construction_id: created[:id])
72
+ expect(result[:found]).to be true
73
+ expect(result[:construction][:usage_count]).to eq(1)
74
+ end
75
+
76
+ it 'increases activation on use' do
77
+ created = runner.create_grammar_construction(**construction_params)
78
+ result = runner.use_grammar_construction(construction_id: created[:id])
79
+ expect(result[:construction][:activation]).to be > created[:activation]
80
+ end
81
+ end
82
+
83
+ describe '#construals_for_scene_report' do
84
+ it 'returns empty construals for unknown scene' do
85
+ result = runner.construals_for_scene_report(scene: 'unknown')
86
+ expect(result[:count]).to eq(0)
87
+ expect(result[:construals]).to eq([])
88
+ end
89
+
90
+ it 'returns matching construals' do
91
+ runner.create_grammar_construal(scene: 'cat on mat', perspective: 'p', figure: 'cat', ground: 'mat')
92
+ result = runner.construals_for_scene_report(scene: 'cat on mat')
93
+ expect(result[:count]).to eq(1)
94
+ expect(result[:construals].first[:figure]).to eq('cat')
95
+ end
96
+
97
+ it 'includes the scene in the result' do
98
+ result = runner.construals_for_scene_report(scene: 'test scene')
99
+ expect(result[:scene]).to eq('test scene')
100
+ end
101
+ end
102
+
103
+ describe '#entrenched_constructions_report' do
104
+ it 'returns empty when none entrenched' do
105
+ runner.create_grammar_construction(**construction_params)
106
+ result = runner.entrenched_constructions_report
107
+ expect(result[:count]).to eq(0)
108
+ end
109
+
110
+ it 'returns entrenched constructions after enough uses' do
111
+ created = runner.create_grammar_construction(**construction_params)
112
+ 5.times { runner.use_grammar_construction(construction_id: created[:id]) }
113
+ result = runner.entrenched_constructions_report
114
+ expect(result[:count]).to eq(1)
115
+ expect(result[:constructions].first[:entrenched]).to be true
116
+ end
117
+ end
118
+
119
+ describe '#constructions_by_domain_report' do
120
+ it 'returns constructions for the given domain' do
121
+ runner.create_grammar_construction(**construction_params) # domain: 'motion'
122
+ runner.create_grammar_construction(form: 'the', meaning: 'def', expression_type: :nominal, domain: 'grammar')
123
+ result = runner.constructions_by_domain_report(domain: 'motion')
124
+ expect(result[:domain]).to eq('motion')
125
+ expect(result[:count]).to eq(1)
126
+ end
127
+ end
128
+
129
+ describe '#most_used_constructions' do
130
+ it 'returns constructions sorted by usage_count' do
131
+ c1 = runner.create_grammar_construction(**construction_params)
132
+ c2 = runner.create_grammar_construction(form: 'the', meaning: 'def', expression_type: :nominal, domain: 'grammar')
133
+ 3.times { runner.use_grammar_construction(construction_id: c1[:id]) }
134
+ runner.use_grammar_construction(construction_id: c2[:id])
135
+ result = runner.most_used_constructions(limit: 2)
136
+ expect(result[:constructions].first[:form]).to eq('she runs')
137
+ end
138
+
139
+ it 'defaults limit to 5' do
140
+ result = runner.most_used_constructions
141
+ expect(result[:limit]).to eq(5)
142
+ end
143
+ end
144
+
145
+ describe '#update_cognitive_grammar' do
146
+ it 'returns stats with pruned count' do
147
+ runner.create_grammar_construction(**construction_params)
148
+ result = runner.update_cognitive_grammar
149
+ expect(result).to include(:pruned, :stats)
150
+ expect(result[:stats]).to include(:constructions_count, :construals_count)
151
+ end
152
+
153
+ it 'removes constructions whose activation decays to <= 0.05' do
154
+ # create with very low activation that will decay below threshold after one pass
155
+ c = runner.create_grammar_construction(
156
+ form: 'x', meaning: 'y', expression_type: :nominal, domain: 'd'
157
+ )
158
+ # Manually decay the underlying construction to near-zero
159
+ engine = runner.send(:engine)
160
+ const = engine.instance_variable_get(:@constructions)[c[:id]]
161
+ const.instance_variable_set(:@activation, 0.05)
162
+ result = runner.update_cognitive_grammar
163
+ expect(result[:pruned]).to be >= 1
164
+ end
165
+ end
166
+
167
+ describe '#cognitive_grammar_stats' do
168
+ it 'returns hash with counts' do
169
+ runner.create_grammar_construction(**construction_params)
170
+ runner.create_grammar_construal(scene: 's', perspective: 'p', figure: 'f', ground: 'g')
171
+ stats = runner.cognitive_grammar_stats
172
+ expect(stats[:constructions_count]).to eq(1)
173
+ expect(stats[:construals_count]).to eq(1)
174
+ end
175
+ end
176
+
177
+ describe 'activation decay after update_cognitive_grammar' do
178
+ it 'reduces activation of all constructions' do
179
+ created = runner.create_grammar_construction(**construction_params)
180
+ original_activation = created[:activation]
181
+ runner.update_cognitive_grammar
182
+ runner.cognitive_grammar_stats
183
+ # activation should have decreased (decay was applied)
184
+ engine = runner.send(:engine)
185
+ const = engine.instance_variable_get(:@constructions)[created[:id]]
186
+ expect(const.activation).to be < original_activation
187
+ end
188
+ end
189
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Language::InnerSpeech::Client do
4
+ subject(:client) { described_class.new }
5
+
6
+ it 'includes the InnerSpeech runner' do
7
+ expect(client).to respond_to(:inner_speak)
8
+ end
9
+
10
+ it 'accepts an injected voice' do
11
+ voice = Legion::Extensions::Agentic::Language::InnerSpeech::Helpers::InnerVoice.new
12
+ c = described_class.new(voice: voice)
13
+ expect(c.inner_speak(content: 'test')[:success]).to be true
14
+ end
15
+
16
+ it 'supports full conversation flow' do
17
+ client.inner_speak(content: 'hmm, what should I do?', mode: :questioning)
18
+ client.inner_plan(content: 'first, check the data')
19
+ client.inner_speak(content: 'that looks right', mode: :affirming)
20
+
21
+ stats = client.inner_speech_stats
22
+ expect(stats[:total_utterances]).to eq(3)
23
+
24
+ narrative = client.inner_narrative
25
+ expect(narrative[:narrative]).to include('check the data')
26
+ end
27
+
28
+ it 'supports debate and voice switching' do
29
+ client.switch_inner_voice(voice_type: :curious)
30
+ client.inner_debate(
31
+ content_a: 'this approach is fast',
32
+ content_b: 'but this approach is safe',
33
+ topic: :architecture
34
+ )
35
+ stats = client.inner_speech_stats
36
+ expect(stats[:active_voice]).to eq(:curious)
37
+ expect(stats[:total_utterances]).to eq(2)
38
+ end
39
+ end