lex-mind-growth 0.3.0 → 0.3.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: 3fcaa84f5b0e455b0ac70019228ebe5bffdbd1c69228aafcad9bf92d7f91bb52
4
- data.tar.gz: 4ea8b0c108d6bf0a957cd9709147e4cfaa3c78e0b297afc85e642331f9153e4a
3
+ metadata.gz: d9b428be29ef5df316acb3e4b9d6a01b826168046e1133e50c293e1ecc1a1186
4
+ data.tar.gz: 7a38b04f5d93dea41f294cb0011a9f74511cd24bfe9687af792edc1940dcfd03
5
5
  SHA512:
6
- metadata.gz: 911dc8e13b4dfe4cb4ee0cd5dd7cd27fc2d93a529aca350cc4d02d4f3f27b9548ac8881945bb57d150ffa8cc8e169e8f62b3590e2bbab4188dbfc0046aa69eb3
7
- data.tar.gz: d4f849194085b3b398dd41e3499b686c87b323ab4950a392eaca71133aeb943fcfe68557829a5cb6be92428bf781c90ab53d49cf877f26abe5b699562c25d07e
6
+ metadata.gz: 33078449b48cf7cae04ceabd91690ae4900f47bc89275aab55005a2f1cb28242e5ef0780ef6321166b09e7b89a79b34b34afbd2d293243807a64c366c5303323
7
+ data.tar.gz: 6d77136475fc7cde737949355135e60fa90891559f29f02ee71aa522e80d5be82cbbda09e41b16fc4293070eae6c9b0466acd564e1441700c326112905035ef6
@@ -254,11 +254,15 @@ module Legion
254
254
 
255
255
  def legacy_implement_file(file_path, proposal)
256
256
  stub_content = ::File.read(file_path)
257
+ prompt = file_implementation_prompt(stub_content, proposal)
257
258
 
258
- chat = Legion::LLM.chat(caller: { extension: 'lex-mind-growth', operation: 'build' }, intent: { capability: :reasoning }) # rubocop:disable Legion/HelperMigration/DirectLlm
259
- chat.with_instructions(implementation_instructions)
260
- response = chat.ask(file_implementation_prompt(stub_content, proposal))
261
- code = extract_ruby_code(response.content)
259
+ response = Legion::LLM.chat( # rubocop:disable Legion/HelperMigration/DirectLlm
260
+ message: prompt,
261
+ caller: { extension: 'lex-mind-growth', operation: 'build' },
262
+ intent: { capability: :reasoning }
263
+ )
264
+ content = implementation_content(response, prompt)
265
+ code = extract_ruby_code(content)
262
266
 
263
267
  ::File.write(file_path, code)
264
268
  { success: true, path: file_path }
@@ -306,7 +310,26 @@ module Legion
306
310
  parts.join("\n")
307
311
  end
308
312
 
313
+ def implementation_content(response, prompt)
314
+ return response.strip if response.is_a?(String)
315
+ return response.content if response.respond_to?(:content)
316
+
317
+ if response.respond_to?(:ask)
318
+ response.with_instructions(implementation_instructions) if response.respond_to?(:with_instructions)
319
+ asked = response.ask(prompt)
320
+ return implementation_content(asked, prompt)
321
+ end
322
+
323
+ return nil unless response.is_a?(Hash)
324
+
325
+ response[:content] || response['content'] ||
326
+ response.dig(:message, :content) || response.dig('message', 'content') ||
327
+ response[:response] || response['response']
328
+ end
329
+
309
330
  def extract_ruby_code(content)
331
+ return '' unless content
332
+
310
333
  code = if content.match?(/```ruby\s*\n/)
311
334
  content.match(/```ruby\s*\n(.*?)```/m)&.captures&.first || content
312
335
  elsif content.match?(/```\s*\n/)
@@ -129,18 +129,35 @@ module Legion
129
129
  def llm_suggestions(name, fitness, weaknesses)
130
130
  # rubocop:disable Legion/HelperMigration/DirectLlm
131
131
  response = Legion::LLM.chat(
132
- caller: {
132
+ message: improvement_prompt(name, fitness, weaknesses),
133
+ caller: {
133
134
  extension: 'lex-mind-growth',
134
135
  operation: 'evolver',
135
136
  phase: 'suggest'
136
137
  }
137
- ).ask(improvement_prompt(name, fitness, weaknesses))
138
+ )
138
139
  # rubocop:enable Legion/HelperMigration/DirectLlm
139
- parse_llm_suggestions(response.content)
140
+ parse_llm_suggestions(llm_content(response, improvement_prompt(name, fitness, weaknesses)))
140
141
  rescue StandardError => _e
141
142
  nil
142
143
  end
143
144
 
145
+ def llm_content(response, prompt)
146
+ return response.strip if response.is_a?(String)
147
+ return response.content if response.respond_to?(:content)
148
+
149
+ if response.respond_to?(:ask)
150
+ asked = response.ask(prompt)
151
+ return llm_content(asked, prompt)
152
+ end
153
+
154
+ return nil unless response.is_a?(Hash)
155
+
156
+ response[:content] || response['content'] ||
157
+ response.dig(:message, :content) || response.dig('message', 'content') ||
158
+ response[:response] || response['response']
159
+ end
160
+
144
161
  def improvement_prompt(name, fitness, weaknesses)
145
162
  <<~PROMPT
146
163
  The LegionIO cognitive extension "#{name}" has a fitness score of #{fitness.round(3)}.
@@ -153,12 +170,14 @@ module Legion
153
170
  end
154
171
 
155
172
  def parse_llm_suggestions(content)
173
+ return nil unless content
174
+
156
175
  cleaned = content.gsub(/```(?:json)?\s*\n?/, '').strip
157
176
  data = ::JSON.parse(cleaned)
158
177
  return nil unless data.is_a?(Array)
159
178
 
160
179
  data.map(&:to_s).reject(&:empty?)
161
- rescue ::JSON::ParserError => _e
180
+ rescue ::JSON::ParserError, NoMethodError => _e
162
181
  nil
163
182
  end
164
183
 
@@ -122,16 +122,15 @@ module Legion
122
122
  def enrich_proposal(name, category, description)
123
123
  return {} unless llm_available?
124
124
 
125
- # rubocop:disable Legion/HelperMigration/DirectLlm
126
- response = Legion::LLM.chat(
125
+ content = llm_chat_content(
126
+ enrichment_prompt(name, category, description),
127
127
  caller: {
128
128
  extension: 'lex-mind-growth',
129
129
  operation: 'propose',
130
130
  phase: 'capability'
131
131
  }
132
- ).ask(enrichment_prompt(name, category, description))
133
- # rubocop:enable Legion/HelperMigration/DirectLlm
134
- parse_enrichment(response.content)
132
+ )
133
+ parse_enrichment(content)
135
134
  rescue StandardError => e
136
135
  log.debug "[mind_growth:proposer] LLM enrichment failed: #{e.message}"
137
136
  {}
@@ -174,8 +173,11 @@ module Legion
174
173
  def score_with_llm(proposal)
175
174
  return nil unless llm_available?
176
175
 
177
- response = Legion::LLM.chat(caller: { extension: 'lex-mind-growth', operation: 'propose', phase: 'score' }).ask(scoring_prompt(proposal)) # rubocop:disable Legion/HelperMigration/DirectLlm
178
- parse_scores(response.content)
176
+ content = llm_chat_content(
177
+ scoring_prompt(proposal),
178
+ caller: { extension: 'lex-mind-growth', operation: 'propose', phase: 'score' }
179
+ )
180
+ parse_scores(content)
179
181
  rescue StandardError => e
180
182
  log.debug "[mind_growth:proposer] LLM scoring failed: #{e.message}"
181
183
  nil
@@ -237,16 +239,15 @@ module Legion
237
239
  return nil unless llm_available?
238
240
 
239
241
  candidates = existing.last(20).map { |p| { name: p.name, description: p.description } }
240
- # rubocop:disable Legion/HelperMigration/DirectLlm
241
- response = Legion::LLM.chat(
242
+ content = llm_chat_content(
243
+ redundancy_prompt(name, description, candidates),
242
244
  caller: {
243
245
  extension: 'lex-mind-growth',
244
246
  operation: 'propose',
245
247
  phase: 'validate'
246
248
  }
247
- ).ask(redundancy_prompt(name, description, candidates))
248
- # rubocop:enable Legion/HelperMigration/DirectLlm
249
- parse_redundancy(response.content)
249
+ )
250
+ parse_redundancy(content)
250
251
  rescue StandardError => e
251
252
  log.debug "[mind_growth:proposer] LLM redundancy check failed: #{e.message}"
252
253
  nil
@@ -286,6 +287,27 @@ module Legion
286
287
  rescue ::JSON::ParserError, NoMethodError => _e
287
288
  nil
288
289
  end
290
+
291
+ def llm_chat_content(prompt, caller:)
292
+ response = Legion::LLM.chat(message: prompt, caller: caller) # rubocop:disable Legion/HelperMigration/DirectLlm
293
+ extract_llm_content(response, prompt)
294
+ end
295
+
296
+ def extract_llm_content(response, prompt)
297
+ return response.strip if response.is_a?(String)
298
+ return response.content if response.respond_to?(:content)
299
+
300
+ if response.respond_to?(:ask)
301
+ asked = response.ask(prompt)
302
+ return extract_llm_content(asked, prompt)
303
+ end
304
+
305
+ return nil unless response.is_a?(Hash)
306
+
307
+ response[:content] || response['content'] ||
308
+ response.dig(:message, :content) || response.dig('message', 'content') ||
309
+ response[:response] || response['response']
310
+ end
289
311
  end
290
312
  end
291
313
  end
@@ -3,7 +3,7 @@
3
3
  module Legion
4
4
  module Extensions
5
5
  module MindGrowth
6
- VERSION = '0.3.0'
6
+ VERSION = '0.3.1'
7
7
  end
8
8
  end
9
9
  end
@@ -266,6 +266,15 @@ RSpec.describe Legion::Extensions::MindGrowth::Runners::Builder do
266
266
  expect(File.read(runner_path)).to include('# implemented')
267
267
  end
268
268
 
269
+ it 'handles native hash responses without requiring ask' do
270
+ allow(Legion::LLM).to receive(:chat).and_return({ content: "# frozen_string_literal: true\n\n# native\n" })
271
+
272
+ builder.build_extension(proposal_id: proposal_id, base_path: ext_dir)
273
+
274
+ runner_path = File.join(ext_dir, 'lex-buildable', 'lib', 'legion', 'extensions', 'buildable', 'runners', 'example.rb')
275
+ expect(File.read(runner_path)).to include('# native')
276
+ end
277
+
269
278
  it 'extracts code from markdown fences' do
270
279
  fenced = "Here's the code:\n```ruby\n# frozen_string_literal: true\n\nreal_code\n```\n"
271
280
  allow(mock_response).to receive(:content).and_return(fenced)
@@ -174,6 +174,15 @@ RSpec.describe Legion::Extensions::MindGrowth::Runners::Evolver do
174
174
  result = evolver.propose_improvement(extension: low_ext)
175
175
  expect(result[:suggestions]).to include('fix error handling')
176
176
  end
177
+
178
+ it 'handles native hash responses without requiring ask' do
179
+ allow(Legion::LLM).to receive(:started?).and_return(true)
180
+ allow(Legion::LLM).to receive(:chat).and_return({ content: '["reduce latency"]' })
181
+
182
+ result = evolver.propose_improvement(extension: low_ext)
183
+
184
+ expect(result[:suggestions]).to include('reduce latency')
185
+ end
177
186
  end
178
187
 
179
188
  it 'falls back to heuristic suggestions when LLM is unavailable' do
@@ -151,6 +151,14 @@ RSpec.describe Legion::Extensions::MindGrowth::Runners::Proposer do
151
151
  expect(result[:proposal][:rationale]).to eq('fills the working memory gap')
152
152
  end
153
153
 
154
+ it 'handles native hash responses without requiring ask' do
155
+ allow(Legion::LLM).to receive(:chat).and_return({ content: enrichment_json })
156
+
157
+ result = proposer.propose_concept(name: 'lex-native', category: :cognition, description: 'native')
158
+
159
+ expect(result[:proposal][:metaphor]).to eq('like a garden growing knowledge')
160
+ end
161
+
154
162
  it 'handles LLM errors gracefully' do
155
163
  allow(mock_chat).to receive(:ask).and_raise(StandardError, 'timeout')
156
164
  result = proposer.propose_concept(name: 'lex-fallback', category: :cognition, description: 'test')
@@ -241,6 +249,18 @@ RSpec.describe Legion::Extensions::MindGrowth::Runners::Proposer do
241
249
  expect(result[:success]).to be true
242
250
  end
243
251
 
252
+ it 'handles native redundancy hash responses without requiring ask' do
253
+ proposer.propose_concept(name: 'lex-native-base', category: :cognition, description: 'base', enrich: false)
254
+ allow(Legion::LLM).to receive(:chat).and_return(
255
+ { content: { redundant: true, similar_to: 'lex-native-base', score: 0.9 }.to_json }
256
+ )
257
+
258
+ result = proposer.propose_concept(name: 'lex-native-dup', category: :cognition, description: 'dup')
259
+
260
+ expect(result[:success]).to be false
261
+ expect(result[:error]).to eq(:redundant)
262
+ end
263
+
244
264
  it 'uses REDUNDANCY_THRESHOLD for the cutoff' do
245
265
  proposer.propose_concept(name: 'lex-base', category: :cognition, description: 'base extension', enrich: false)
246
266
  below_threshold = { redundant: false, similar_to: 'lex-base', score: 0.79 }.to_json
@@ -385,6 +405,14 @@ RSpec.describe Legion::Extensions::MindGrowth::Runners::Proposer do
385
405
  expect(result[:proposal][:scores][:fit]).to eq(0.75)
386
406
  end
387
407
 
408
+ it 'handles native score hash responses without requiring ask' do
409
+ allow(Legion::LLM).to receive(:chat).and_return({ content: score_json })
410
+
411
+ result = proposer.evaluate_proposal(proposal_id: proposal_id)
412
+
413
+ expect(result[:proposal][:scores][:novelty]).to eq(0.85)
414
+ end
415
+
388
416
  it 'approves when LLM scores are above threshold' do
389
417
  result = proposer.evaluate_proposal(proposal_id: proposal_id)
390
418
  expect(result[:approved]).to be true
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.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity