lex-mind-growth 0.2.2 → 0.2.4

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: '09bcf8ac10095a5ec6dc77fc9d3ceffbcec8f7b207a218fc52fb8f3571f829ff'
4
- data.tar.gz: e9c0078ad42782ab0b9ee324c3a8cfb9d02a89ff7e6b5445009f36327f78abbb
3
+ metadata.gz: e521090b9c272a1969beb62170e55aab7ebdaefbb65a8e9d9cb36107217b73b6
4
+ data.tar.gz: f92b34127bb840cbb72e70247fb759d1c79a23d6f1690edbafcbe47cc53b1bd4
5
5
  SHA512:
6
- metadata.gz: 420a685ab1a80584957d3a30e4dc9a478c3e12e1f765f544c852e7f522a587dbb58f0fb22a7c70e2177e0ba3ba76785237b4bac086d49d80f261c06374593995
7
- data.tar.gz: 43abebd9a9da4f3f4d296d965fef963583768bb3adc40fbfaae5378e84937ba15b266580ec4c2a042ba2838559474040fe5d6a7cea0755f2926943b42ba1d992
6
+ metadata.gz: a7354413edb5d555fdee4d69706c3d086edeed67f668609d1f870f67d531ae49d5d6a2150b74152e57cd66786043698f00808109e5344908fd50bc4d55a17c34
7
+ data.tar.gz: fe1bbaa1c74eb7ee2fd81fe41924cf91177f45e3748d0f5702a8346602c256f94e5b0a652a5ee632f0b95756fa011531fcc637a043c9e1f220f20b6de9f2a9eb
@@ -83,9 +83,11 @@ module Legion
83
83
  end
84
84
 
85
85
  # --- Implement Stage ---
86
- # Delegates to legion-llm when loaded and started; stubs otherwise
86
+ # Delegates to lex-codegen FromGap when loaded; falls back to legion-llm; stubs otherwise
87
87
  def implement_stage(proposal, base_path)
88
- return { success: true, stage: :implement, message: 'implementation requires legion-llm' } unless llm_available?
88
+ unless llm_available? || codegen_from_gap_available?
89
+ return { success: true, stage: :implement, message: 'implementation requires legion-llm or lex-codegen' }
90
+ end
89
91
 
90
92
  path = ext_path(proposal, base_path)
91
93
  target_files = implementation_targets(path)
@@ -179,6 +181,28 @@ module Legion
179
181
  end
180
182
 
181
183
  def implement_file(file_path, proposal)
184
+ return legacy_implement_file(file_path, proposal) unless codegen_from_gap_available?
185
+
186
+ result = Legion::Extensions::Codegen::Runners::FromGap.implement_stub(
187
+ file_path: file_path,
188
+ context: proposal_context(proposal)
189
+ )
190
+ return { success: false, error: result[:error] || result[:reason] } unless result[:success]
191
+
192
+ if eval_available?
193
+ review = Legion::Extensions::Eval::Runners::CodeReview.review_generated(
194
+ code: result[:code], spec_code: nil, context: { source: :mind_growth }
195
+ )
196
+ return { success: false, error: "review #{review[:verdict]}: #{review[:issues]&.join(', ')}" } unless review[:passed]
197
+ end
198
+
199
+ ::File.write(file_path, result[:code])
200
+ { success: true, path: file_path }
201
+ rescue StandardError => e
202
+ { success: false, error: e.message }
203
+ end
204
+
205
+ def legacy_implement_file(file_path, proposal)
182
206
  stub_content = ::File.read(file_path)
183
207
 
184
208
  chat = Legion::LLM.chat(caller: { extension: 'lex-mind-growth', operation: 'build' }, intent: { capability: :reasoning })
@@ -192,6 +216,15 @@ module Legion
192
216
  { success: false, error: e.message }
193
217
  end
194
218
 
219
+ def proposal_context(proposal)
220
+ {
221
+ name: proposal.name,
222
+ category: proposal.category,
223
+ description: proposal.description,
224
+ metaphor: proposal.metaphor
225
+ }
226
+ end
227
+
195
228
  def implementation_instructions
196
229
  <<~INSTRUCTIONS
197
230
  You are a Ruby code generator for LegionIO cognitive extensions.
@@ -239,6 +272,16 @@ module Legion
239
272
  defined?(Legion::Extensions::Codegen::Runners::Generate)
240
273
  end
241
274
 
275
+ def codegen_from_gap_available?
276
+ defined?(Legion::Extensions::Codegen::Runners::FromGap) &&
277
+ Legion::Extensions::Codegen::Runners::FromGap.respond_to?(:implement_stub)
278
+ end
279
+
280
+ def eval_available?
281
+ defined?(Legion::Extensions::Eval::Runners::CodeReview) &&
282
+ Legion::Extensions::Eval::Runners::CodeReview.respond_to?(:review_generated)
283
+ end
284
+
242
285
  def exec_available?
243
286
  defined?(Legion::Extensions::Exec::Runners::Bundler)
244
287
  end
@@ -3,7 +3,7 @@
3
3
  module Legion
4
4
  module Extensions
5
5
  module MindGrowth
6
- VERSION = '0.2.2'
6
+ VERSION = '0.2.4'
7
7
  end
8
8
  end
9
9
  end
@@ -307,6 +307,158 @@ RSpec.describe Legion::Extensions::MindGrowth::Runners::Builder do
307
307
  end
308
308
  end
309
309
 
310
+ describe 'implement_stage with codegen FromGap' do
311
+ let(:ext_dir) { File.join(Dir.tmpdir, "lex-mind-growth-codegen-test-#{SecureRandom.hex(4)}") }
312
+ let(:generated_code) { "# frozen_string_literal: true\n\nmodule Impl; end\n" }
313
+
314
+ before do
315
+ from_gap_mod = Module.new do
316
+ def self.implement_stub(file_path:, context: nil) # rubocop:disable Lint/UnusedMethodArgument
317
+ { success: true, code: "# frozen_string_literal: true\n\nmodule Impl; end\n", file_path: file_path }
318
+ end
319
+ end
320
+ stub_const('Legion::Extensions::Codegen::Runners::FromGap', from_gap_mod)
321
+ stub_const('Legion::Extensions::Codegen::Runners::Generate', Module.new)
322
+ stub_const('Legion::Extensions::Codegen::Runners::Validate', Module.new)
323
+ allow(Legion::Extensions::Codegen::Runners::Generate).to receive(:scaffold_extension)
324
+ .and_return({ success: true, path: File.join(ext_dir, 'lex-buildable'), files_created: 12 })
325
+ allow(Legion::Extensions::Codegen::Runners::Validate).to receive(:validate_structure)
326
+ .and_return({ valid: true, missing: [], present: %w[Gemfile] })
327
+ allow(Legion::Extensions::Codegen::Runners::Validate).to receive(:validate_gemspec)
328
+ .and_return({ valid: true, issues: [] })
329
+
330
+ runner_dir = File.join(ext_dir, 'lex-buildable', 'lib', 'legion', 'extensions', 'buildable', 'runners')
331
+ FileUtils.mkdir_p(runner_dir)
332
+ File.write(File.join(runner_dir, 'example.rb'), "# stub\n")
333
+ end
334
+
335
+ after { FileUtils.rm_rf(ext_dir) }
336
+
337
+ it 'delegates to FromGap.implement_stub' do
338
+ allow(Legion::Extensions::Codegen::Runners::FromGap).to receive(:implement_stub)
339
+ .and_return({ success: true, code: generated_code, file_path: 'test.rb' })
340
+ builder.build_extension(proposal_id: proposal_id, base_path: ext_dir)
341
+ expect(Legion::Extensions::Codegen::Runners::FromGap).to have_received(:implement_stub)
342
+ .with(hash_including(file_path: a_string_ending_with('example.rb')))
343
+ end
344
+
345
+ it 'includes proposal context in call' do
346
+ allow(Legion::Extensions::Codegen::Runners::FromGap).to receive(:implement_stub)
347
+ .and_return({ success: true, code: generated_code, file_path: 'test.rb' })
348
+ builder.build_extension(proposal_id: proposal_id, base_path: ext_dir)
349
+ expect(Legion::Extensions::Codegen::Runners::FromGap).to have_received(:implement_stub)
350
+ .with(hash_including(context: hash_including(name: 'lex-buildable', category: :cognition,
351
+ description: 'a buildable proposal')))
352
+ end
353
+
354
+ it 'writes approved code to disk' do
355
+ builder.build_extension(proposal_id: proposal_id, base_path: ext_dir)
356
+ runner_path = File.join(ext_dir, 'lex-buildable', 'lib', 'legion', 'extensions', 'buildable', 'runners', 'example.rb')
357
+ expect(File.read(runner_path)).to include('module Impl')
358
+ end
359
+
360
+ it 'pipeline completes when FromGap succeeds' do
361
+ result = builder.build_extension(proposal_id: proposal_id, base_path: ext_dir)
362
+ expect(result[:pipeline][:stage]).to eq(:complete)
363
+ end
364
+
365
+ it 'pipeline fails when FromGap returns failure' do
366
+ allow(Legion::Extensions::Codegen::Runners::FromGap).to receive(:implement_stub)
367
+ .and_return({ success: false, reason: :llm_empty_response })
368
+ result = builder.build_extension(proposal_id: proposal_id, base_path: ext_dir)
369
+ expect(result[:pipeline][:errors]).not_to be_empty
370
+ end
371
+ end
372
+
373
+ describe 'implement_stage with codegen + eval' do
374
+ let(:ext_dir) { File.join(Dir.tmpdir, "lex-mind-growth-eval-test-#{SecureRandom.hex(4)}") }
375
+ let(:generated_code) { "# frozen_string_literal: true\n\nmodule Reviewed; end\n" }
376
+
377
+ before do
378
+ from_gap_mod = Module.new do
379
+ def self.implement_stub(file_path:, context: nil) # rubocop:disable Lint/UnusedMethodArgument
380
+ { success: true, code: "# frozen_string_literal: true\n\nmodule Reviewed; end\n", file_path: file_path }
381
+ end
382
+ end
383
+ stub_const('Legion::Extensions::Codegen::Runners::FromGap', from_gap_mod)
384
+ stub_const('Legion::Extensions::Codegen::Runners::Generate', Module.new)
385
+ stub_const('Legion::Extensions::Codegen::Runners::Validate', Module.new)
386
+ stub_const('Legion::Extensions::Eval::Runners::CodeReview', Module.new)
387
+ allow(Legion::Extensions::Codegen::Runners::Generate).to receive(:scaffold_extension)
388
+ .and_return({ success: true, path: File.join(ext_dir, 'lex-buildable'), files_created: 12 })
389
+ allow(Legion::Extensions::Codegen::Runners::Validate).to receive(:validate_structure)
390
+ .and_return({ valid: true, missing: [], present: %w[Gemfile] })
391
+ allow(Legion::Extensions::Codegen::Runners::Validate).to receive(:validate_gemspec)
392
+ .and_return({ valid: true, issues: [] })
393
+
394
+ runner_dir = File.join(ext_dir, 'lex-buildable', 'lib', 'legion', 'extensions', 'buildable', 'runners')
395
+ FileUtils.mkdir_p(runner_dir)
396
+ File.write(File.join(runner_dir, 'example.rb'), "# stub\n")
397
+ end
398
+
399
+ after { FileUtils.rm_rf(ext_dir) }
400
+
401
+ it 'validates code through CodeReview before writing' do
402
+ allow(Legion::Extensions::Eval::Runners::CodeReview).to receive(:review_generated)
403
+ .and_return({ passed: true, verdict: :approve })
404
+ builder.build_extension(proposal_id: proposal_id, base_path: ext_dir)
405
+ expect(Legion::Extensions::Eval::Runners::CodeReview).to have_received(:review_generated)
406
+ .with(hash_including(code: a_string_including('module Reviewed'), context: { source: :mind_growth }))
407
+ end
408
+
409
+ it 'writes when review approves' do
410
+ allow(Legion::Extensions::Eval::Runners::CodeReview).to receive(:review_generated)
411
+ .and_return({ passed: true, verdict: :approve })
412
+ result = builder.build_extension(proposal_id: proposal_id, base_path: ext_dir)
413
+ expect(result[:pipeline][:stage]).to eq(:complete)
414
+ runner_path = File.join(ext_dir, 'lex-buildable', 'lib', 'legion', 'extensions', 'buildable', 'runners', 'example.rb')
415
+ expect(File.read(runner_path)).to include('module Reviewed')
416
+ end
417
+
418
+ it 'does not write when review rejects' do
419
+ allow(Legion::Extensions::Eval::Runners::CodeReview).to receive(:review_generated)
420
+ .and_return({ passed: false, verdict: :reject, issues: ['unsafe eval'] })
421
+ result = builder.build_extension(proposal_id: proposal_id, base_path: ext_dir)
422
+ expect(result[:pipeline][:errors]).not_to be_empty
423
+ runner_path = File.join(ext_dir, 'lex-buildable', 'lib', 'legion', 'extensions', 'buildable', 'runners', 'example.rb')
424
+ expect(File.read(runner_path)).to eq("# stub\n")
425
+ end
426
+ end
427
+
428
+ describe 'implement_stage fallback (no FromGap)' do
429
+ let(:mock_chat) { double('RubyLLM::Chat') }
430
+ let(:mock_response) { double('RubyLLM::Message', content: "# frozen_string_literal: true\n\n{ success: true }\n") }
431
+ let(:ext_dir) { File.join(Dir.tmpdir, "lex-mind-growth-fallback-test-#{SecureRandom.hex(4)}") }
432
+
433
+ before do
434
+ llm_mod = Module.new do
435
+ def self.started? = true
436
+ def self.chat(**) = nil
437
+ end
438
+ stub_const('Legion::LLM', llm_mod)
439
+ allow(Legion::LLM).to receive(:chat).and_return(mock_chat)
440
+ allow(mock_chat).to receive(:with_instructions).and_return(mock_chat)
441
+ allow(mock_chat).to receive(:ask).and_return(mock_response)
442
+
443
+ runner_dir = File.join(ext_dir, 'lex-buildable', 'lib', 'legion', 'extensions', 'buildable', 'runners')
444
+ FileUtils.mkdir_p(runner_dir)
445
+ File.write(File.join(runner_dir, 'example.rb'), "# stub\n")
446
+ end
447
+
448
+ after { FileUtils.rm_rf(ext_dir) }
449
+
450
+ it 'falls back to legacy LLM implementation' do
451
+ builder.build_extension(proposal_id: proposal_id, base_path: ext_dir)
452
+ expect(mock_chat).to have_received(:ask).at_least(:once)
453
+ end
454
+
455
+ it 'writes LLM output to files via legacy path' do
456
+ builder.build_extension(proposal_id: proposal_id, base_path: ext_dir)
457
+ runner_path = File.join(ext_dir, 'lex-buildable', 'lib', 'legion', 'extensions', 'buildable', 'runners', 'example.rb')
458
+ expect(File.read(runner_path)).to include('success: true')
459
+ end
460
+ end
461
+
310
462
  describe 'full wired pipeline' do
311
463
  before do
312
464
  stub_const('Legion::Extensions::Codegen::Runners::Generate', Module.new)
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.2
4
+ version: 0.2.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity