lex-mind-growth 0.1.0 → 0.1.5
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 +4 -4
- data/Gemfile +4 -5
- data/lex-mind-growth.gemspec +9 -1
- data/lib/legion/extensions/mind_growth/helpers/build_pipeline.rb +10 -0
- data/lib/legion/extensions/mind_growth/helpers/phase_allocator.rb +106 -0
- data/lib/legion/extensions/mind_growth/runners/builder.rb +5 -2
- data/lib/legion/extensions/mind_growth/runners/integration_tester.rb +116 -0
- data/lib/legion/extensions/mind_growth/runners/orchestrator.rb +4 -3
- data/lib/legion/extensions/mind_growth/runners/proposer.rb +15 -10
- data/lib/legion/extensions/mind_growth/runners/wirer.rb +145 -0
- data/lib/legion/extensions/mind_growth/version.rb +1 -1
- data/lib/legion/extensions/mind_growth.rb +3 -0
- data/spec/legion/extensions/mind_growth/helpers/build_pipeline_spec.rb +29 -0
- data/spec/legion/extensions/mind_growth/helpers/phase_allocator_spec.rb +323 -0
- data/spec/legion/extensions/mind_growth/runners/integration_tester_spec.rb +334 -0
- data/spec/legion/extensions/mind_growth/runners/wirer_spec.rb +526 -0
- data/spec/spec_helper.rb +29 -7
- metadata +107 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3749e6e3fd39af6abe591e437da33b0c159db29b6e3463bd96dfb46f17b65d47
|
|
4
|
+
data.tar.gz: c702f090f36a6279fcded74bc19fccfa16cf8149d549d2603d0d38ed8b706432
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5a70ad32a8ca26d122eae4d6e176d3d680f8943c08fa9c74379e3e03bbc99f1f7663b1b0abf6ecb5ef3d06f997107fd2c3d04d48ab32306f458a78419f92e39d
|
|
7
|
+
data.tar.gz: 9861dfcd32bad6bcb08b398be0a1a1fc3e3f564e2cb8d17a58cb30cdd43218d1ce1600c7d2f0af559f38c0ce5868f639e14fb59f8dc00fd6ac13b551770cb197
|
data/Gemfile
CHANGED
|
@@ -12,8 +12,7 @@ group :test do
|
|
|
12
12
|
gem 'simplecov'
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
gem 'legion-
|
|
16
|
-
gem '
|
|
17
|
-
gem 'lex-
|
|
18
|
-
|
|
19
|
-
gem 'legion-gaia', path: '../../legion-gaia'
|
|
15
|
+
gem 'legion-gaia', path: '../../legion-gaia' if File.directory?(File.expand_path('../../legion-gaia', __dir__))
|
|
16
|
+
gem 'legion-llm', path: '../../legion-llm', require: false if File.directory?(File.expand_path('../../legion-llm', __dir__))
|
|
17
|
+
gem 'lex-codegen', path: '../../extensions-core/lex-codegen', require: false if File.directory?(File.expand_path('../../extensions-core/lex-codegen', __dir__))
|
|
18
|
+
gem 'lex-exec', path: '../../extensions-core/lex-exec', require: false if File.directory?(File.expand_path('../../extensions-core/lex-exec', __dir__))
|
data/lex-mind-growth.gemspec
CHANGED
|
@@ -25,5 +25,13 @@ Gem::Specification.new do |spec|
|
|
|
25
25
|
Dir.glob('{lib,spec}/**/*') + %w[lex-mind-growth.gemspec Gemfile LICENSE]
|
|
26
26
|
end
|
|
27
27
|
spec.require_paths = ['lib']
|
|
28
|
-
spec.
|
|
28
|
+
spec.add_dependency 'legion-cache', '>= 1.3.11'
|
|
29
|
+
spec.add_dependency 'legion-crypt', '>= 1.4.9'
|
|
30
|
+
spec.add_dependency 'legion-data', '>= 1.4.17'
|
|
31
|
+
spec.add_dependency 'legion-json', '>= 1.2.1'
|
|
32
|
+
spec.add_dependency 'legion-logging', '>= 1.3.2'
|
|
33
|
+
spec.add_dependency 'legion-settings', '>= 1.3.14'
|
|
34
|
+
spec.add_dependency 'legion-transport', '>= 1.3.9'
|
|
35
|
+
|
|
36
|
+
spec.add_development_dependency 'legion-gaia', '>= 0.9.9'
|
|
29
37
|
end
|
|
@@ -21,6 +21,12 @@ module Legion
|
|
|
21
21
|
def advance!(result)
|
|
22
22
|
return if complete? || failed?
|
|
23
23
|
|
|
24
|
+
if timed_out?
|
|
25
|
+
@errors << { stage: @stage, error: 'build timeout exceeded', at: Time.now.utc }
|
|
26
|
+
@stage = :failed
|
|
27
|
+
return
|
|
28
|
+
end
|
|
29
|
+
|
|
24
30
|
if result[:success]
|
|
25
31
|
@artifacts[@stage] = result
|
|
26
32
|
next_idx = STAGES.index(@stage) + 1
|
|
@@ -40,6 +46,10 @@ module Legion
|
|
|
40
46
|
@stage == :failed
|
|
41
47
|
end
|
|
42
48
|
|
|
49
|
+
def timed_out?
|
|
50
|
+
duration_ms >= Helpers::Constants::BUILD_TIMEOUT_MS
|
|
51
|
+
end
|
|
52
|
+
|
|
43
53
|
def to_h
|
|
44
54
|
{
|
|
45
55
|
proposal_id: @proposal.id,
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module MindGrowth
|
|
6
|
+
module Helpers
|
|
7
|
+
module PhaseAllocator
|
|
8
|
+
# Maps cognitive categories to GAIA tick phases
|
|
9
|
+
CATEGORY_PHASE_MAP = {
|
|
10
|
+
perception: :sensory_processing,
|
|
11
|
+
attention: :sensory_processing,
|
|
12
|
+
emotion: :emotional_evaluation,
|
|
13
|
+
affect: :emotional_evaluation,
|
|
14
|
+
memory: :memory_retrieval,
|
|
15
|
+
knowledge: :knowledge_retrieval,
|
|
16
|
+
identity: :identity_entropy_check,
|
|
17
|
+
cognition: :working_memory_integration,
|
|
18
|
+
reasoning: :working_memory_integration,
|
|
19
|
+
safety: :procedural_check,
|
|
20
|
+
defense: :procedural_check,
|
|
21
|
+
prediction: :prediction_engine,
|
|
22
|
+
inference: :prediction_engine,
|
|
23
|
+
communication: :mesh_interface,
|
|
24
|
+
social: :mesh_interface,
|
|
25
|
+
coordination: :mesh_interface,
|
|
26
|
+
motivation: :action_selection,
|
|
27
|
+
executive: :action_selection,
|
|
28
|
+
learning: :memory_consolidation,
|
|
29
|
+
consolidation: :memory_consolidation,
|
|
30
|
+
introspection: :post_tick_reflection,
|
|
31
|
+
self: :post_tick_reflection,
|
|
32
|
+
reflection: :post_tick_reflection
|
|
33
|
+
}.freeze
|
|
34
|
+
|
|
35
|
+
# Dream cycle phase mappings
|
|
36
|
+
DREAM_PHASE_MAP = {
|
|
37
|
+
memory: :memory_audit,
|
|
38
|
+
association: :association_walk,
|
|
39
|
+
conflict: :contradiction_resolution,
|
|
40
|
+
curiosity: :agenda_formation,
|
|
41
|
+
consolidation: :consolidation_commit,
|
|
42
|
+
knowledge: :knowledge_promotion,
|
|
43
|
+
reflection: :dream_reflection,
|
|
44
|
+
narrative: :dream_narration
|
|
45
|
+
}.freeze
|
|
46
|
+
|
|
47
|
+
# Maps method name substrings to inferred GAIA phases for unknown categories
|
|
48
|
+
METHOD_INFERENCE_MAP = [
|
|
49
|
+
[%w[filter sense detect], :sensory_processing],
|
|
50
|
+
[%w[predict forecast estimate], :prediction_engine],
|
|
51
|
+
[%w[reflect evaluate assess], :post_tick_reflection],
|
|
52
|
+
[%w[store retrieve recall], :memory_retrieval],
|
|
53
|
+
[%w[decide select choose], :action_selection]
|
|
54
|
+
].freeze
|
|
55
|
+
|
|
56
|
+
module_function
|
|
57
|
+
|
|
58
|
+
def allocate_phase(category:, runner_methods: [])
|
|
59
|
+
category_sym = category.to_s.downcase.to_sym
|
|
60
|
+
|
|
61
|
+
# Check active phases first
|
|
62
|
+
phase = CATEGORY_PHASE_MAP[category_sym]
|
|
63
|
+
return { phase: phase, cycle: :active, confidence: :high } if phase
|
|
64
|
+
|
|
65
|
+
# Try to infer from runner method names
|
|
66
|
+
inferred = infer_from_methods(runner_methods)
|
|
67
|
+
return inferred if inferred
|
|
68
|
+
|
|
69
|
+
# Default to working_memory_integration (safest catch-all)
|
|
70
|
+
{ phase: :working_memory_integration, cycle: :active, confidence: :low }
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def allocate_dream_phase(category:)
|
|
74
|
+
category_sym = category.to_s.downcase.to_sym
|
|
75
|
+
phase = DREAM_PHASE_MAP[category_sym]
|
|
76
|
+
return { phase: phase, cycle: :dream, confidence: :high } if phase
|
|
77
|
+
|
|
78
|
+
nil
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def infer_from_methods(methods)
|
|
82
|
+
method_names = methods.map(&:to_s)
|
|
83
|
+
match = METHOD_INFERENCE_MAP.find do |keywords, _phase|
|
|
84
|
+
method_names.any? { |m| keywords.any? { |kw| m.include?(kw) } }
|
|
85
|
+
end
|
|
86
|
+
return nil unless match
|
|
87
|
+
|
|
88
|
+
{ phase: match[1], cycle: :active, confidence: :medium }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def valid_phase?(phase)
|
|
92
|
+
CATEGORY_PHASE_MAP.values.include?(phase) || DREAM_PHASE_MAP.values.include?(phase)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def phases_for_category(category)
|
|
96
|
+
category_sym = category.to_s.downcase.to_sym
|
|
97
|
+
results = []
|
|
98
|
+
results << CATEGORY_PHASE_MAP[category_sym] if CATEGORY_PHASE_MAP.key?(category_sym)
|
|
99
|
+
results << DREAM_PHASE_MAP[category_sym] if DREAM_PHASE_MAP.key?(category_sym)
|
|
100
|
+
results.compact
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -5,6 +5,9 @@ module Legion
|
|
|
5
5
|
module MindGrowth
|
|
6
6
|
module Runners
|
|
7
7
|
module Builder
|
|
8
|
+
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
|
|
9
|
+
Legion::Extensions::Helpers.const_defined?(:Lex)
|
|
10
|
+
|
|
8
11
|
extend self
|
|
9
12
|
|
|
10
13
|
def build_extension(proposal_id:, base_path: nil, **)
|
|
@@ -22,7 +25,7 @@ module Legion
|
|
|
22
25
|
run_stage(pipeline, :register, -> { register_stage(proposal) }) unless pipeline.failed?
|
|
23
26
|
|
|
24
27
|
proposal.transition!(pipeline.complete? ? :passing : :build_failed)
|
|
25
|
-
|
|
28
|
+
log.info "[mind_growth:builder] #{proposal.name}: #{pipeline.stage}"
|
|
26
29
|
{ success: pipeline.complete?, pipeline: pipeline.to_h, proposal: proposal.to_h }
|
|
27
30
|
rescue ArgumentError => e
|
|
28
31
|
{ success: false, error: e.message }
|
|
@@ -178,7 +181,7 @@ module Legion
|
|
|
178
181
|
def implement_file(file_path, proposal)
|
|
179
182
|
stub_content = ::File.read(file_path)
|
|
180
183
|
|
|
181
|
-
chat = Legion::LLM.chat
|
|
184
|
+
chat = Legion::LLM.chat(caller: { extension: 'lex-mind-growth', operation: 'build' }, intent: { capability: :reasoning })
|
|
182
185
|
chat.with_instructions(implementation_instructions)
|
|
183
186
|
response = chat.ask(file_implementation_prompt(stub_content, proposal))
|
|
184
187
|
code = extract_ruby_code(response.content)
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module MindGrowth
|
|
6
|
+
module Runners
|
|
7
|
+
module IntegrationTester
|
|
8
|
+
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
|
|
9
|
+
Legion::Extensions::Helpers.const_defined?(:Lex)
|
|
10
|
+
|
|
11
|
+
extend self
|
|
12
|
+
|
|
13
|
+
TICK_BUDGET_MS = 5000
|
|
14
|
+
|
|
15
|
+
def test_extension_in_tick(ext_module:, runner_module:, fn:, phase:, test_args: {}, **) # rubocop:disable Naming/MethodParameterName
|
|
16
|
+
return { success: false, reason: :gaia_not_available } unless gaia_available?
|
|
17
|
+
|
|
18
|
+
runner_class = resolve_runner_class(ext_module, runner_module)
|
|
19
|
+
return { success: false, reason: :runner_not_found } unless runner_class
|
|
20
|
+
|
|
21
|
+
# Test 1: Method exists and is callable
|
|
22
|
+
method_check = test_method_callable(runner_class, fn)
|
|
23
|
+
return method_check unless method_check[:success]
|
|
24
|
+
|
|
25
|
+
# Test 2: Method returns valid response hash
|
|
26
|
+
response_check = test_valid_response(runner_class, fn, test_args)
|
|
27
|
+
return response_check unless response_check[:success]
|
|
28
|
+
|
|
29
|
+
# Test 3: Method completes within budget
|
|
30
|
+
perf_check = test_performance(runner_class, fn, test_args)
|
|
31
|
+
|
|
32
|
+
{
|
|
33
|
+
success: true,
|
|
34
|
+
method_callable: method_check[:success],
|
|
35
|
+
valid_response: response_check[:success],
|
|
36
|
+
performance: perf_check,
|
|
37
|
+
phase: phase
|
|
38
|
+
}
|
|
39
|
+
rescue StandardError => e
|
|
40
|
+
{ success: false, reason: :exception, error: e.message }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def benchmark_tick(with_extension: nil, iterations: 5, **)
|
|
44
|
+
return { success: false, reason: :gaia_not_available } unless gaia_available?
|
|
45
|
+
|
|
46
|
+
timings = iterations.times.map do
|
|
47
|
+
start = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
|
48
|
+
Legion::Gaia.heartbeat if Legion::Gaia.respond_to?(:heartbeat)
|
|
49
|
+
finish = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
|
50
|
+
((finish - start) * 1000).round(2)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
{
|
|
54
|
+
success: true,
|
|
55
|
+
with_extension: with_extension,
|
|
56
|
+
iterations: iterations,
|
|
57
|
+
avg_ms: (timings.sum / timings.size).round(2),
|
|
58
|
+
max_ms: timings.max,
|
|
59
|
+
min_ms: timings.min,
|
|
60
|
+
within_budget: timings.max <= TICK_BUDGET_MS
|
|
61
|
+
}
|
|
62
|
+
rescue StandardError => e
|
|
63
|
+
{ success: false, reason: :benchmark_failed, error: e.message }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
def gaia_available?
|
|
69
|
+
defined?(Legion::Gaia)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def resolve_runner_class(ext_module, runner_module)
|
|
73
|
+
return nil unless defined?(Legion::Gaia::PhaseWiring)
|
|
74
|
+
|
|
75
|
+
Legion::Gaia::PhaseWiring.resolve_runner_class(ext_module.to_sym, runner_module.to_sym)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def test_method_callable(runner_class, fn) # rubocop:disable Naming/MethodParameterName
|
|
79
|
+
if runner_class.method_defined?(fn) || runner_class.respond_to?(fn)
|
|
80
|
+
{ success: true, method: fn }
|
|
81
|
+
else
|
|
82
|
+
{ success: false, reason: :method_not_defined, method: fn }
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def test_valid_response(runner_class, fn, args) # rubocop:disable Naming/MethodParameterName
|
|
87
|
+
host = Object.new.extend(runner_class)
|
|
88
|
+
result = host.send(fn, **args)
|
|
89
|
+
|
|
90
|
+
if result.is_a?(Hash)
|
|
91
|
+
{ success: true, response_type: :hash, keys: result.keys }
|
|
92
|
+
elsif result.nil?
|
|
93
|
+
{ success: true, response_type: :nil }
|
|
94
|
+
else
|
|
95
|
+
{ success: true, response_type: result.class.name }
|
|
96
|
+
end
|
|
97
|
+
rescue StandardError => e
|
|
98
|
+
{ success: false, reason: :invocation_error, error: e.message }
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def test_performance(runner_class, fn, args) # rubocop:disable Naming/MethodParameterName
|
|
102
|
+
host = Object.new.extend(runner_class)
|
|
103
|
+
start = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
|
104
|
+
host.send(fn, **args)
|
|
105
|
+
finish = ::Process.clock_gettime(::Process::CLOCK_MONOTONIC)
|
|
106
|
+
duration_ms = ((finish - start) * 1000).round(2)
|
|
107
|
+
|
|
108
|
+
{ duration_ms: duration_ms, within_budget: duration_ms <= TICK_BUDGET_MS }
|
|
109
|
+
rescue StandardError => e
|
|
110
|
+
{ duration_ms: nil, error: e.message, within_budget: false }
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
@@ -5,6 +5,9 @@ module Legion
|
|
|
5
5
|
module MindGrowth
|
|
6
6
|
module Runners
|
|
7
7
|
module Orchestrator
|
|
8
|
+
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
|
|
9
|
+
Legion::Extensions::Helpers.const_defined?(:Lex)
|
|
10
|
+
|
|
8
11
|
extend self
|
|
9
12
|
|
|
10
13
|
def run_growth_cycle(existing_extensions: nil, base_path: nil, max_proposals: 3, force: false, **)
|
|
@@ -165,11 +168,9 @@ module Legion
|
|
|
165
168
|
end
|
|
166
169
|
|
|
167
170
|
def log_cycle_summary(trace)
|
|
168
|
-
return unless defined?(Legion::Logging)
|
|
169
|
-
|
|
170
171
|
build_step = trace[:steps].find { |s| s[:step] == :build }
|
|
171
172
|
succeeded = build_step ? build_step[:succeeded] : 0
|
|
172
|
-
|
|
173
|
+
log.info "[mind_growth:orchestrator] cycle complete: #{succeeded} extensions built"
|
|
173
174
|
end
|
|
174
175
|
end
|
|
175
176
|
end
|
|
@@ -7,13 +7,16 @@ module Legion
|
|
|
7
7
|
module MindGrowth
|
|
8
8
|
module Runners
|
|
9
9
|
module Proposer
|
|
10
|
+
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
|
|
11
|
+
Legion::Extensions::Helpers.const_defined?(:Lex)
|
|
12
|
+
|
|
10
13
|
extend self
|
|
11
14
|
|
|
12
15
|
def analyze_gaps(existing_extensions: nil, **)
|
|
13
16
|
extensions = existing_extensions || current_extensions
|
|
14
17
|
analysis = Helpers::CognitiveModels.gap_analysis(extensions)
|
|
15
18
|
recommendations = Helpers::CognitiveModels.recommend_from_gaps(analysis)
|
|
16
|
-
|
|
19
|
+
log.debug "[mind_growth:proposer] gap analysis: #{recommendations.size} recommendations"
|
|
17
20
|
{ success: true, models: analysis, recommendations: recommendations.first(10) }
|
|
18
21
|
rescue ArgumentError => e
|
|
19
22
|
{ success: false, error: e.message }
|
|
@@ -27,7 +30,7 @@ module Legion
|
|
|
27
30
|
|
|
28
31
|
redundancy = check_redundancy(gem_name, desc)
|
|
29
32
|
if redundancy[:redundant]
|
|
30
|
-
|
|
33
|
+
log.info "[mind_growth:proposer] rejected redundant: #{gem_name} (#{redundancy[:score]})"
|
|
31
34
|
return { success: false, error: :redundant, similar_to: redundancy[:similar_to],
|
|
32
35
|
score: redundancy[:score] }
|
|
33
36
|
end
|
|
@@ -46,7 +49,7 @@ module Legion
|
|
|
46
49
|
origin: :manual
|
|
47
50
|
)
|
|
48
51
|
proposal_store.store(proposal)
|
|
49
|
-
|
|
52
|
+
log.info "[mind_growth:proposer] proposed: #{proposal.name} (#{cat})"
|
|
50
53
|
{ success: true, proposal: proposal.to_h }
|
|
51
54
|
rescue ArgumentError => e
|
|
52
55
|
{ success: false, error: e.message }
|
|
@@ -58,7 +61,7 @@ module Legion
|
|
|
58
61
|
|
|
59
62
|
eval_scores = scores || score_with_llm(proposal) || default_scores
|
|
60
63
|
proposal.evaluate!(eval_scores)
|
|
61
|
-
|
|
64
|
+
log.info "[mind_growth:proposer] evaluated #{proposal.name}: #{proposal.status}"
|
|
62
65
|
{ success: true, proposal: proposal.to_h, approved: proposal.status == :approved,
|
|
63
66
|
auto_approved: proposal.auto_approvable? }
|
|
64
67
|
rescue ArgumentError => e
|
|
@@ -114,10 +117,11 @@ module Legion
|
|
|
114
117
|
def enrich_proposal(name, category, description)
|
|
115
118
|
return {} unless llm_available?
|
|
116
119
|
|
|
117
|
-
response = Legion::LLM.chat
|
|
120
|
+
response = Legion::LLM.chat(caller: { extension: 'lex-mind-growth', operation: 'propose',
|
|
121
|
+
phase: 'capability' }).ask(enrichment_prompt(name, category, description))
|
|
118
122
|
parse_enrichment(response.content)
|
|
119
123
|
rescue StandardError => e
|
|
120
|
-
|
|
124
|
+
log.debug "[mind_growth:proposer] LLM enrichment failed: #{e.message}"
|
|
121
125
|
{}
|
|
122
126
|
end
|
|
123
127
|
|
|
@@ -158,10 +162,10 @@ module Legion
|
|
|
158
162
|
def score_with_llm(proposal)
|
|
159
163
|
return nil unless llm_available?
|
|
160
164
|
|
|
161
|
-
response = Legion::LLM.chat.ask(scoring_prompt(proposal))
|
|
165
|
+
response = Legion::LLM.chat(caller: { extension: 'lex-mind-growth', operation: 'propose', phase: 'score' }).ask(scoring_prompt(proposal))
|
|
162
166
|
parse_scores(response.content)
|
|
163
167
|
rescue StandardError => e
|
|
164
|
-
|
|
168
|
+
log.debug "[mind_growth:proposer] LLM scoring failed: #{e.message}"
|
|
165
169
|
nil
|
|
166
170
|
end
|
|
167
171
|
|
|
@@ -221,10 +225,11 @@ module Legion
|
|
|
221
225
|
return nil unless llm_available?
|
|
222
226
|
|
|
223
227
|
candidates = existing.last(20).map { |p| { name: p.name, description: p.description } }
|
|
224
|
-
response = Legion::LLM.chat
|
|
228
|
+
response = Legion::LLM.chat(caller: { extension: 'lex-mind-growth', operation: 'propose',
|
|
229
|
+
phase: 'validate' }).ask(redundancy_prompt(name, description, candidates))
|
|
225
230
|
parse_redundancy(response.content)
|
|
226
231
|
rescue StandardError => e
|
|
227
|
-
|
|
232
|
+
log.debug "[mind_growth:proposer] LLM redundancy check failed: #{e.message}"
|
|
228
233
|
nil
|
|
229
234
|
end
|
|
230
235
|
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Legion
|
|
4
|
+
module Extensions
|
|
5
|
+
module MindGrowth
|
|
6
|
+
module Runners
|
|
7
|
+
module Wirer
|
|
8
|
+
include Legion::Extensions::Helpers::Lex if Legion::Extensions.const_defined?(:Helpers) &&
|
|
9
|
+
Legion::Extensions::Helpers.const_defined?(:Lex)
|
|
10
|
+
|
|
11
|
+
extend self
|
|
12
|
+
|
|
13
|
+
def analyze_fit(extension_name:, category:, runner_module: nil, runner_methods: [], **) # rubocop:disable Lint/UnusedMethodArgument
|
|
14
|
+
allocation = Helpers::PhaseAllocator.allocate_phase(
|
|
15
|
+
category: category,
|
|
16
|
+
runner_methods: runner_methods
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
dream_allocation = Helpers::PhaseAllocator.allocate_dream_phase(category: category)
|
|
20
|
+
|
|
21
|
+
{
|
|
22
|
+
success: true,
|
|
23
|
+
extension: extension_name,
|
|
24
|
+
active_phase: allocation,
|
|
25
|
+
dream_phase: dream_allocation,
|
|
26
|
+
recommendation: build_recommendation(allocation, dream_allocation)
|
|
27
|
+
}
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def wire_extension(extension_name:, ext_module:, runner_module:, fn:, phase:, **) # rubocop:disable Naming/MethodParameterName
|
|
31
|
+
return { success: false, reason: :gaia_not_available } unless gaia_available?
|
|
32
|
+
return { success: false, reason: :invalid_phase } unless Helpers::PhaseAllocator.valid_phase?(phase)
|
|
33
|
+
|
|
34
|
+
# Verify the runner class exists and has the method
|
|
35
|
+
runner_class = resolve_runner(ext_module: ext_module, runner_module: runner_module)
|
|
36
|
+
return { success: false, reason: :runner_not_found } unless runner_class
|
|
37
|
+
|
|
38
|
+
return { success: false, reason: :method_not_found, method: fn } unless runner_class.method_defined?(fn) || runner_class.respond_to?(fn)
|
|
39
|
+
|
|
40
|
+
# Record the wiring in our registry
|
|
41
|
+
wiring_registry[extension_name] = {
|
|
42
|
+
ext_module: ext_module,
|
|
43
|
+
runner_module: runner_module,
|
|
44
|
+
fn: fn,
|
|
45
|
+
phase: phase,
|
|
46
|
+
wired_at: Time.now,
|
|
47
|
+
enabled: true
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
# Trigger GAIA rediscovery to pick up the new wiring
|
|
51
|
+
rediscover_result = trigger_rediscovery
|
|
52
|
+
|
|
53
|
+
{
|
|
54
|
+
success: true,
|
|
55
|
+
extension: extension_name,
|
|
56
|
+
phase: phase,
|
|
57
|
+
fn: fn,
|
|
58
|
+
rediscovery: rediscover_result
|
|
59
|
+
}
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def unwire_extension(extension_name:, **)
|
|
63
|
+
entry = wiring_registry.delete(extension_name)
|
|
64
|
+
return { success: false, reason: :not_wired } unless entry
|
|
65
|
+
|
|
66
|
+
trigger_rediscovery
|
|
67
|
+
|
|
68
|
+
{ success: true, extension: extension_name, unwired_phase: entry[:phase] }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def disable_extension(extension_name:, **)
|
|
72
|
+
entry = wiring_registry[extension_name]
|
|
73
|
+
return { success: false, reason: :not_wired } unless entry
|
|
74
|
+
|
|
75
|
+
entry[:enabled] = false
|
|
76
|
+
{ success: true, extension: extension_name, status: :disabled }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def enable_extension(extension_name:, **)
|
|
80
|
+
entry = wiring_registry[extension_name]
|
|
81
|
+
return { success: false, reason: :not_wired } unless entry
|
|
82
|
+
|
|
83
|
+
entry[:enabled] = true
|
|
84
|
+
trigger_rediscovery
|
|
85
|
+
{ success: true, extension: extension_name, status: :enabled }
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def wiring_status(**)
|
|
89
|
+
{
|
|
90
|
+
success: true,
|
|
91
|
+
wired_count: wiring_registry.size,
|
|
92
|
+
enabled_count: wiring_registry.count { |_, v| v[:enabled] },
|
|
93
|
+
extensions: wiring_registry.transform_values { |v| v.slice(:phase, :fn, :enabled, :wired_at) }
|
|
94
|
+
}
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def rewire_all(**)
|
|
98
|
+
return { success: false, reason: :gaia_not_available } unless gaia_available?
|
|
99
|
+
|
|
100
|
+
result = trigger_rediscovery
|
|
101
|
+
{ success: true, rediscovery: result }
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
private
|
|
105
|
+
|
|
106
|
+
def gaia_available?
|
|
107
|
+
defined?(Legion::Gaia) && Legion::Gaia.respond_to?(:registry)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def resolve_runner(ext_module:, runner_module:)
|
|
111
|
+
return nil unless defined?(Legion::Gaia::PhaseWiring)
|
|
112
|
+
|
|
113
|
+
Legion::Gaia::PhaseWiring.resolve_runner_class(ext_module.to_sym, runner_module.to_sym)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def trigger_rediscovery
|
|
117
|
+
return { rediscovered: false, reason: :gaia_not_available } unless gaia_available?
|
|
118
|
+
|
|
119
|
+
Legion::Gaia.registry.rediscover
|
|
120
|
+
rescue StandardError => e
|
|
121
|
+
{ rediscovered: false, error: e.message }
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def wiring_registry
|
|
125
|
+
@wiring_registry ||= {}
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def build_recommendation(active_alloc, dream_alloc)
|
|
129
|
+
parts = []
|
|
130
|
+
parts << case active_alloc[:confidence]
|
|
131
|
+
when :high
|
|
132
|
+
"Wire to #{active_alloc[:phase]} (high confidence)"
|
|
133
|
+
when :medium
|
|
134
|
+
"Suggest #{active_alloc[:phase]} (medium confidence, verify manually)"
|
|
135
|
+
else
|
|
136
|
+
"Default to #{active_alloc[:phase]} (low confidence, manual review recommended)"
|
|
137
|
+
end
|
|
138
|
+
parts << "Also wire dream phase: #{dream_alloc[:phase]}" if dream_alloc
|
|
139
|
+
parts.join('. ')
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|
|
@@ -9,11 +9,14 @@ require 'legion/extensions/mind_growth/helpers/proposal_store'
|
|
|
9
9
|
require 'legion/extensions/mind_growth/helpers/cognitive_models'
|
|
10
10
|
require 'legion/extensions/mind_growth/helpers/build_pipeline'
|
|
11
11
|
require 'legion/extensions/mind_growth/helpers/fitness_evaluator'
|
|
12
|
+
require 'legion/extensions/mind_growth/helpers/phase_allocator'
|
|
12
13
|
require 'legion/extensions/mind_growth/runners/proposer'
|
|
13
14
|
require 'legion/extensions/mind_growth/runners/analyzer'
|
|
14
15
|
require 'legion/extensions/mind_growth/runners/builder'
|
|
15
16
|
require 'legion/extensions/mind_growth/runners/validator'
|
|
16
17
|
require 'legion/extensions/mind_growth/runners/orchestrator'
|
|
18
|
+
require 'legion/extensions/mind_growth/runners/wirer'
|
|
19
|
+
require 'legion/extensions/mind_growth/runners/integration_tester'
|
|
17
20
|
require 'legion/extensions/mind_growth/client'
|
|
18
21
|
|
|
19
22
|
module Legion
|
|
@@ -118,6 +118,35 @@ RSpec.describe Legion::Extensions::MindGrowth::Helpers::BuildPipeline do
|
|
|
118
118
|
end
|
|
119
119
|
end
|
|
120
120
|
|
|
121
|
+
describe '#timed_out?' do
|
|
122
|
+
it 'returns false when within time budget' do
|
|
123
|
+
expect(pipeline.timed_out?).to be false
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
it 'returns true when duration exceeds BUILD_TIMEOUT_MS' do
|
|
127
|
+
pipeline.instance_variable_set(:@started_at, Time.now.utc - 700)
|
|
128
|
+
expect(pipeline.timed_out?).to be true
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
it 'transitions to failed on advance! when timed out' do
|
|
132
|
+
pipeline.instance_variable_set(:@started_at, Time.now.utc - 700)
|
|
133
|
+
pipeline.advance!({ success: true })
|
|
134
|
+
expect(pipeline.stage).to eq(:failed)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
it 'records timeout error message' do
|
|
138
|
+
pipeline.instance_variable_set(:@started_at, Time.now.utc - 700)
|
|
139
|
+
pipeline.advance!({ success: true })
|
|
140
|
+
expect(pipeline.errors.last[:error]).to eq('build timeout exceeded')
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
it 'records the stage where timeout occurred' do
|
|
144
|
+
pipeline.instance_variable_set(:@started_at, Time.now.utc - 700)
|
|
145
|
+
pipeline.advance!({ success: true })
|
|
146
|
+
expect(pipeline.errors.last[:stage]).to eq(:scaffold)
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
121
150
|
describe '#complete?' do
|
|
122
151
|
it 'returns false initially' do
|
|
123
152
|
expect(pipeline.complete?).to be false
|