lex-cognitive-catalyst 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.
- checksums.yaml +7 -0
- data/.github/workflows/ci.yml +16 -0
- data/.gitignore +17 -0
- data/.rspec +4 -0
- data/.rubocop.yml +29 -0
- data/CLAUDE.md +78 -0
- data/Gemfile +11 -0
- data/README.md +44 -0
- data/lex-cognitive-catalyst.gemspec +31 -0
- data/lib/legion/extensions/cognitive_catalyst/client.rb +11 -0
- data/lib/legion/extensions/cognitive_catalyst/helpers/catalyst.rb +83 -0
- data/lib/legion/extensions/cognitive_catalyst/helpers/catalyst_engine.rb +149 -0
- data/lib/legion/extensions/cognitive_catalyst/helpers/constants.rb +51 -0
- data/lib/legion/extensions/cognitive_catalyst/helpers/reaction.rb +83 -0
- data/lib/legion/extensions/cognitive_catalyst/runners/cognitive_catalyst.rb +103 -0
- data/lib/legion/extensions/cognitive_catalyst/version.rb +9 -0
- data/lib/legion/extensions/cognitive_catalyst.rb +19 -0
- data/spec/legion/extensions/cognitive_catalyst/client_spec.rb +58 -0
- data/spec/legion/extensions/cognitive_catalyst/helpers/catalyst_engine_spec.rb +263 -0
- data/spec/legion/extensions/cognitive_catalyst/helpers/catalyst_spec.rb +214 -0
- data/spec/legion/extensions/cognitive_catalyst/helpers/reaction_spec.rb +223 -0
- data/spec/legion/extensions/cognitive_catalyst/runners/cognitive_catalyst_spec.rb +217 -0
- data/spec/legion/extensions/cognitive_catalyst_spec.rb +49 -0
- data/spec/spec_helper.rb +34 -0
- metadata +86 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module CognitiveCatalyst
|
|
6
|
+
module Runners
|
|
7
|
+
module CognitiveCatalyst
|
|
8
|
+
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
|
|
9
|
+
Legion::Extensions::Helpers.const_defined?(:Lex)
|
|
10
|
+
|
|
11
|
+
def create_catalyst(catalyst_type:, domain:, potency: nil, specificity: nil, engine: nil, **)
|
|
12
|
+
e = engine || default_engine
|
|
13
|
+
unless Helpers::Constants::CATALYST_TYPES.include?(catalyst_type.to_sym)
|
|
14
|
+
raise ArgumentError, "invalid catalyst_type: #{catalyst_type}"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
opts = {
|
|
18
|
+
catalyst_type: catalyst_type.to_sym,
|
|
19
|
+
domain: domain
|
|
20
|
+
}
|
|
21
|
+
opts[:potency] = potency unless potency.nil?
|
|
22
|
+
opts[:specificity] = specificity unless specificity.nil?
|
|
23
|
+
|
|
24
|
+
catalyst = e.create_catalyst(**opts)
|
|
25
|
+
Legion::Logging.debug "[cognitive_catalyst] create_catalyst id=#{catalyst.id[0..7]} " \
|
|
26
|
+
"type=#{catalyst_type} domain=#{domain} potency=#{catalyst.potency.round(2)}"
|
|
27
|
+
{ success: true, catalyst: catalyst.to_h }
|
|
28
|
+
rescue ArgumentError => e
|
|
29
|
+
Legion::Logging.warn "[cognitive_catalyst] create_catalyst failed: #{e.message}"
|
|
30
|
+
{ success: false, reason: e.message }
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def create_reaction(reaction_type:, reactants:, activation_energy: nil, engine: nil, **)
|
|
34
|
+
e = engine || default_engine
|
|
35
|
+
unless Helpers::Constants::REACTION_TYPES.include?(reaction_type.to_sym)
|
|
36
|
+
raise ArgumentError, "invalid reaction_type: #{reaction_type}"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
opts = {
|
|
40
|
+
reaction_type: reaction_type.to_sym,
|
|
41
|
+
reactants: Array(reactants)
|
|
42
|
+
}
|
|
43
|
+
opts[:activation_energy] = activation_energy unless activation_energy.nil?
|
|
44
|
+
|
|
45
|
+
reaction = e.create_reaction(**opts)
|
|
46
|
+
Legion::Logging.debug "[cognitive_catalyst] create_reaction id=#{reaction.id[0..7]} " \
|
|
47
|
+
"type=#{reaction_type} reactants=#{reaction.reactants.size}"
|
|
48
|
+
{ success: true, reaction: reaction.to_h }
|
|
49
|
+
rescue ArgumentError => e
|
|
50
|
+
Legion::Logging.warn "[cognitive_catalyst] create_reaction failed: #{e.message}"
|
|
51
|
+
{ success: false, reason: e.message }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def apply_catalyst(catalyst_id:, reaction_id:, engine: nil, **)
|
|
55
|
+
e = engine || default_engine
|
|
56
|
+
result = e.apply_catalyst(catalyst_id: catalyst_id, reaction_id: reaction_id)
|
|
57
|
+
Legion::Logging.debug '[cognitive_catalyst] apply_catalyst ' \
|
|
58
|
+
"catalyst=#{catalyst_id[0..7]} reaction=#{reaction_id[0..7]} " \
|
|
59
|
+
"success=#{result[:success]}"
|
|
60
|
+
result
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def attempt_reaction(reaction_id:, energy_input:, engine: nil, **)
|
|
64
|
+
e = engine || default_engine
|
|
65
|
+
result = e.attempt_reaction(reaction_id: reaction_id, energy_input: energy_input)
|
|
66
|
+
Legion::Logging.debug "[cognitive_catalyst] attempt_reaction id=#{reaction_id[0..7]} " \
|
|
67
|
+
"energy=#{energy_input} completed=#{result[:completed]}"
|
|
68
|
+
result
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def recharge(catalyst_id:, amount:, engine: nil, **)
|
|
72
|
+
e = engine || default_engine
|
|
73
|
+
result = e.recharge_catalyst(catalyst_id: catalyst_id, amount: amount)
|
|
74
|
+
Legion::Logging.debug "[cognitive_catalyst] recharge id=#{catalyst_id[0..7]} " \
|
|
75
|
+
"amount=#{amount} potency=#{result[:potency]&.round(2)}"
|
|
76
|
+
result
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def list_catalysts(engine: nil, **)
|
|
80
|
+
e = engine || default_engine
|
|
81
|
+
catalysts = e.all_catalysts
|
|
82
|
+
Legion::Logging.debug "[cognitive_catalyst] list_catalysts count=#{catalysts.size}"
|
|
83
|
+
{ success: true, catalysts: catalysts.map(&:to_h), count: catalysts.size }
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def catalyst_status(engine: nil, **)
|
|
87
|
+
e = engine || default_engine
|
|
88
|
+
report = e.catalyst_report
|
|
89
|
+
Legion::Logging.debug "[cognitive_catalyst] catalyst_status total=#{report[:total_catalysts]} " \
|
|
90
|
+
"reactions=#{report[:total_reactions]} rate=#{report[:catalyzed_rate].round(2)}"
|
|
91
|
+
{ success: true }.merge(report)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
private
|
|
95
|
+
|
|
96
|
+
def default_engine
|
|
97
|
+
@default_engine ||= Helpers::CatalystEngine.new
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
|
|
5
|
+
require_relative 'cognitive_catalyst/version'
|
|
6
|
+
require_relative 'cognitive_catalyst/helpers/constants'
|
|
7
|
+
require_relative 'cognitive_catalyst/helpers/catalyst'
|
|
8
|
+
require_relative 'cognitive_catalyst/helpers/reaction'
|
|
9
|
+
require_relative 'cognitive_catalyst/helpers/catalyst_engine'
|
|
10
|
+
require_relative 'cognitive_catalyst/runners/cognitive_catalyst'
|
|
11
|
+
require_relative 'cognitive_catalyst/client'
|
|
12
|
+
|
|
13
|
+
module Legion
|
|
14
|
+
module Extensions
|
|
15
|
+
module CognitiveCatalyst
|
|
16
|
+
extend Legion::Extensions::Core if Legion::Extensions.const_defined?(:Core)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Legion::Extensions::CognitiveCatalyst::Client do
|
|
4
|
+
subject(:client) { described_class.new }
|
|
5
|
+
|
|
6
|
+
it 'includes Runners::CognitiveCatalyst' do
|
|
7
|
+
expect(client).to respond_to(:create_catalyst)
|
|
8
|
+
expect(client).to respond_to(:create_reaction)
|
|
9
|
+
expect(client).to respond_to(:apply_catalyst)
|
|
10
|
+
expect(client).to respond_to(:attempt_reaction)
|
|
11
|
+
expect(client).to respond_to(:recharge)
|
|
12
|
+
expect(client).to respond_to(:list_catalysts)
|
|
13
|
+
expect(client).to respond_to(:catalyst_status)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it 'can create and then apply a catalyst to a reaction' do
|
|
17
|
+
engine = Legion::Extensions::CognitiveCatalyst::Helpers::CatalystEngine.new
|
|
18
|
+
cat = client.create_catalyst(catalyst_type: :experience, domain: :learning,
|
|
19
|
+
potency: 0.9, specificity: 0.9, engine: engine)
|
|
20
|
+
rxn = client.create_reaction(reaction_type: :synthesis, reactants: %w[a b], engine: engine)
|
|
21
|
+
result = client.apply_catalyst(catalyst_id: cat[:catalyst][:id],
|
|
22
|
+
reaction_id: rxn[:reaction][:id],
|
|
23
|
+
engine: engine)
|
|
24
|
+
expect(result[:success]).to be true
|
|
25
|
+
expect(result[:activation_energy]).to be < 0.6
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it 'can complete a full catalyst workflow: create -> apply -> attempt -> report' do
|
|
29
|
+
engine = Legion::Extensions::CognitiveCatalyst::Helpers::CatalystEngine.new
|
|
30
|
+
cat = client.create_catalyst(catalyst_type: :insight, domain: :reasoning,
|
|
31
|
+
potency: 1.0, specificity: 1.0, engine: engine)
|
|
32
|
+
rxn = client.create_reaction(reaction_type: :decomposition,
|
|
33
|
+
reactants: %w[complex_idea],
|
|
34
|
+
activation_energy: 0.1,
|
|
35
|
+
engine: engine)
|
|
36
|
+
client.apply_catalyst(catalyst_id: cat[:catalyst][:id],
|
|
37
|
+
reaction_id: rxn[:reaction][:id],
|
|
38
|
+
engine: engine)
|
|
39
|
+
result = client.attempt_reaction(reaction_id: rxn[:reaction][:id],
|
|
40
|
+
energy_input: 0.8,
|
|
41
|
+
engine: engine)
|
|
42
|
+
expect(result[:completed]).to be true
|
|
43
|
+
expect(result[:catalyzed]).to be true
|
|
44
|
+
status = client.catalyst_status(engine: engine)
|
|
45
|
+
expect(status[:catalyzed_count]).to eq(1)
|
|
46
|
+
expect(status[:catalyzed_rate]).to eq(1.0)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it 'maintains separate state per client instance' do
|
|
50
|
+
c1 = described_class.new
|
|
51
|
+
c2 = described_class.new
|
|
52
|
+
c1.create_catalyst(catalyst_type: :insight, domain: :d)
|
|
53
|
+
s1 = c1.catalyst_status
|
|
54
|
+
s2 = c2.catalyst_status
|
|
55
|
+
expect(s1[:total_catalysts]).to eq(1)
|
|
56
|
+
expect(s2[:total_catalysts]).to eq(0)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Legion::Extensions::CognitiveCatalyst::Helpers::CatalystEngine do
|
|
4
|
+
subject(:engine) { described_class.new }
|
|
5
|
+
|
|
6
|
+
let(:constants) { Legion::Extensions::CognitiveCatalyst::Helpers::Constants }
|
|
7
|
+
|
|
8
|
+
def build_catalyst(potency: 0.7, specificity: 0.6)
|
|
9
|
+
engine.create_catalyst(catalyst_type: :insight, domain: :reasoning,
|
|
10
|
+
potency: potency, specificity: specificity)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def build_reaction(activation_energy: nil)
|
|
14
|
+
opts = { reaction_type: :synthesis, reactants: %w[a b] }
|
|
15
|
+
opts[:activation_energy] = activation_energy if activation_energy
|
|
16
|
+
engine.create_reaction(**opts)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
describe '#create_catalyst' do
|
|
20
|
+
it 'creates and returns a Catalyst' do
|
|
21
|
+
result = build_catalyst
|
|
22
|
+
expect(result).to be_a(Legion::Extensions::CognitiveCatalyst::Helpers::Catalyst)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
it 'stores the catalyst' do
|
|
26
|
+
build_catalyst
|
|
27
|
+
expect(engine.all_catalysts.size).to eq(1)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
it 'accepts all valid catalyst types' do
|
|
31
|
+
constants::CATALYST_TYPES.each do |type|
|
|
32
|
+
expect { engine.create_catalyst(catalyst_type: type, domain: :d) }.not_to raise_error
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
it 'raises ArgumentError for invalid catalyst_type' do
|
|
37
|
+
expect { engine.create_catalyst(catalyst_type: :invalid, domain: :d) }.to raise_error(ArgumentError)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it 'evicts oldest when at MAX_CATALYSTS capacity' do
|
|
41
|
+
max = constants::MAX_CATALYSTS
|
|
42
|
+
max.times { |i| engine.create_catalyst(catalyst_type: :insight, domain: "d#{i}") }
|
|
43
|
+
engine.create_catalyst(catalyst_type: :analogy, domain: :overflow)
|
|
44
|
+
expect(engine.all_catalysts.size).to eq(max)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
describe '#create_reaction' do
|
|
49
|
+
it 'creates and returns a Reaction' do
|
|
50
|
+
result = build_reaction
|
|
51
|
+
expect(result).to be_a(Legion::Extensions::CognitiveCatalyst::Helpers::Reaction)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
it 'stores the reaction' do
|
|
55
|
+
build_reaction
|
|
56
|
+
expect(engine.all_reactions.size).to eq(1)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
it 'accepts all valid reaction types' do
|
|
60
|
+
constants::REACTION_TYPES.each do |type|
|
|
61
|
+
expect { engine.create_reaction(reaction_type: type, reactants: ['x']) }.not_to raise_error
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it 'raises ArgumentError for invalid reaction_type' do
|
|
66
|
+
expect { engine.create_reaction(reaction_type: :explode, reactants: []) }.to raise_error(ArgumentError)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it 'evicts oldest when at MAX_REACTIONS capacity' do
|
|
70
|
+
max = constants::MAX_REACTIONS
|
|
71
|
+
max.times { |i| engine.create_reaction(reaction_type: :synthesis, reactants: ["r#{i}"]) }
|
|
72
|
+
engine.create_reaction(reaction_type: :exchange, reactants: [:overflow])
|
|
73
|
+
expect(engine.all_reactions.size).to eq(max)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
describe '#apply_catalyst' do
|
|
78
|
+
let(:catalyst) { build_catalyst }
|
|
79
|
+
let(:reaction) { build_reaction }
|
|
80
|
+
|
|
81
|
+
it 'returns success with updated activation_energy' do
|
|
82
|
+
result = engine.apply_catalyst(catalyst_id: catalyst.id, reaction_id: reaction.id)
|
|
83
|
+
expect(result[:success]).to be true
|
|
84
|
+
expect(result[:activation_energy]).to be < constants::ACTIVATION_ENERGY
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
it 'returns catalyst_not_found for unknown catalyst' do
|
|
88
|
+
result = engine.apply_catalyst(catalyst_id: 'bad', reaction_id: reaction.id)
|
|
89
|
+
expect(result[:reason]).to eq(:catalyst_not_found)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
it 'returns reaction_not_found for unknown reaction' do
|
|
93
|
+
result = engine.apply_catalyst(catalyst_id: catalyst.id, reaction_id: 'bad')
|
|
94
|
+
expect(result[:reason]).to eq(:reaction_not_found)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
it 'returns already_completed for a done reaction' do
|
|
98
|
+
engine.attempt_reaction(reaction_id: reaction.id, energy_input: 1.0)
|
|
99
|
+
result = engine.apply_catalyst(catalyst_id: catalyst.id, reaction_id: reaction.id)
|
|
100
|
+
expect(result[:reason]).to eq(:already_completed)
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
describe '#attempt_reaction' do
|
|
105
|
+
let(:reaction) { build_reaction }
|
|
106
|
+
|
|
107
|
+
it 'completes the reaction when energy is sufficient' do
|
|
108
|
+
result = engine.attempt_reaction(reaction_id: reaction.id, energy_input: 1.0)
|
|
109
|
+
expect(result[:completed]).to be true
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
it 'does not complete when energy is insufficient' do
|
|
113
|
+
result = engine.attempt_reaction(reaction_id: reaction.id, energy_input: 0.1)
|
|
114
|
+
expect(result[:completed]).to be false
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
it 'returns not_found for unknown reaction' do
|
|
118
|
+
result = engine.attempt_reaction(reaction_id: 'missing', energy_input: 1.0)
|
|
119
|
+
expect(result[:reason]).to eq(:not_found)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
it 'returns already_completed for a done reaction' do
|
|
123
|
+
engine.attempt_reaction(reaction_id: reaction.id, energy_input: 1.0)
|
|
124
|
+
result = engine.attempt_reaction(reaction_id: reaction.id, energy_input: 1.0)
|
|
125
|
+
expect(result[:reason]).to eq(:already_completed)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
it 'includes yield_value and yield_label' do
|
|
129
|
+
result = engine.attempt_reaction(reaction_id: reaction.id, energy_input: 1.0)
|
|
130
|
+
expect(result).to include(:yield_value, :yield_label)
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
describe '#degrade_all!' do
|
|
135
|
+
it 'reduces potency of all catalysts' do
|
|
136
|
+
c = build_catalyst(potency: 0.8)
|
|
137
|
+
engine.degrade_all!
|
|
138
|
+
expect(c.potency).to be < 0.8
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
it 'does nothing when no catalysts exist' do
|
|
142
|
+
expect { engine.degrade_all! }.not_to raise_error
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
describe '#recharge_catalyst' do
|
|
147
|
+
let(:catalyst) { build_catalyst(potency: 0.3) }
|
|
148
|
+
|
|
149
|
+
it 'increases potency' do
|
|
150
|
+
result = engine.recharge_catalyst(catalyst_id: catalyst.id, amount: 0.2)
|
|
151
|
+
expect(result[:success]).to be true
|
|
152
|
+
expect(result[:potency]).to be_within(0.001).of(0.5)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
it 'returns not_found for unknown catalyst' do
|
|
156
|
+
result = engine.recharge_catalyst(catalyst_id: 'bad', amount: 0.1)
|
|
157
|
+
expect(result[:reason]).to eq(:not_found)
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
it 'includes potency_label in result' do
|
|
161
|
+
result = engine.recharge_catalyst(catalyst_id: catalyst.id, amount: 0.6)
|
|
162
|
+
expect(result).to include(:potency_label)
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
describe '#all_catalysts' do
|
|
167
|
+
it 'returns empty array initially' do
|
|
168
|
+
expect(engine.all_catalysts).to eq([])
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
it 'returns all stored catalysts' do
|
|
172
|
+
2.times { build_catalyst }
|
|
173
|
+
expect(engine.all_catalysts.size).to eq(2)
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
describe '#all_reactions' do
|
|
178
|
+
it 'returns empty array initially' do
|
|
179
|
+
expect(engine.all_reactions).to eq([])
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
it 'returns all stored reactions' do
|
|
183
|
+
2.times { build_reaction }
|
|
184
|
+
expect(engine.all_reactions.size).to eq(2)
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
describe '#completed_reactions' do
|
|
189
|
+
it 'returns only completed reactions' do
|
|
190
|
+
r1 = build_reaction
|
|
191
|
+
r2 = build_reaction
|
|
192
|
+
engine.attempt_reaction(reaction_id: r1.id, energy_input: 1.0)
|
|
193
|
+
engine.attempt_reaction(reaction_id: r2.id, energy_input: 0.1)
|
|
194
|
+
expect(engine.completed_reactions.size).to eq(1)
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
describe '#catalyzed_rate' do
|
|
199
|
+
it 'returns 0.0 when no reactions completed' do
|
|
200
|
+
expect(engine.catalyzed_rate).to eq(0.0)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
it 'returns 1.0 when all completed reactions were catalyzed' do
|
|
204
|
+
catalyst = build_catalyst(potency: 1.0, specificity: 1.0)
|
|
205
|
+
reaction = build_reaction(activation_energy: 0.01)
|
|
206
|
+
engine.apply_catalyst(catalyst_id: catalyst.id, reaction_id: reaction.id)
|
|
207
|
+
engine.attempt_reaction(reaction_id: reaction.id, energy_input: 1.0)
|
|
208
|
+
expect(engine.catalyzed_rate).to eq(1.0)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
it 'returns 0.0 when no completed reactions were catalyzed' do
|
|
212
|
+
reaction = build_reaction
|
|
213
|
+
engine.attempt_reaction(reaction_id: reaction.id, energy_input: 1.0)
|
|
214
|
+
expect(engine.catalyzed_rate).to eq(0.0)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
it 'returns fractional rate for mixed completions' do
|
|
218
|
+
r1 = build_reaction
|
|
219
|
+
r2 = build_reaction
|
|
220
|
+
engine.attempt_reaction(reaction_id: r1.id, energy_input: 1.0)
|
|
221
|
+
catalyst = build_catalyst(potency: 1.0, specificity: 1.0)
|
|
222
|
+
engine.apply_catalyst(catalyst_id: catalyst.id, reaction_id: r2.id)
|
|
223
|
+
engine.attempt_reaction(reaction_id: r2.id, energy_input: 1.0)
|
|
224
|
+
expect(engine.catalyzed_rate).to be_within(0.01).of(0.5)
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
describe '#catalyst_report' do
|
|
229
|
+
it 'returns a report hash with all required keys' do
|
|
230
|
+
report = engine.catalyst_report
|
|
231
|
+
expect(report).to include(
|
|
232
|
+
:total_catalysts, :total_reactions, :completed, :catalyzed_count,
|
|
233
|
+
:catalyzed_rate, :avg_potency, :powerful_count, :inert_count
|
|
234
|
+
)
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
it 'counts powerful catalysts' do
|
|
238
|
+
build_catalyst(potency: 0.9)
|
|
239
|
+
build_catalyst(potency: 0.5)
|
|
240
|
+
report = engine.catalyst_report
|
|
241
|
+
expect(report[:powerful_count]).to eq(1)
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
it 'counts inert catalysts' do
|
|
245
|
+
engine.create_catalyst(catalyst_type: :insight, domain: :d, potency: 0.1)
|
|
246
|
+
engine.create_catalyst(catalyst_type: :insight, domain: :d, potency: 0.5)
|
|
247
|
+
report = engine.catalyst_report
|
|
248
|
+
expect(report[:inert_count]).to eq(1)
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
it 'computes avg_potency' do
|
|
252
|
+
engine.create_catalyst(catalyst_type: :insight, domain: :d, potency: 0.4)
|
|
253
|
+
engine.create_catalyst(catalyst_type: :insight, domain: :d, potency: 0.6)
|
|
254
|
+
report = engine.catalyst_report
|
|
255
|
+
expect(report[:avg_potency]).to be_within(0.01).of(0.5)
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
it 'returns avg_potency of 0.0 when no catalysts' do
|
|
259
|
+
report = engine.catalyst_report
|
|
260
|
+
expect(report[:avg_potency]).to eq(0.0)
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
end
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe Legion::Extensions::CognitiveCatalyst::Helpers::Catalyst do
|
|
4
|
+
subject(:catalyst) { described_class.new(catalyst_type: :insight, domain: :reasoning) }
|
|
5
|
+
|
|
6
|
+
describe '#initialize' do
|
|
7
|
+
it 'assigns a uuid id' do
|
|
8
|
+
expect(catalyst.id).to match(/\A[0-9a-f-]{36}\z/)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it 'assigns catalyst_type' do
|
|
12
|
+
expect(catalyst.catalyst_type).to eq(:insight)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
it 'assigns domain' do
|
|
16
|
+
expect(catalyst.domain).to eq(:reasoning)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
it 'defaults potency to 0.5' do
|
|
20
|
+
expect(catalyst.potency).to eq(0.5)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
it 'defaults specificity to 0.5' do
|
|
24
|
+
expect(catalyst.specificity).to eq(0.5)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
it 'defaults uses_count to 0' do
|
|
28
|
+
expect(catalyst.uses_count).to eq(0)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
it 'records created_at' do
|
|
32
|
+
expect(catalyst.created_at).to be_a(Time)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
it 'clamps potency above 1.0' do
|
|
36
|
+
c = described_class.new(catalyst_type: :analogy, domain: :d, potency: 2.0)
|
|
37
|
+
expect(c.potency).to eq(1.0)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
it 'clamps potency below 0.0' do
|
|
41
|
+
c = described_class.new(catalyst_type: :analogy, domain: :d, potency: -0.5)
|
|
42
|
+
expect(c.potency).to eq(0.0)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it 'clamps specificity above 1.0' do
|
|
46
|
+
c = described_class.new(catalyst_type: :analogy, domain: :d, specificity: 5.0)
|
|
47
|
+
expect(c.specificity).to eq(1.0)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
it 'accepts custom potency' do
|
|
51
|
+
c = described_class.new(catalyst_type: :pattern, domain: :d, potency: 0.9)
|
|
52
|
+
expect(c.potency).to eq(0.9)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
describe '#catalyze!' do
|
|
57
|
+
it 'increments uses_count each call' do
|
|
58
|
+
catalyst.catalyze!(:synthesis)
|
|
59
|
+
catalyst.catalyze!(:synthesis)
|
|
60
|
+
expect(catalyst.uses_count).to eq(2)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
it 'returns activation_reduction = potency * specificity' do
|
|
64
|
+
c = described_class.new(catalyst_type: :insight, domain: :d, potency: 0.8, specificity: 0.5)
|
|
65
|
+
reduction = c.catalyze!(:synthesis)
|
|
66
|
+
expect(reduction).to be_within(0.0001).of(0.4)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
it 'does NOT reduce potency (catalysts are not consumed)' do
|
|
70
|
+
original = catalyst.potency
|
|
71
|
+
catalyst.catalyze!(:synthesis)
|
|
72
|
+
expect(catalyst.potency).to eq(original)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
it 'returns a float' do
|
|
76
|
+
expect(catalyst.catalyze!(:exchange)).to be_a(Float)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it 'works with all reaction types' do
|
|
80
|
+
Legion::Extensions::CognitiveCatalyst::Helpers::Constants::REACTION_TYPES.each do |rt|
|
|
81
|
+
expect { catalyst.catalyze!(rt) }.not_to raise_error
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
describe '#degrade!' do
|
|
87
|
+
it 'reduces potency by POTENCY_DECAY' do
|
|
88
|
+
decay = Legion::Extensions::CognitiveCatalyst::Helpers::Constants::POTENCY_DECAY
|
|
89
|
+
original = catalyst.potency
|
|
90
|
+
catalyst.degrade!
|
|
91
|
+
expect(catalyst.potency).to be_within(0.0001).of(original - decay)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
it 'does not reduce potency below 0.0' do
|
|
95
|
+
c = described_class.new(catalyst_type: :insight, domain: :d, potency: 0.01)
|
|
96
|
+
c.degrade!
|
|
97
|
+
expect(c.potency).to eq(0.0)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
it 'can be called multiple times' do
|
|
101
|
+
5.times { catalyst.degrade! }
|
|
102
|
+
expect(catalyst.potency).to be >= 0.0
|
|
103
|
+
expect(catalyst.potency).to be < 0.5
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
describe '#recharge!' do
|
|
108
|
+
it 'increases potency by amount' do
|
|
109
|
+
c = described_class.new(catalyst_type: :insight, domain: :d, potency: 0.3)
|
|
110
|
+
c.recharge!(0.2)
|
|
111
|
+
expect(c.potency).to be_within(0.0001).of(0.5)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
it 'clamps at 1.0' do
|
|
115
|
+
c = described_class.new(catalyst_type: :insight, domain: :d, potency: 0.9)
|
|
116
|
+
c.recharge!(0.5)
|
|
117
|
+
expect(c.potency).to eq(1.0)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
it 'returns the new potency' do
|
|
121
|
+
result = catalyst.recharge!(0.1)
|
|
122
|
+
expect(result).to be_a(Float)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
describe '#powerful?' do
|
|
127
|
+
it 'returns true for potency >= 0.8' do
|
|
128
|
+
c = described_class.new(catalyst_type: :insight, domain: :d, potency: 0.8)
|
|
129
|
+
expect(c.powerful?).to be true
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
it 'returns false for potency < 0.8' do
|
|
133
|
+
c = described_class.new(catalyst_type: :insight, domain: :d, potency: 0.7)
|
|
134
|
+
expect(c.powerful?).to be false
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
describe '#inert?' do
|
|
139
|
+
it 'returns true for potency < 0.2' do
|
|
140
|
+
c = described_class.new(catalyst_type: :insight, domain: :d, potency: 0.1)
|
|
141
|
+
expect(c.inert?).to be true
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
it 'returns false for potency >= 0.2' do
|
|
145
|
+
c = described_class.new(catalyst_type: :insight, domain: :d, potency: 0.2)
|
|
146
|
+
expect(c.inert?).to be false
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
describe '#specific?' do
|
|
151
|
+
it 'returns true for specificity >= 0.7' do
|
|
152
|
+
c = described_class.new(catalyst_type: :insight, domain: :d, specificity: 0.7)
|
|
153
|
+
expect(c.specific?).to be true
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
it 'returns false for specificity < 0.7' do
|
|
157
|
+
c = described_class.new(catalyst_type: :insight, domain: :d, specificity: 0.6)
|
|
158
|
+
expect(c.specific?).to be false
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
describe '#broad?' do
|
|
163
|
+
it 'returns true for specificity < 0.3' do
|
|
164
|
+
c = described_class.new(catalyst_type: :insight, domain: :d, specificity: 0.2)
|
|
165
|
+
expect(c.broad?).to be true
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
it 'returns false for specificity >= 0.3' do
|
|
169
|
+
c = described_class.new(catalyst_type: :insight, domain: :d, specificity: 0.3)
|
|
170
|
+
expect(c.broad?).to be false
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
describe '#potency_label' do
|
|
175
|
+
it 'returns :powerful for potency >= 0.8' do
|
|
176
|
+
c = described_class.new(catalyst_type: :insight, domain: :d, potency: 0.9)
|
|
177
|
+
expect(c.potency_label).to eq(:powerful)
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
it 'returns :strong for potency in 0.6...0.8' do
|
|
181
|
+
c = described_class.new(catalyst_type: :insight, domain: :d, potency: 0.7)
|
|
182
|
+
expect(c.potency_label).to eq(:strong)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
it 'returns :moderate for potency in 0.4...0.6' do
|
|
186
|
+
c = described_class.new(catalyst_type: :insight, domain: :d, potency: 0.5)
|
|
187
|
+
expect(c.potency_label).to eq(:moderate)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
it 'returns :weak for potency in 0.2...0.4' do
|
|
191
|
+
c = described_class.new(catalyst_type: :insight, domain: :d, potency: 0.3)
|
|
192
|
+
expect(c.potency_label).to eq(:weak)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
it 'returns :inert for potency < 0.2' do
|
|
196
|
+
c = described_class.new(catalyst_type: :insight, domain: :d, potency: 0.1)
|
|
197
|
+
expect(c.potency_label).to eq(:inert)
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
describe '#to_h' do
|
|
202
|
+
it 'returns a hash with all required keys' do
|
|
203
|
+
h = catalyst.to_h
|
|
204
|
+
expect(h).to include(:id, :catalyst_type, :domain, :potency, :specificity,
|
|
205
|
+
:uses_count, :potency_label, :powerful, :inert,
|
|
206
|
+
:specific, :broad, :created_at)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
it 'reflects current state' do
|
|
210
|
+
catalyst.catalyze!(:synthesis)
|
|
211
|
+
expect(catalyst.to_h[:uses_count]).to eq(1)
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|