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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e521090b9c272a1969beb62170e55aab7ebdaefbb65a8e9d9cb36107217b73b6
|
|
4
|
+
data.tar.gz: f92b34127bb840cbb72e70247fb759d1c79a23d6f1690edbafcbe47cc53b1bd4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
86
|
+
# Delegates to lex-codegen FromGap when loaded; falls back to legion-llm; stubs otherwise
|
|
87
87
|
def implement_stage(proposal, base_path)
|
|
88
|
-
|
|
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
|
|
@@ -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)
|