lex-agentic-learning 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 (192) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +12 -0
  3. data/Gemfile +5 -0
  4. data/LICENSE +21 -0
  5. data/README.md +13 -0
  6. data/lex-agentic-learning.gemspec +30 -0
  7. data/lib/legion/extensions/agentic/learning/anchoring/client.rb +26 -0
  8. data/lib/legion/extensions/agentic/learning/anchoring/helpers/anchor.rb +65 -0
  9. data/lib/legion/extensions/agentic/learning/anchoring/helpers/anchor_store.rb +132 -0
  10. data/lib/legion/extensions/agentic/learning/anchoring/helpers/constants.rb +31 -0
  11. data/lib/legion/extensions/agentic/learning/anchoring/runners/anchoring.rb +100 -0
  12. data/lib/legion/extensions/agentic/learning/anchoring/version.rb +13 -0
  13. data/lib/legion/extensions/agentic/learning/anchoring.rb +19 -0
  14. data/lib/legion/extensions/agentic/learning/catalyst/client.rb +15 -0
  15. data/lib/legion/extensions/agentic/learning/catalyst/helpers/catalyst.rb +87 -0
  16. data/lib/legion/extensions/agentic/learning/catalyst/helpers/catalyst_engine.rb +153 -0
  17. data/lib/legion/extensions/agentic/learning/catalyst/helpers/constants.rb +55 -0
  18. data/lib/legion/extensions/agentic/learning/catalyst/helpers/reaction.rb +87 -0
  19. data/lib/legion/extensions/agentic/learning/catalyst/runners/cognitive_catalyst.rb +103 -0
  20. data/lib/legion/extensions/agentic/learning/catalyst/version.rb +13 -0
  21. data/lib/legion/extensions/agentic/learning/catalyst.rb +22 -0
  22. data/lib/legion/extensions/agentic/learning/chrysalis/client.rb +22 -0
  23. data/lib/legion/extensions/agentic/learning/chrysalis/helpers/chrysalis.rb +137 -0
  24. data/lib/legion/extensions/agentic/learning/chrysalis/helpers/cocoon.rb +89 -0
  25. data/lib/legion/extensions/agentic/learning/chrysalis/helpers/constants.rb +49 -0
  26. data/lib/legion/extensions/agentic/learning/chrysalis/helpers/metamorphosis_engine.rb +157 -0
  27. data/lib/legion/extensions/agentic/learning/chrysalis/runners/cognitive_chrysalis.rb +129 -0
  28. data/lib/legion/extensions/agentic/learning/chrysalis/version.rb +13 -0
  29. data/lib/legion/extensions/agentic/learning/chrysalis.rb +21 -0
  30. data/lib/legion/extensions/agentic/learning/curiosity/client.rb +28 -0
  31. data/lib/legion/extensions/agentic/learning/curiosity/helpers/constants.rb +30 -0
  32. data/lib/legion/extensions/agentic/learning/curiosity/helpers/gap_detector.rb +167 -0
  33. data/lib/legion/extensions/agentic/learning/curiosity/helpers/wonder.rb +73 -0
  34. data/lib/legion/extensions/agentic/learning/curiosity/helpers/wonder_store.rb +149 -0
  35. data/lib/legion/extensions/agentic/learning/curiosity/runners/curiosity.rb +163 -0
  36. data/lib/legion/extensions/agentic/learning/curiosity/version.rb +13 -0
  37. data/lib/legion/extensions/agentic/learning/curiosity.rb +21 -0
  38. data/lib/legion/extensions/agentic/learning/epistemic_curiosity/client.rb +28 -0
  39. data/lib/legion/extensions/agentic/learning/epistemic_curiosity/helpers/constants.rb +31 -0
  40. data/lib/legion/extensions/agentic/learning/epistemic_curiosity/helpers/curiosity_engine.rb +122 -0
  41. data/lib/legion/extensions/agentic/learning/epistemic_curiosity/helpers/knowledge_gap.rb +70 -0
  42. data/lib/legion/extensions/agentic/learning/epistemic_curiosity/runners/epistemic_curiosity.rb +106 -0
  43. data/lib/legion/extensions/agentic/learning/epistemic_curiosity/version.rb +13 -0
  44. data/lib/legion/extensions/agentic/learning/epistemic_curiosity.rb +19 -0
  45. data/lib/legion/extensions/agentic/learning/fermentation/client.rb +19 -0
  46. data/lib/legion/extensions/agentic/learning/fermentation/helpers/batch.rb +75 -0
  47. data/lib/legion/extensions/agentic/learning/fermentation/helpers/constants.rb +78 -0
  48. data/lib/legion/extensions/agentic/learning/fermentation/helpers/fermentation_engine.rb +147 -0
  49. data/lib/legion/extensions/agentic/learning/fermentation/helpers/substrate.rb +108 -0
  50. data/lib/legion/extensions/agentic/learning/fermentation/runners/cognitive_fermentation.rb +60 -0
  51. data/lib/legion/extensions/agentic/learning/fermentation/version.rb +13 -0
  52. data/lib/legion/extensions/agentic/learning/fermentation.rb +22 -0
  53. data/lib/legion/extensions/agentic/learning/habit/client.rb +26 -0
  54. data/lib/legion/extensions/agentic/learning/habit/helpers/action_sequence.rb +120 -0
  55. data/lib/legion/extensions/agentic/learning/habit/helpers/constants.rb +44 -0
  56. data/lib/legion/extensions/agentic/learning/habit/helpers/habit_store.rb +148 -0
  57. data/lib/legion/extensions/agentic/learning/habit/runners/habit.rb +86 -0
  58. data/lib/legion/extensions/agentic/learning/habit/version.rb +13 -0
  59. data/lib/legion/extensions/agentic/learning/habit.rb +19 -0
  60. data/lib/legion/extensions/agentic/learning/hebbian/actors/decay.rb +45 -0
  61. data/lib/legion/extensions/agentic/learning/hebbian/client.rb +29 -0
  62. data/lib/legion/extensions/agentic/learning/hebbian/helpers/assembly.rb +82 -0
  63. data/lib/legion/extensions/agentic/learning/hebbian/helpers/assembly_network.rb +190 -0
  64. data/lib/legion/extensions/agentic/learning/hebbian/helpers/constants.rb +50 -0
  65. data/lib/legion/extensions/agentic/learning/hebbian/helpers/unit.rb +94 -0
  66. data/lib/legion/extensions/agentic/learning/hebbian/runners/hebbian_assembly.rb +94 -0
  67. data/lib/legion/extensions/agentic/learning/hebbian/version.rb +13 -0
  68. data/lib/legion/extensions/agentic/learning/hebbian.rb +20 -0
  69. data/lib/legion/extensions/agentic/learning/learning_rate/client.rb +25 -0
  70. data/lib/legion/extensions/agentic/learning/learning_rate/helpers/constants.rb +35 -0
  71. data/lib/legion/extensions/agentic/learning/learning_rate/helpers/rate_model.rb +133 -0
  72. data/lib/legion/extensions/agentic/learning/learning_rate/runners/learning_rate.rb +85 -0
  73. data/lib/legion/extensions/agentic/learning/learning_rate/version.rb +13 -0
  74. data/lib/legion/extensions/agentic/learning/learning_rate.rb +18 -0
  75. data/lib/legion/extensions/agentic/learning/meta_learning/client.rb +27 -0
  76. data/lib/legion/extensions/agentic/learning/meta_learning/helpers/constants.rb +46 -0
  77. data/lib/legion/extensions/agentic/learning/meta_learning/helpers/learning_domain.rb +85 -0
  78. data/lib/legion/extensions/agentic/learning/meta_learning/helpers/meta_learning_engine.rb +202 -0
  79. data/lib/legion/extensions/agentic/learning/meta_learning/helpers/strategy.rb +62 -0
  80. data/lib/legion/extensions/agentic/learning/meta_learning/runners/meta_learning.rb +118 -0
  81. data/lib/legion/extensions/agentic/learning/meta_learning/version.rb +13 -0
  82. data/lib/legion/extensions/agentic/learning/meta_learning.rb +20 -0
  83. data/lib/legion/extensions/agentic/learning/plasticity/client.rb +15 -0
  84. data/lib/legion/extensions/agentic/learning/plasticity/helpers/constants.rb +45 -0
  85. data/lib/legion/extensions/agentic/learning/plasticity/helpers/neural_pathway.rb +85 -0
  86. data/lib/legion/extensions/agentic/learning/plasticity/helpers/plasticity_engine.rb +130 -0
  87. data/lib/legion/extensions/agentic/learning/plasticity/runners/cognitive_plasticity.rb +85 -0
  88. data/lib/legion/extensions/agentic/learning/plasticity/version.rb +13 -0
  89. data/lib/legion/extensions/agentic/learning/plasticity.rb +19 -0
  90. data/lib/legion/extensions/agentic/learning/preference_learning/actors/decay.rb +45 -0
  91. data/lib/legion/extensions/agentic/learning/preference_learning/client.rb +28 -0
  92. data/lib/legion/extensions/agentic/learning/preference_learning/helpers/constants.rb +35 -0
  93. data/lib/legion/extensions/agentic/learning/preference_learning/helpers/option.rb +78 -0
  94. data/lib/legion/extensions/agentic/learning/preference_learning/helpers/preference_engine.rb +121 -0
  95. data/lib/legion/extensions/agentic/learning/preference_learning/runners/preference_learning.rb +84 -0
  96. data/lib/legion/extensions/agentic/learning/preference_learning/version.rb +13 -0
  97. data/lib/legion/extensions/agentic/learning/preference_learning.rb +19 -0
  98. data/lib/legion/extensions/agentic/learning/procedural/client.rb +19 -0
  99. data/lib/legion/extensions/agentic/learning/procedural/helpers/constants.rb +46 -0
  100. data/lib/legion/extensions/agentic/learning/procedural/helpers/learning_engine.rb +160 -0
  101. data/lib/legion/extensions/agentic/learning/procedural/helpers/production.rb +66 -0
  102. data/lib/legion/extensions/agentic/learning/procedural/helpers/skill.rb +101 -0
  103. data/lib/legion/extensions/agentic/learning/procedural/runners/procedural_learning.rb +96 -0
  104. data/lib/legion/extensions/agentic/learning/procedural/version.rb +13 -0
  105. data/lib/legion/extensions/agentic/learning/procedural.rb +20 -0
  106. data/lib/legion/extensions/agentic/learning/scaffolding/client.rb +26 -0
  107. data/lib/legion/extensions/agentic/learning/scaffolding/helpers/constants.rb +42 -0
  108. data/lib/legion/extensions/agentic/learning/scaffolding/helpers/scaffold.rb +136 -0
  109. data/lib/legion/extensions/agentic/learning/scaffolding/helpers/scaffolding_engine.rb +112 -0
  110. data/lib/legion/extensions/agentic/learning/scaffolding/runners/cognitive_scaffolding.rb +107 -0
  111. data/lib/legion/extensions/agentic/learning/scaffolding/version.rb +13 -0
  112. data/lib/legion/extensions/agentic/learning/scaffolding.rb +19 -0
  113. data/lib/legion/extensions/agentic/learning/version.rb +11 -0
  114. data/lib/legion/extensions/agentic/learning.rb +31 -0
  115. data/spec/legion/extensions/agentic/learning/anchoring/client_spec.rb +32 -0
  116. data/spec/legion/extensions/agentic/learning/anchoring/helpers/anchor_spec.rb +130 -0
  117. data/spec/legion/extensions/agentic/learning/anchoring/helpers/anchor_store_spec.rb +201 -0
  118. data/spec/legion/extensions/agentic/learning/anchoring/helpers/constants_spec.rb +63 -0
  119. data/spec/legion/extensions/agentic/learning/anchoring/runners/anchoring_spec.rb +199 -0
  120. data/spec/legion/extensions/agentic/learning/catalyst/client_spec.rb +58 -0
  121. data/spec/legion/extensions/agentic/learning/catalyst/cognitive_catalyst_spec.rb +49 -0
  122. data/spec/legion/extensions/agentic/learning/catalyst/helpers/catalyst_engine_spec.rb +263 -0
  123. data/spec/legion/extensions/agentic/learning/catalyst/helpers/catalyst_spec.rb +214 -0
  124. data/spec/legion/extensions/agentic/learning/catalyst/helpers/reaction_spec.rb +223 -0
  125. data/spec/legion/extensions/agentic/learning/catalyst/runners/cognitive_catalyst_spec.rb +217 -0
  126. data/spec/legion/extensions/agentic/learning/chrysalis/client_spec.rb +83 -0
  127. data/spec/legion/extensions/agentic/learning/chrysalis/cognitive_chrysalis_spec.rb +15 -0
  128. data/spec/legion/extensions/agentic/learning/chrysalis/helpers/chrysalis_engine_spec.rb +57 -0
  129. data/spec/legion/extensions/agentic/learning/chrysalis/helpers/chrysalis_spec.rb +305 -0
  130. data/spec/legion/extensions/agentic/learning/chrysalis/helpers/cocoon_spec.rb +206 -0
  131. data/spec/legion/extensions/agentic/learning/chrysalis/helpers/constants_spec.rb +109 -0
  132. data/spec/legion/extensions/agentic/learning/chrysalis/helpers/metamorphic_cycle_spec.rb +76 -0
  133. data/spec/legion/extensions/agentic/learning/chrysalis/helpers/metamorphosis_engine_spec.rb +247 -0
  134. data/spec/legion/extensions/agentic/learning/chrysalis/helpers/transformation_phase_spec.rb +98 -0
  135. data/spec/legion/extensions/agentic/learning/chrysalis/runners/cognitive_chrysalis_spec.rb +180 -0
  136. data/spec/legion/extensions/agentic/learning/chrysalis/runners/reporting_spec.rb +81 -0
  137. data/spec/legion/extensions/agentic/learning/chrysalis/runners/transformation_spec.rb +74 -0
  138. data/spec/legion/extensions/agentic/learning/curiosity/client_spec.rb +27 -0
  139. data/spec/legion/extensions/agentic/learning/curiosity/helpers/gap_detector_spec.rb +118 -0
  140. data/spec/legion/extensions/agentic/learning/curiosity/helpers/wonder_spec.rb +130 -0
  141. data/spec/legion/extensions/agentic/learning/curiosity/helpers/wonder_store_spec.rb +136 -0
  142. data/spec/legion/extensions/agentic/learning/curiosity/runners/curiosity_spec.rb +159 -0
  143. data/spec/legion/extensions/agentic/learning/epistemic_curiosity/client_spec.rb +47 -0
  144. data/spec/legion/extensions/agentic/learning/epistemic_curiosity/helpers/constants_spec.rb +45 -0
  145. data/spec/legion/extensions/agentic/learning/epistemic_curiosity/helpers/curiosity_engine_spec.rb +229 -0
  146. data/spec/legion/extensions/agentic/learning/epistemic_curiosity/helpers/knowledge_gap_spec.rb +188 -0
  147. data/spec/legion/extensions/agentic/learning/epistemic_curiosity/runners/epistemic_curiosity_spec.rb +175 -0
  148. data/spec/legion/extensions/agentic/learning/fermentation/client_spec.rb +36 -0
  149. data/spec/legion/extensions/agentic/learning/fermentation/helpers/batch_spec.rb +72 -0
  150. data/spec/legion/extensions/agentic/learning/fermentation/helpers/fermentation_engine_spec.rb +138 -0
  151. data/spec/legion/extensions/agentic/learning/fermentation/helpers/substrate_spec.rb +146 -0
  152. data/spec/legion/extensions/agentic/learning/habit/client_spec.rb +50 -0
  153. data/spec/legion/extensions/agentic/learning/habit/helpers/action_sequence_spec.rb +276 -0
  154. data/spec/legion/extensions/agentic/learning/habit/helpers/constants_spec.rb +115 -0
  155. data/spec/legion/extensions/agentic/learning/habit/helpers/habit_store_spec.rb +274 -0
  156. data/spec/legion/extensions/agentic/learning/habit/runners/habit_spec.rb +228 -0
  157. data/spec/legion/extensions/agentic/learning/hebbian/client_spec.rb +38 -0
  158. data/spec/legion/extensions/agentic/learning/hebbian/helpers/assembly_network_spec.rb +142 -0
  159. data/spec/legion/extensions/agentic/learning/hebbian/helpers/assembly_spec.rb +89 -0
  160. data/spec/legion/extensions/agentic/learning/hebbian/helpers/unit_spec.rb +119 -0
  161. data/spec/legion/extensions/agentic/learning/hebbian/runners/hebbian_assembly_spec.rb +109 -0
  162. data/spec/legion/extensions/agentic/learning/learning_rate/client_spec.rb +51 -0
  163. data/spec/legion/extensions/agentic/learning/learning_rate/helpers/constants_spec.rb +29 -0
  164. data/spec/legion/extensions/agentic/learning/learning_rate/helpers/rate_model_spec.rb +151 -0
  165. data/spec/legion/extensions/agentic/learning/learning_rate/runners/learning_rate_spec.rb +92 -0
  166. data/spec/legion/extensions/agentic/learning/meta_learning/client_spec.rb +27 -0
  167. data/spec/legion/extensions/agentic/learning/meta_learning/helpers/constants_spec.rb +43 -0
  168. data/spec/legion/extensions/agentic/learning/meta_learning/helpers/learning_domain_spec.rb +146 -0
  169. data/spec/legion/extensions/agentic/learning/meta_learning/helpers/meta_learning_engine_spec.rb +309 -0
  170. data/spec/legion/extensions/agentic/learning/meta_learning/helpers/strategy_spec.rb +82 -0
  171. data/spec/legion/extensions/agentic/learning/meta_learning/runners/meta_learning_spec.rb +185 -0
  172. data/spec/legion/extensions/agentic/learning/plasticity/helpers/constants_spec.rb +54 -0
  173. data/spec/legion/extensions/agentic/learning/plasticity/helpers/neural_pathway_spec.rb +136 -0
  174. data/spec/legion/extensions/agentic/learning/plasticity/helpers/plasticity_engine_spec.rb +157 -0
  175. data/spec/legion/extensions/agentic/learning/plasticity/runners/cognitive_plasticity_spec.rb +83 -0
  176. data/spec/legion/extensions/agentic/learning/preference_learning/client_spec.rb +17 -0
  177. data/spec/legion/extensions/agentic/learning/preference_learning/helpers/constants_spec.rb +67 -0
  178. data/spec/legion/extensions/agentic/learning/preference_learning/helpers/option_spec.rb +104 -0
  179. data/spec/legion/extensions/agentic/learning/preference_learning/helpers/preference_engine_spec.rb +151 -0
  180. data/spec/legion/extensions/agentic/learning/preference_learning/runners/preference_learning_spec.rb +86 -0
  181. data/spec/legion/extensions/agentic/learning/procedural/client_spec.rb +22 -0
  182. data/spec/legion/extensions/agentic/learning/procedural/helpers/learning_engine_spec.rb +135 -0
  183. data/spec/legion/extensions/agentic/learning/procedural/helpers/production_spec.rb +66 -0
  184. data/spec/legion/extensions/agentic/learning/procedural/helpers/skill_spec.rb +102 -0
  185. data/spec/legion/extensions/agentic/learning/procedural/runners/procedural_learning_spec.rb +94 -0
  186. data/spec/legion/extensions/agentic/learning/scaffolding/client_spec.rb +20 -0
  187. data/spec/legion/extensions/agentic/learning/scaffolding/helpers/constants_spec.rb +36 -0
  188. data/spec/legion/extensions/agentic/learning/scaffolding/helpers/scaffold_spec.rb +187 -0
  189. data/spec/legion/extensions/agentic/learning/scaffolding/helpers/scaffolding_engine_spec.rb +159 -0
  190. data/spec/legion/extensions/agentic/learning/scaffolding/runners/cognitive_scaffolding_spec.rb +163 -0
  191. data/spec/spec_helper.rb +46 -0
  192. metadata +277 -0
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Learning::Habit::Helpers::Constants do
4
+ describe 'MATURITY_STAGES' do
5
+ it 'has exactly 5 stages' do
6
+ expect(described_class::MATURITY_STAGES.size).to eq(5)
7
+ end
8
+
9
+ it 'includes all expected stages' do
10
+ expect(described_class::MATURITY_STAGES).to include(:novel)
11
+ expect(described_class::MATURITY_STAGES).to include(:learning)
12
+ expect(described_class::MATURITY_STAGES).to include(:practiced)
13
+ expect(described_class::MATURITY_STAGES).to include(:habitual)
14
+ expect(described_class::MATURITY_STAGES).to include(:automatic)
15
+ end
16
+
17
+ it 'is ordered from least to most mature' do
18
+ stages = described_class::MATURITY_STAGES
19
+ expect(stages.first).to eq(:novel)
20
+ expect(stages.last).to eq(:automatic)
21
+ end
22
+ end
23
+
24
+ describe 'MATURITY_THRESHOLDS' do
25
+ it 'has a threshold for each maturity stage' do
26
+ described_class::MATURITY_STAGES.each do |stage|
27
+ expect(described_class::MATURITY_THRESHOLDS).to have_key(stage)
28
+ end
29
+ end
30
+
31
+ it 'thresholds increase with maturity' do
32
+ stages = described_class::MATURITY_STAGES
33
+ thresholds = stages.map { |s| described_class::MATURITY_THRESHOLDS[s] }
34
+ expect(thresholds).to eq(thresholds.sort)
35
+ end
36
+
37
+ it 'novel starts at 0' do
38
+ expect(described_class::MATURITY_THRESHOLDS[:novel]).to eq(0)
39
+ end
40
+ end
41
+
42
+ describe 'COGNITIVE_COST' do
43
+ it 'has a cost for each maturity stage' do
44
+ described_class::MATURITY_STAGES.each do |stage|
45
+ expect(described_class::COGNITIVE_COST).to have_key(stage)
46
+ end
47
+ end
48
+
49
+ it 'cognitive cost decreases with maturity' do
50
+ stages = described_class::MATURITY_STAGES
51
+ costs = stages.map { |s| described_class::COGNITIVE_COST[s] }
52
+ expect(costs).to eq(costs.sort.reverse)
53
+ end
54
+
55
+ it 'novel has maximum cognitive cost of 1.0' do
56
+ expect(described_class::COGNITIVE_COST[:novel]).to eq(1.0)
57
+ end
58
+
59
+ it 'automatic has minimum cognitive cost' do
60
+ expect(described_class::COGNITIVE_COST[:automatic]).to be < described_class::COGNITIVE_COST[:habitual]
61
+ end
62
+ end
63
+
64
+ describe 'numeric constants' do
65
+ it 'REINFORCEMENT_RATE is positive' do
66
+ expect(described_class::REINFORCEMENT_RATE).to be > 0
67
+ end
68
+
69
+ it 'DECAY_RATE is positive' do
70
+ expect(described_class::DECAY_RATE).to be > 0
71
+ end
72
+
73
+ it 'MIN_SEQUENCE_LENGTH is at least 2' do
74
+ expect(described_class::MIN_SEQUENCE_LENGTH).to be >= 2
75
+ end
76
+
77
+ it 'MAX_SEQUENCE_LENGTH is greater than MIN_SEQUENCE_LENGTH' do
78
+ expect(described_class::MAX_SEQUENCE_LENGTH).to be > described_class::MIN_SEQUENCE_LENGTH
79
+ end
80
+
81
+ it 'MAX_HABITS is a positive integer' do
82
+ expect(described_class::MAX_HABITS).to be > 0
83
+ end
84
+
85
+ it 'SIMILARITY_THRESHOLD is between 0 and 1' do
86
+ expect(described_class::SIMILARITY_THRESHOLD).to be_between(0.0, 1.0)
87
+ end
88
+
89
+ it 'CHUNKING_THRESHOLD is a positive integer' do
90
+ expect(described_class::CHUNKING_THRESHOLD).to be > 0
91
+ end
92
+
93
+ it 'HABIT_STRENGTH_FLOOR is positive' do
94
+ expect(described_class::HABIT_STRENGTH_FLOOR).to be > 0
95
+ end
96
+ end
97
+
98
+ describe 'CONTEXT_DIMENSIONS' do
99
+ it 'includes :domain' do
100
+ expect(described_class::CONTEXT_DIMENSIONS).to include(:domain)
101
+ end
102
+
103
+ it 'includes :mood' do
104
+ expect(described_class::CONTEXT_DIMENSIONS).to include(:mood)
105
+ end
106
+
107
+ it 'includes :time_of_day' do
108
+ expect(described_class::CONTEXT_DIMENSIONS).to include(:time_of_day)
109
+ end
110
+
111
+ it 'includes :trigger' do
112
+ expect(described_class::CONTEXT_DIMENSIONS).to include(:trigger)
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,274 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Learning::Habit::Helpers::HabitStore do
4
+ subject(:store) { described_class.new }
5
+
6
+ let(:chunking_threshold) { Legion::Extensions::Agentic::Learning::Habit::Helpers::Constants::CHUNKING_THRESHOLD }
7
+
8
+ describe '#initialize' do
9
+ it 'starts with empty habits' do
10
+ expect(store.habits).to be_empty
11
+ end
12
+
13
+ it 'starts with empty action buffer' do
14
+ expect(store.action_buffer).to be_empty
15
+ end
16
+ end
17
+
18
+ describe '#record_action' do
19
+ it 'adds an entry to the action buffer' do
20
+ store.record_action(:fetch)
21
+ expect(store.action_buffer.size).to eq(1)
22
+ end
23
+
24
+ it 'stores the action as a symbol' do
25
+ store.record_action('fetch')
26
+ expect(store.action_buffer.last[:action]).to eq(:fetch)
27
+ end
28
+
29
+ it 'stores associated context' do
30
+ store.record_action(:fetch, context: { domain: :api })
31
+ expect(store.action_buffer.last[:context]).to eq({ domain: :api })
32
+ end
33
+
34
+ it 'evicts oldest entry when buffer exceeds MAX_BUFFER_SIZE' do
35
+ 51.times { |i| store.record_action(:"action_#{i}") }
36
+ expect(store.action_buffer.size).to eq(50)
37
+ end
38
+ end
39
+
40
+ describe '#detect_patterns' do
41
+ context 'when a subsequence repeats enough times' do
42
+ it 'detects and stores a new habit' do
43
+ # Fill buffer with CHUNKING_THRESHOLD repetitions of the same 2-action sequence
44
+ chunking_threshold.times do
45
+ store.record_action(:fetch)
46
+ store.record_action(:parse)
47
+ end
48
+ new_habits = store.detect_patterns
49
+ expect(new_habits).not_to be_empty
50
+ end
51
+
52
+ it 'returns the detected habit objects' do
53
+ chunking_threshold.times do
54
+ store.record_action(:a)
55
+ store.record_action(:b)
56
+ end
57
+ new_habits = store.detect_patterns
58
+ expect(new_habits.first).to be_a(Legion::Extensions::Agentic::Learning::Habit::Helpers::ActionSequence)
59
+ end
60
+ end
61
+
62
+ context 'when a subsequence does not repeat enough times' do
63
+ it 'returns empty array' do
64
+ store.record_action(:fetch)
65
+ store.record_action(:parse)
66
+ new_habits = store.detect_patterns
67
+ expect(new_habits).to be_empty
68
+ end
69
+ end
70
+
71
+ context 'when an existing habit matches' do
72
+ it 'reinforces the existing habit rather than creating a new one' do
73
+ # Seed initial habit
74
+ chunking_threshold.times do
75
+ store.record_action(:x)
76
+ store.record_action(:y)
77
+ end
78
+ store.detect_patterns
79
+ expect(store.habits.size).to be >= 1
80
+
81
+ # The same sequence already exists — find it and record its execution count
82
+ existing = store.habits.values.find { |h| h.actions == %i[x y] }
83
+ expect(existing).not_to be_nil
84
+ before_count = existing.execution_count
85
+
86
+ # Clear the buffer and re-seed to isolate the existing-habit path
87
+ store.action_buffer.clear
88
+ chunking_threshold.times do
89
+ store.record_action(:x)
90
+ store.record_action(:y)
91
+ end
92
+ store.detect_patterns
93
+
94
+ # Existing habit should have been reinforced
95
+ expect(existing.execution_count).to be > before_count
96
+ end
97
+ end
98
+ end
99
+
100
+ describe '#find_matching' do
101
+ before do
102
+ chunking_threshold.times do
103
+ store.record_action(:fetch, context: { domain: :api })
104
+ store.record_action(:parse, context: { domain: :api })
105
+ end
106
+ store.detect_patterns
107
+ end
108
+
109
+ it 'returns habits matching the given context' do
110
+ matches = store.find_matching(context: { domain: :api })
111
+ expect(matches).not_to be_empty
112
+ end
113
+
114
+ it 'returns habits sorted by strength descending' do
115
+ matches = store.find_matching(context: { domain: :api })
116
+ strengths = matches.map(&:strength)
117
+ expect(strengths).to eq(strengths.sort.reverse)
118
+ end
119
+
120
+ it 'returns empty when no context match' do
121
+ matches = store.find_matching(context: { domain: :db })
122
+ expect(matches).to be_empty
123
+ end
124
+ end
125
+
126
+ describe '#get' do
127
+ it 'returns nil for unknown id' do
128
+ expect(store.get('unknown-id')).to be_nil
129
+ end
130
+
131
+ it 'returns the habit for a known id' do
132
+ chunking_threshold.times do
133
+ store.record_action(:m)
134
+ store.record_action(:n)
135
+ end
136
+ store.detect_patterns
137
+ id = store.habits.keys.first
138
+ expect(store.get(id)).to be_a(Legion::Extensions::Agentic::Learning::Habit::Helpers::ActionSequence)
139
+ end
140
+ end
141
+
142
+ describe '#reinforce' do
143
+ it 'returns nil for unknown id' do
144
+ expect(store.reinforce('unknown', success: true)).to be_nil
145
+ end
146
+
147
+ it 'records execution on the habit' do
148
+ chunking_threshold.times do
149
+ store.record_action(:p)
150
+ store.record_action(:q)
151
+ end
152
+ store.detect_patterns
153
+ id = store.habits.keys.first
154
+ before = store.get(id).execution_count
155
+ store.reinforce(id, success: true)
156
+ expect(store.get(id).execution_count).to be > before
157
+ end
158
+ end
159
+
160
+ describe '#decay_all' do
161
+ before do
162
+ chunking_threshold.times do
163
+ store.record_action(:d)
164
+ store.record_action(:e)
165
+ end
166
+ store.detect_patterns
167
+ end
168
+
169
+ it 'returns count of removed habits when habits fall below floor' do
170
+ # Force strength below floor by setting it directly
171
+ store.habits.each_value do |h|
172
+ h.instance_variable_set(:@strength, 0.05)
173
+ end
174
+ removed = store.decay_all
175
+ expect(removed).to be >= 0
176
+ end
177
+
178
+ it 'returns 0 when all habits survive decay' do
179
+ store.habits.each_value do |h|
180
+ h.instance_variable_set(:@strength, 0.9)
181
+ end
182
+ removed = store.decay_all
183
+ expect(removed).to eq(0)
184
+ end
185
+
186
+ it 'removes habits that fall below the floor' do
187
+ store.habits.each_value do |h|
188
+ h.instance_variable_set(:@strength, 0.05)
189
+ end
190
+ store.decay_all
191
+ expect(store.habits).to be_empty
192
+ end
193
+ end
194
+
195
+ describe '#merge_similar' do
196
+ it 'returns 0 when there is only one habit' do
197
+ chunking_threshold.times do
198
+ store.record_action(:a)
199
+ store.record_action(:b)
200
+ end
201
+ store.detect_patterns
202
+ expect(store.merge_similar).to eq(0)
203
+ end
204
+
205
+ it 'merges highly similar habits and reduces the count' do
206
+ # Create two nearly identical habits manually
207
+ habit_a = Legion::Extensions::Agentic::Learning::Habit::Helpers::ActionSequence.new(actions: %i[x y z])
208
+ habit_b = Legion::Extensions::Agentic::Learning::Habit::Helpers::ActionSequence.new(actions: %i[x y z])
209
+ store.habits[habit_a.id] = habit_a
210
+ store.habits[habit_b.id] = habit_b
211
+
212
+ before = store.habits.size
213
+ merged = store.merge_similar
214
+ expect(merged).to eq(1)
215
+ expect(store.habits.size).to eq(before - 1)
216
+ end
217
+ end
218
+
219
+ describe '#by_maturity' do
220
+ it 'returns habits at the specified maturity stage' do
221
+ chunking_threshold.times do
222
+ store.record_action(:f)
223
+ store.record_action(:g)
224
+ end
225
+ store.detect_patterns
226
+ novel_habits = store.by_maturity(:novel)
227
+ novel_habits.each { |h| expect(h.maturity).to eq(:novel) }
228
+ end
229
+
230
+ it 'returns empty array when no habits at stage' do
231
+ expect(store.by_maturity(:automatic)).to be_empty
232
+ end
233
+ end
234
+
235
+ describe '#stats' do
236
+ it 'returns total count of 0 when empty' do
237
+ expect(store.stats[:total]).to eq(0)
238
+ end
239
+
240
+ it 'returns correct total after detecting patterns' do
241
+ chunking_threshold.times do
242
+ store.record_action(:h)
243
+ store.record_action(:i)
244
+ end
245
+ store.detect_patterns
246
+ expect(store.stats[:total]).to be >= 1
247
+ end
248
+
249
+ it 'includes per_maturity breakdown' do
250
+ expect(store.stats[:per_maturity]).to be_a(Hash)
251
+ end
252
+
253
+ it 'includes avg_strength' do
254
+ expect(store.stats[:avg_strength]).to be_a(Numeric)
255
+ end
256
+
257
+ it 'returns nil oldest when empty' do
258
+ expect(store.stats[:oldest]).to be_nil
259
+ end
260
+ end
261
+
262
+ describe '#evict_if_needed' do
263
+ it 'removes weakest habit when over MAX_HABITS' do
264
+ # Insert MAX_HABITS + 1 habits directly
265
+ (Legion::Extensions::Agentic::Learning::Habit::Helpers::Constants::MAX_HABITS + 1).times do |i|
266
+ h = Legion::Extensions::Agentic::Learning::Habit::Helpers::ActionSequence.new(actions: [:"a#{i}", :"b#{i}"])
267
+ h.instance_variable_set(:@strength, (i + 1).to_f / 1000)
268
+ store.habits[h.id] = h
269
+ end
270
+ store.evict_if_needed
271
+ expect(store.habits.size).to eq(Legion::Extensions::Agentic::Learning::Habit::Helpers::Constants::MAX_HABITS)
272
+ end
273
+ end
274
+ end
@@ -0,0 +1,228 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'legion/extensions/agentic/learning/habit/client'
4
+
5
+ RSpec.describe Legion::Extensions::Agentic::Learning::Habit::Runners::Habit do
6
+ let(:habit_store) { Legion::Extensions::Agentic::Learning::Habit::Helpers::HabitStore.new }
7
+ let(:client) { Legion::Extensions::Agentic::Learning::Habit::Client.new(habit_store: habit_store) }
8
+
9
+ let(:chunking_threshold) { Legion::Extensions::Agentic::Learning::Habit::Helpers::Constants::CHUNKING_THRESHOLD }
10
+
11
+ def seed_habit
12
+ chunking_threshold.times do
13
+ habit_store.record_action(:fetch)
14
+ habit_store.record_action(:parse)
15
+ end
16
+ habit_store.detect_patterns
17
+ habit_store.habits.values.first
18
+ end
19
+
20
+ describe '#observe_action' do
21
+ it 'returns recorded: true' do
22
+ result = client.observe_action(action: :fetch)
23
+ expect(result[:recorded]).to be true
24
+ end
25
+
26
+ it 'includes the action in response' do
27
+ result = client.observe_action(action: :fetch)
28
+ expect(result[:action]).to eq(:fetch)
29
+ end
30
+
31
+ it 'includes new_habits_detected count' do
32
+ result = client.observe_action(action: :fetch)
33
+ expect(result).to have_key(:new_habits_detected)
34
+ end
35
+
36
+ it 'detects new habits after sufficient repetitions' do
37
+ (chunking_threshold - 1).times do
38
+ client.observe_action(action: :x)
39
+ client.observe_action(action: :y)
40
+ end
41
+ client.observe_action(action: :x)
42
+ # The final observe adds the action; next observe_action with :y should trigger detection
43
+ client.observe_action(action: :y)
44
+ expect(habit_store.habits.size).to be >= 0
45
+ end
46
+
47
+ it 'includes habits array in response' do
48
+ result = client.observe_action(action: :fetch)
49
+ expect(result[:habits]).to be_an(Array)
50
+ end
51
+ end
52
+
53
+ describe '#suggest_habit' do
54
+ context 'when no matching habits exist' do
55
+ it 'returns suggestion: nil' do
56
+ result = client.suggest_habit(context: { domain: :api })
57
+ expect(result[:suggestion]).to be_nil
58
+ end
59
+
60
+ it 'returns reason: :no_matching_habits' do
61
+ result = client.suggest_habit(context: { domain: :api })
62
+ expect(result[:reason]).to eq(:no_matching_habits)
63
+ end
64
+ end
65
+
66
+ context 'when matching habits exist' do
67
+ before do
68
+ chunking_threshold.times do
69
+ habit_store.record_action(:fetch, context: { domain: :api })
70
+ habit_store.record_action(:parse, context: { domain: :api })
71
+ end
72
+ habit_store.detect_patterns
73
+ end
74
+
75
+ it 'returns a suggestion hash' do
76
+ result = client.suggest_habit(context: { domain: :api })
77
+ expect(result[:suggestion]).to be_a(Hash)
78
+ end
79
+
80
+ it 'returns cognitive_savings as a positive value' do
81
+ result = client.suggest_habit(context: { domain: :api })
82
+ expect(result[:cognitive_savings]).to be > 0
83
+ end
84
+
85
+ it 'includes alternatives array' do
86
+ result = client.suggest_habit(context: { domain: :api })
87
+ expect(result[:alternatives]).to be_an(Array)
88
+ end
89
+ end
90
+ end
91
+
92
+ describe '#execute_habit' do
93
+ context 'when habit not found' do
94
+ it 'returns error: :not_found' do
95
+ result = client.execute_habit(id: 'nonexistent')
96
+ expect(result[:error]).to eq(:not_found)
97
+ end
98
+ end
99
+
100
+ context 'when habit exists' do
101
+ it 'returns executed: true' do
102
+ habit = seed_habit
103
+ result = client.execute_habit(id: habit.id)
104
+ expect(result[:executed]).to be true
105
+ end
106
+
107
+ it 'returns the updated habit hash' do
108
+ habit = seed_habit
109
+ result = client.execute_habit(id: habit.id)
110
+ expect(result[:habit]).to be_a(Hash)
111
+ expect(result[:habit][:id]).to eq(habit.id)
112
+ end
113
+
114
+ it 'returns cognitive_cost' do
115
+ habit = seed_habit
116
+ result = client.execute_habit(id: habit.id)
117
+ expect(result[:cognitive_cost]).to be_a(Numeric)
118
+ end
119
+
120
+ it 'increments the execution count' do
121
+ habit = seed_habit
122
+ before = habit.execution_count
123
+ client.execute_habit(id: habit.id, success: true)
124
+ expect(habit.execution_count).to eq(before + 1)
125
+ end
126
+ end
127
+ end
128
+
129
+ describe '#decay_habits' do
130
+ it 'returns decayed: true' do
131
+ result = client.decay_habits
132
+ expect(result[:decayed]).to be true
133
+ end
134
+
135
+ it 'returns removed_count' do
136
+ result = client.decay_habits
137
+ expect(result).to have_key(:removed_count)
138
+ end
139
+
140
+ it 'removes habits that fall below floor' do
141
+ habit = seed_habit
142
+ habit.instance_variable_set(:@strength, 0.05)
143
+ client.decay_habits
144
+ expect(habit_store.get(habit.id)).to be_nil
145
+ end
146
+ end
147
+
148
+ describe '#merge_habits' do
149
+ it 'returns merged_count' do
150
+ result = client.merge_habits
151
+ expect(result).to have_key(:merged_count)
152
+ end
153
+
154
+ it 'returns 0 when no similar habits to merge' do
155
+ result = client.merge_habits
156
+ expect(result[:merged_count]).to eq(0)
157
+ end
158
+
159
+ it 'merges identical habits' do
160
+ # Manually insert two identical habits
161
+ h1 = Legion::Extensions::Agentic::Learning::Habit::Helpers::ActionSequence.new(actions: %i[p q r])
162
+ h2 = Legion::Extensions::Agentic::Learning::Habit::Helpers::ActionSequence.new(actions: %i[p q r])
163
+ habit_store.habits[h1.id] = h1
164
+ habit_store.habits[h2.id] = h2
165
+
166
+ result = client.merge_habits
167
+ expect(result[:merged_count]).to eq(1)
168
+ end
169
+ end
170
+
171
+ describe '#habit_stats' do
172
+ it 'returns a hash with total key' do
173
+ result = client.habit_stats
174
+ expect(result).to have_key(:total)
175
+ end
176
+
177
+ it 'returns per_maturity breakdown' do
178
+ result = client.habit_stats
179
+ expect(result[:per_maturity]).to be_a(Hash)
180
+ end
181
+
182
+ it 'returns avg_strength' do
183
+ result = client.habit_stats
184
+ expect(result[:avg_strength]).to be_a(Numeric)
185
+ end
186
+ end
187
+
188
+ describe '#habit_repertoire' do
189
+ context 'when no maturity filter' do
190
+ it 'returns all habits' do
191
+ seed_habit
192
+ result = client.habit_repertoire
193
+ expect(result[:habits]).to be_an(Array)
194
+ expect(result[:total]).to be >= 1
195
+ end
196
+ end
197
+
198
+ context 'with maturity filter' do
199
+ it 'returns only habits at the given maturity stage' do
200
+ seed_habit
201
+ result = client.habit_repertoire(maturity: :novel)
202
+ result[:habits].each do |h|
203
+ expect(h[:maturity]).to eq(:novel)
204
+ end
205
+ end
206
+ end
207
+
208
+ it 'respects limit parameter' do
209
+ 10.times do |i|
210
+ h = Legion::Extensions::Agentic::Learning::Habit::Helpers::ActionSequence.new(actions: [:"action#{i}", :"step#{i}"])
211
+ habit_store.habits[h.id] = h
212
+ end
213
+ result = client.habit_repertoire(limit: 3)
214
+ expect(result[:habits].size).to be <= 3
215
+ end
216
+
217
+ it 'sorts by strength descending' do
218
+ 3.times do |i|
219
+ h = Legion::Extensions::Agentic::Learning::Habit::Helpers::ActionSequence.new(actions: [:"s#{i}", :"t#{i}"])
220
+ h.instance_variable_set(:@strength, (i + 1).to_f / 10)
221
+ habit_store.habits[h.id] = h
222
+ end
223
+ result = client.habit_repertoire
224
+ strengths = result[:habits].map { |h| h[:strength] }
225
+ expect(strengths).to eq(strengths.sort.reverse)
226
+ end
227
+ end
228
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::Agentic::Learning::Hebbian::Client do
4
+ subject(:client) { described_class.new }
5
+
6
+ it 'includes Runners::HebbianAssembly' do
7
+ expect(described_class.ancestors).to include(Legion::Extensions::Agentic::Learning::Hebbian::Runners::HebbianAssembly)
8
+ end
9
+
10
+ it 'supports full Hebbian learning lifecycle' do
11
+ # Repeatedly co-activate a pattern
12
+ 10.times { client.co_activate_units(ids: %i[see dog bark]) }
13
+
14
+ # Assembly should form
15
+ assemblies = client.list_assemblies
16
+ expect(assemblies[:count]).to be >= 1
17
+
18
+ # Pattern completion: given "see" and "dog", predict "bark"
19
+ completion = client.pattern_complete(partial_ids: %i[see dog])
20
+ expect(completion[:success]).to be true
21
+ expect(completion[:completion][:predicted]).to include(:bark)
22
+
23
+ # Weights should be strong
24
+ weight = client.query_weight(from: :see, to: :dog)
25
+ expect(weight[:weight]).to be > 0.3
26
+
27
+ # Find assemblies containing :dog
28
+ dog_asms = client.assemblies_for(unit_id: :dog)
29
+ expect(dog_asms[:count]).to be >= 1
30
+
31
+ # Decay
32
+ client.update_hebbian
33
+
34
+ # Stats
35
+ stats = client.hebbian_stats
36
+ expect(stats[:stats][:units]).to eq(3)
37
+ end
38
+ end