lex-mind-growth 0.2.0 → 0.2.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 999d127eafaaedc61d385450a47bfbceb21d0f9ce7ef753205fc18dff5ca3de0
4
- data.tar.gz: 2ee2545c17f817ed5df9be04b88d6869f6032709e5cdd621e351df29b82908f3
3
+ metadata.gz: 44ba3c4f6e43488f8e92edb6be0b6204890b58f76220efd964c924db011674b3
4
+ data.tar.gz: 571392567974177a54cc4d3df6b094ad895849f3de415b271f0133bb3ed6d40d
5
5
  SHA512:
6
- metadata.gz: db4150d16a4a452f59ecdc32ee140031b9d8fc8d03bab1f082accadba07be7d708cee018106dbec216c0246f243e9086e435788e0ec426f96a56e46e82aa30d7
7
- data.tar.gz: b8c2d52aeb38acde956b34c75df47aff07520c3047becae634e5e662118bbb65fc9747778326535dbe38e2f23b636f4432653b900a4d403560e5ababd8bc8ce8
6
+ metadata.gz: 23e9288fa07cce5693c0aa7f9475ad7f6f9c2f7e211fecf39c4dc4f2309ed674115537358dbcf65dda6b3091e1153927716b2d17798b16ff7b368d7576f6612c
7
+ data.tar.gz: 7a141886553785dca8178cacb63f5021be1ae3aa1eee80003f86e06b5b3a641778e5cc85f282db229916d4ab0d812a4c0c0d82b3d8e652061578043e51a961d2
@@ -91,6 +91,15 @@ module Legion
91
91
  def resolve_disagreement(**) = Runners::ConsensusBuilder.resolve_disagreement(**)
92
92
  def consensus_summary(**) = Runners::ConsensusBuilder.consensus_summary(**)
93
93
 
94
+ # CompetitiveEvolver delegation
95
+ def create_competition(**) = Runners::CompetitiveEvolver.create_competition(**)
96
+ def run_trial(**) = Runners::CompetitiveEvolver.run_trial(**)
97
+ def compare_results(**) = Runners::CompetitiveEvolver.compare_results(**)
98
+ def declare_winner(**) = Runners::CompetitiveEvolver.declare_winner(**)
99
+ def competition_status(**) = Runners::CompetitiveEvolver.competition_status(**)
100
+ def active_competitions(**) = Runners::CompetitiveEvolver.active_competitions(**)
101
+ def competition_history(**) = Runners::CompetitiveEvolver.competition_history(**)
102
+
94
103
  # Dashboard delegation
95
104
  def extension_timeline(**) = Runners::Dashboard.extension_timeline(**)
96
105
  def category_distribution(**) = Runners::Dashboard.category_distribution(**)
@@ -0,0 +1,165 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module MindGrowth
6
+ module Runners
7
+ module CompetitiveEvolver
8
+ extend self
9
+
10
+ COMPETITION_STATUSES = %i[pending active evaluating decided cancelled].freeze
11
+ MIN_TRIAL_ITERATIONS = 10
12
+
13
+ def create_competition(gap:, proposal_ids:, **)
14
+ ids = Array(proposal_ids)
15
+ return { success: false, reason: :insufficient_competitors } if ids.size < 2
16
+
17
+ competition_id = SecureRandom.uuid
18
+ competition = {
19
+ id: competition_id,
20
+ gap: gap.to_s,
21
+ proposal_ids: ids,
22
+ status: :pending,
23
+ trials: {},
24
+ winner: nil,
25
+ created_at: Time.now.utc,
26
+ decided_at: nil
27
+ }
28
+
29
+ store_competition(competition)
30
+ { success: true, competition_id: competition_id, gap: gap.to_s, competitors: ids.size }
31
+ end
32
+
33
+ def run_trial(competition_id:, extension:, iterations: MIN_TRIAL_ITERATIONS, **)
34
+ competition = get_competition(competition_id)
35
+ return { success: false, reason: :not_found } unless competition
36
+ return { success: false, reason: :already_decided } if competition[:status] == :decided
37
+
38
+ transition_competition(competition_id, :active) if competition[:status] == :pending
39
+
40
+ fitness = Helpers::FitnessEvaluator.fitness(extension)
41
+ name = extension[:name] || extension[:extension_name]
42
+
43
+ trial = {
44
+ extension_name: name,
45
+ fitness: fitness,
46
+ error_rate: extension[:error_rate] || 0.0,
47
+ avg_latency_ms: extension[:avg_latency_ms] || 0,
48
+ invocations: extension[:invocation_count] || 0,
49
+ iterations: iterations,
50
+ recorded_at: Time.now.utc
51
+ }
52
+
53
+ record_trial(competition_id, name, trial)
54
+ { success: true, competition_id: competition_id, trial: trial }
55
+ end
56
+
57
+ def compare_results(competition_id:, **)
58
+ competition = get_competition(competition_id)
59
+ return { success: false, reason: :not_found } unless competition
60
+
61
+ trials = competition[:trials]
62
+ return { success: true, comparison: [], leader: nil } if trials.empty?
63
+
64
+ ranked = trials.values.sort_by { |t| [-t[:fitness], t[:avg_latency_ms]] }
65
+ leader = ranked.first
66
+
67
+ comparison = ranked.map.with_index(1) do |trial, rank|
68
+ {
69
+ extension_name: trial[:extension_name],
70
+ fitness: trial[:fitness],
71
+ error_rate: trial[:error_rate],
72
+ avg_latency_ms: trial[:avg_latency_ms],
73
+ rank: rank
74
+ }
75
+ end
76
+
77
+ { success: true, comparison: comparison, leader: leader[:extension_name] }
78
+ end
79
+
80
+ def declare_winner(competition_id:, **)
81
+ competition = get_competition(competition_id)
82
+ return { success: false, reason: :not_found } unless competition
83
+ return { success: false, reason: :already_decided } if competition[:status] == :decided
84
+ return { success: false, reason: :no_trials } if competition[:trials].empty?
85
+
86
+ comparison = compare_results(competition_id: competition_id)
87
+ winner_name = comparison[:leader]
88
+
89
+ losers = competition[:trials].keys.reject { |name| name == winner_name }
90
+
91
+ losers.each do |loser_name|
92
+ Runners::Evolver.replace_extension(old_name: loser_name, new_proposal_id: "winner:#{winner_name}")
93
+ end
94
+
95
+ transition_competition(competition_id, :decided)
96
+ set_winner(competition_id, winner_name)
97
+
98
+ { success: true, winner: winner_name, losers: losers, competition_id: competition_id }
99
+ end
100
+
101
+ def competition_status(competition_id:, **)
102
+ competition = get_competition(competition_id)
103
+ return { success: false, reason: :not_found } unless competition
104
+
105
+ { success: true, id: competition[:id], gap: competition[:gap],
106
+ status: competition[:status], competitors: competition[:proposal_ids],
107
+ trial_count: competition[:trials].size, winner: competition[:winner] }
108
+ end
109
+
110
+ def active_competitions(**)
111
+ comps = all_competitions.select { |c| %i[pending active evaluating].include?(c[:status]) }
112
+ { success: true, competitions: comps.map { |c| { id: c[:id], gap: c[:gap], status: c[:status] } },
113
+ count: comps.size }
114
+ end
115
+
116
+ def competition_history(limit: 20, **)
117
+ comps = all_competitions.sort_by { |c| c[:created_at] }.reverse.first(limit)
118
+ entries = comps.map do |c|
119
+ { id: c[:id], gap: c[:gap], status: c[:status], winner: c[:winner],
120
+ competitors: c[:proposal_ids].size, trial_count: c[:trials].size }
121
+ end
122
+ { success: true, competitions: entries, count: entries.size }
123
+ end
124
+
125
+ private
126
+
127
+ def competitions
128
+ @competitions ||= {}
129
+ end
130
+
131
+ def mutex
132
+ @mutex ||= Mutex.new
133
+ end
134
+
135
+ def store_competition(competition)
136
+ mutex.synchronize { competitions[competition[:id]] = competition }
137
+ end
138
+
139
+ def get_competition(id)
140
+ mutex.synchronize { competitions[id]&.dup }
141
+ end
142
+
143
+ def all_competitions
144
+ mutex.synchronize { competitions.values.map(&:dup) }
145
+ end
146
+
147
+ def transition_competition(id, new_status)
148
+ mutex.synchronize do
149
+ competitions[id][:status] = new_status
150
+ competitions[id][:decided_at] = Time.now.utc if new_status == :decided
151
+ end
152
+ end
153
+
154
+ def set_winner(id, winner_name)
155
+ mutex.synchronize { competitions[id][:winner] = winner_name }
156
+ end
157
+
158
+ def record_trial(competition_id, name, trial)
159
+ mutex.synchronize { competitions[competition_id][:trials][name] = trial }
160
+ end
161
+ end
162
+ end
163
+ end
164
+ end
165
+ end
@@ -3,7 +3,7 @@
3
3
  module Legion
4
4
  module Extensions
5
5
  module MindGrowth
6
- VERSION = '0.2.0'
6
+ VERSION = '0.2.1'
7
7
  end
8
8
  end
9
9
  end
@@ -28,6 +28,7 @@ require 'legion/extensions/mind_growth/runners/evolver'
28
28
  require 'legion/extensions/mind_growth/runners/dashboard'
29
29
  require 'legion/extensions/mind_growth/runners/swarm_builder'
30
30
  require 'legion/extensions/mind_growth/runners/consensus_builder'
31
+ require 'legion/extensions/mind_growth/runners/competitive_evolver'
31
32
  require 'legion/extensions/mind_growth/client'
32
33
 
33
34
  module Legion
@@ -0,0 +1,340 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Legion::Extensions::MindGrowth::Runners::CompetitiveEvolver do
4
+ subject(:evolver) { described_class }
5
+
6
+ # Reset internal state between tests
7
+ after do
8
+ described_class.instance_variable_set(:@competitions, {})
9
+ end
10
+
11
+ # ─── helpers ──────────────────────────────────────────────────────────────
12
+
13
+ def build_extension(name:, invocation_count: 100, error_rate: 0.01, avg_latency_ms: 50,
14
+ impact_score: 0.7, health_score: 0.8, category: :cognition)
15
+ { name: name, invocation_count: invocation_count, error_rate: error_rate,
16
+ avg_latency_ms: avg_latency_ms, impact_score: impact_score,
17
+ health_score: health_score, category: category }
18
+ end
19
+
20
+ def create_and_return_id(gap: 'working_memory', proposal_ids: %w[p1 p2])
21
+ result = evolver.create_competition(gap: gap, proposal_ids: proposal_ids)
22
+ result[:competition_id]
23
+ end
24
+
25
+ # ─── constants ───────────────────────────────────────────────────────────
26
+
27
+ describe 'constants' do
28
+ it 'defines COMPETITION_STATUSES' do
29
+ expect(described_class::COMPETITION_STATUSES).to contain_exactly(
30
+ :pending, :active, :evaluating, :decided, :cancelled
31
+ )
32
+ end
33
+
34
+ it 'defines MIN_TRIAL_ITERATIONS as 10' do
35
+ expect(described_class::MIN_TRIAL_ITERATIONS).to eq(10)
36
+ end
37
+ end
38
+
39
+ # ─── create_competition ─────────────────────────────────────────────────
40
+
41
+ describe '.create_competition' do
42
+ it 'returns success: true with a competition_id' do
43
+ result = evolver.create_competition(gap: 'attention', proposal_ids: %w[p1 p2])
44
+ expect(result[:success]).to be true
45
+ expect(result[:competition_id]).to be_a(String)
46
+ end
47
+
48
+ it 'returns the gap and competitor count' do
49
+ result = evolver.create_competition(gap: 'attention', proposal_ids: %w[p1 p2 p3])
50
+ expect(result[:gap]).to eq('attention')
51
+ expect(result[:competitors]).to eq(3)
52
+ end
53
+
54
+ it 'requires at least 2 competitors' do
55
+ result = evolver.create_competition(gap: 'attention', proposal_ids: ['p1'])
56
+ expect(result[:success]).to be false
57
+ expect(result[:reason]).to eq(:insufficient_competitors)
58
+ end
59
+
60
+ it 'returns failure for empty proposal_ids' do
61
+ result = evolver.create_competition(gap: 'attention', proposal_ids: [])
62
+ expect(result[:success]).to be false
63
+ end
64
+
65
+ it 'creates competition in :pending status' do
66
+ cid = create_and_return_id
67
+ status = evolver.competition_status(competition_id: cid)
68
+ expect(status[:status]).to eq(:pending)
69
+ end
70
+ end
71
+
72
+ # ─── run_trial ──────────────────────────────────────────────────────────
73
+
74
+ describe '.run_trial' do
75
+ let(:competition_id) { create_and_return_id }
76
+
77
+ it 'returns success: true with trial data' do
78
+ ext = build_extension(name: 'ext-a')
79
+ result = evolver.run_trial(competition_id: competition_id, extension: ext)
80
+ expect(result[:success]).to be true
81
+ expect(result[:trial][:extension_name]).to eq('ext-a')
82
+ end
83
+
84
+ it 'records the fitness score from FitnessEvaluator' do
85
+ ext = build_extension(name: 'ext-a', invocation_count: 1000, impact_score: 0.9, health_score: 1.0)
86
+ result = evolver.run_trial(competition_id: competition_id, extension: ext)
87
+ expect(result[:trial][:fitness]).to be > 0
88
+ end
89
+
90
+ it 'transitions competition to :active on first trial' do
91
+ ext = build_extension(name: 'ext-a')
92
+ evolver.run_trial(competition_id: competition_id, extension: ext)
93
+ status = evolver.competition_status(competition_id: competition_id)
94
+ expect(status[:status]).to eq(:active)
95
+ end
96
+
97
+ it 'returns failure for nonexistent competition' do
98
+ ext = build_extension(name: 'ext-a')
99
+ result = evolver.run_trial(competition_id: 'bogus', extension: ext)
100
+ expect(result[:success]).to be false
101
+ expect(result[:reason]).to eq(:not_found)
102
+ end
103
+
104
+ it 'returns failure for decided competition' do
105
+ cid = create_and_return_id
106
+ ext_a = build_extension(name: 'ext-a', invocation_count: 1000, impact_score: 0.9)
107
+ ext_b = build_extension(name: 'ext-b', invocation_count: 10, impact_score: 0.1)
108
+ evolver.run_trial(competition_id: cid, extension: ext_a)
109
+ evolver.run_trial(competition_id: cid, extension: ext_b)
110
+ evolver.declare_winner(competition_id: cid)
111
+
112
+ result = evolver.run_trial(competition_id: cid, extension: ext_a)
113
+ expect(result[:success]).to be false
114
+ expect(result[:reason]).to eq(:already_decided)
115
+ end
116
+
117
+ it 'uses extension_name if name is not present' do
118
+ ext = { extension_name: 'ext-fallback', invocation_count: 50, error_rate: 0.0,
119
+ avg_latency_ms: 10, impact_score: 0.5, health_score: 0.7 }
120
+ result = evolver.run_trial(competition_id: competition_id, extension: ext)
121
+ expect(result[:trial][:extension_name]).to eq('ext-fallback')
122
+ end
123
+
124
+ it 'records custom iteration count' do
125
+ ext = build_extension(name: 'ext-a')
126
+ result = evolver.run_trial(competition_id: competition_id, extension: ext, iterations: 50)
127
+ expect(result[:trial][:iterations]).to eq(50)
128
+ end
129
+ end
130
+
131
+ # ─── compare_results ────────────────────────────────────────────────────
132
+
133
+ describe '.compare_results' do
134
+ let(:competition_id) { create_and_return_id }
135
+
136
+ it 'returns empty comparison when no trials exist' do
137
+ result = evolver.compare_results(competition_id: competition_id)
138
+ expect(result[:success]).to be true
139
+ expect(result[:comparison]).to be_empty
140
+ expect(result[:leader]).to be_nil
141
+ end
142
+
143
+ it 'ranks extensions by fitness descending' do
144
+ ext_high = build_extension(name: 'winner', invocation_count: 1000, impact_score: 0.9, health_score: 1.0)
145
+ ext_low = build_extension(name: 'loser', invocation_count: 10, impact_score: 0.1, health_score: 0.3)
146
+ evolver.run_trial(competition_id: competition_id, extension: ext_low)
147
+ evolver.run_trial(competition_id: competition_id, extension: ext_high)
148
+
149
+ result = evolver.compare_results(competition_id: competition_id)
150
+ expect(result[:leader]).to eq('winner')
151
+ expect(result[:comparison].first[:rank]).to eq(1)
152
+ expect(result[:comparison].first[:extension_name]).to eq('winner')
153
+ end
154
+
155
+ it 'breaks ties by lower latency' do
156
+ ext_a = build_extension(name: 'fast', invocation_count: 100, impact_score: 0.7,
157
+ health_score: 0.8, avg_latency_ms: 10)
158
+ ext_b = build_extension(name: 'slow', invocation_count: 100, impact_score: 0.7,
159
+ health_score: 0.8, avg_latency_ms: 500)
160
+ evolver.run_trial(competition_id: competition_id, extension: ext_a)
161
+ evolver.run_trial(competition_id: competition_id, extension: ext_b)
162
+
163
+ result = evolver.compare_results(competition_id: competition_id)
164
+ expect(result[:leader]).to eq('fast')
165
+ end
166
+
167
+ it 'returns failure for nonexistent competition' do
168
+ result = evolver.compare_results(competition_id: 'bogus')
169
+ expect(result[:success]).to be false
170
+ end
171
+
172
+ it 'includes all trial data in comparison' do
173
+ ext = build_extension(name: 'solo', error_rate: 0.05, avg_latency_ms: 42)
174
+ evolver.run_trial(competition_id: competition_id, extension: ext)
175
+ result = evolver.compare_results(competition_id: competition_id)
176
+ entry = result[:comparison].first
177
+ expect(entry).to include(:fitness, :error_rate, :avg_latency_ms, :rank)
178
+ end
179
+ end
180
+
181
+ # ─── declare_winner ─────────────────────────────────────────────────────
182
+
183
+ describe '.declare_winner' do
184
+ let(:competition_id) { create_and_return_id }
185
+
186
+ before do
187
+ ext_a = build_extension(name: 'champion', invocation_count: 1000, impact_score: 0.9, health_score: 1.0)
188
+ ext_b = build_extension(name: 'challenger', invocation_count: 10, impact_score: 0.1, health_score: 0.3)
189
+ evolver.run_trial(competition_id: competition_id, extension: ext_a)
190
+ evolver.run_trial(competition_id: competition_id, extension: ext_b)
191
+ end
192
+
193
+ it 'returns success: true with the winner' do
194
+ result = evolver.declare_winner(competition_id: competition_id)
195
+ expect(result[:success]).to be true
196
+ expect(result[:winner]).to eq('champion')
197
+ end
198
+
199
+ it 'returns the losers list' do
200
+ result = evolver.declare_winner(competition_id: competition_id)
201
+ expect(result[:losers]).to contain_exactly('challenger')
202
+ end
203
+
204
+ it 'transitions competition to :decided' do
205
+ evolver.declare_winner(competition_id: competition_id)
206
+ status = evolver.competition_status(competition_id: competition_id)
207
+ expect(status[:status]).to eq(:decided)
208
+ end
209
+
210
+ it 'sets the winner on the competition' do
211
+ evolver.declare_winner(competition_id: competition_id)
212
+ status = evolver.competition_status(competition_id: competition_id)
213
+ expect(status[:winner]).to eq('champion')
214
+ end
215
+
216
+ it 'calls Evolver.replace_extension for each loser' do
217
+ expect(Legion::Extensions::MindGrowth::Runners::Evolver).to receive(:replace_extension)
218
+ .with(old_name: 'challenger', new_proposal_id: 'winner:champion')
219
+ .and_return({ success: true, replaced: 'challenger' })
220
+ evolver.declare_winner(competition_id: competition_id)
221
+ end
222
+
223
+ it 'returns failure for nonexistent competition' do
224
+ result = evolver.declare_winner(competition_id: 'bogus')
225
+ expect(result[:success]).to be false
226
+ expect(result[:reason]).to eq(:not_found)
227
+ end
228
+
229
+ it 'returns failure when already decided' do
230
+ evolver.declare_winner(competition_id: competition_id)
231
+ result = evolver.declare_winner(competition_id: competition_id)
232
+ expect(result[:success]).to be false
233
+ expect(result[:reason]).to eq(:already_decided)
234
+ end
235
+
236
+ it 'returns failure when no trials exist' do
237
+ cid = create_and_return_id(proposal_ids: %w[x y])
238
+ result = evolver.declare_winner(competition_id: cid)
239
+ expect(result[:success]).to be false
240
+ expect(result[:reason]).to eq(:no_trials)
241
+ end
242
+ end
243
+
244
+ # ─── competition_status ─────────────────────────────────────────────────
245
+
246
+ describe '.competition_status' do
247
+ it 'returns competition details' do
248
+ cid = create_and_return_id(gap: 'prediction', proposal_ids: %w[a b c])
249
+ result = evolver.competition_status(competition_id: cid)
250
+ expect(result[:success]).to be true
251
+ expect(result[:gap]).to eq('prediction')
252
+ expect(result[:competitors]).to eq(%w[a b c])
253
+ expect(result[:trial_count]).to eq(0)
254
+ end
255
+
256
+ it 'returns failure for nonexistent competition' do
257
+ result = evolver.competition_status(competition_id: 'missing')
258
+ expect(result[:success]).to be false
259
+ end
260
+ end
261
+
262
+ # ─── active_competitions ────────────────────────────────────────────────
263
+
264
+ describe '.active_competitions' do
265
+ it 'returns empty list when no competitions exist' do
266
+ result = evolver.active_competitions
267
+ expect(result[:success]).to be true
268
+ expect(result[:count]).to eq(0)
269
+ end
270
+
271
+ it 'includes pending and active competitions' do
272
+ create_and_return_id(gap: 'gap1')
273
+ cid2 = create_and_return_id(gap: 'gap2')
274
+ evolver.run_trial(competition_id: cid2, extension: build_extension(name: 'e1'))
275
+
276
+ result = evolver.active_competitions
277
+ expect(result[:count]).to eq(2)
278
+ end
279
+
280
+ it 'excludes decided competitions' do
281
+ cid = create_and_return_id
282
+ evolver.run_trial(competition_id: cid, extension: build_extension(name: 'a', invocation_count: 1000))
283
+ evolver.run_trial(competition_id: cid, extension: build_extension(name: 'b', invocation_count: 1))
284
+ evolver.declare_winner(competition_id: cid)
285
+
286
+ result = evolver.active_competitions
287
+ expect(result[:count]).to eq(0)
288
+ end
289
+ end
290
+
291
+ # ─── competition_history ────────────────────────────────────────────────
292
+
293
+ describe '.competition_history' do
294
+ it 'returns empty list when no competitions exist' do
295
+ result = evolver.competition_history
296
+ expect(result[:success]).to be true
297
+ expect(result[:count]).to eq(0)
298
+ end
299
+
300
+ it 'returns competitions sorted by most recent first' do
301
+ create_and_return_id(gap: 'first')
302
+ _cid2 = create_and_return_id(gap: 'second')
303
+
304
+ result = evolver.competition_history
305
+ expect(result[:count]).to eq(2)
306
+ expect(result[:competitions].first[:gap]).to eq('second')
307
+ end
308
+
309
+ it 'respects the limit parameter' do
310
+ 3.times { |i| create_and_return_id(gap: "gap-#{i}") }
311
+ result = evolver.competition_history(limit: 2)
312
+ expect(result[:count]).to eq(2)
313
+ end
314
+
315
+ it 'includes winner and trial_count for decided competitions' do
316
+ cid = create_and_return_id
317
+ evolver.run_trial(competition_id: cid, extension: build_extension(name: 'w', invocation_count: 1000))
318
+ evolver.run_trial(competition_id: cid, extension: build_extension(name: 'l', invocation_count: 1))
319
+ evolver.declare_winner(competition_id: cid)
320
+
321
+ result = evolver.competition_history
322
+ entry = result[:competitions].find { |c| c[:id] == cid }
323
+ expect(entry[:winner]).to eq('w')
324
+ expect(entry[:trial_count]).to eq(2)
325
+ end
326
+ end
327
+
328
+ # ─── thread safety ─────────────────────────────────────────────────────
329
+
330
+ describe 'thread safety' do
331
+ it 'handles concurrent competition creation' do
332
+ threads = 10.times.map do |i|
333
+ Thread.new { evolver.create_competition(gap: "gap-#{i}", proposal_ids: %w[a b]) }
334
+ end
335
+ results = threads.map(&:value)
336
+ expect(results.count { |r| r[:success] }).to eq(10)
337
+ expect(evolver.competition_history[:count]).to eq(10)
338
+ end
339
+ end
340
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lex-mind-growth
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
@@ -144,6 +144,7 @@ files:
144
144
  - lib/legion/extensions/mind_growth/helpers/proposal_store.rb
145
145
  - lib/legion/extensions/mind_growth/runners/analyzer.rb
146
146
  - lib/legion/extensions/mind_growth/runners/builder.rb
147
+ - lib/legion/extensions/mind_growth/runners/competitive_evolver.rb
147
148
  - lib/legion/extensions/mind_growth/runners/composer.rb
148
149
  - lib/legion/extensions/mind_growth/runners/consensus_builder.rb
149
150
  - lib/legion/extensions/mind_growth/runners/dashboard.rb
@@ -171,6 +172,7 @@ files:
171
172
  - spec/legion/extensions/mind_growth/helpers/proposal_store_spec.rb
172
173
  - spec/legion/extensions/mind_growth/runners/analyzer_spec.rb
173
174
  - spec/legion/extensions/mind_growth/runners/builder_spec.rb
175
+ - spec/legion/extensions/mind_growth/runners/competitive_evolver_spec.rb
174
176
  - spec/legion/extensions/mind_growth/runners/composer_spec.rb
175
177
  - spec/legion/extensions/mind_growth/runners/consensus_builder_spec.rb
176
178
  - spec/legion/extensions/mind_growth/runners/dashboard_spec.rb