lex-mind-growth 0.1.5 → 0.1.7

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: 3749e6e3fd39af6abe591e437da33b0c159db29b6e3463bd96dfb46f17b65d47
4
- data.tar.gz: c702f090f36a6279fcded74bc19fccfa16cf8149d549d2603d0d38ed8b706432
3
+ metadata.gz: 2182f87f0f59f727c61f209083c225b1b31d9cbb5589af437618350433e8b692
4
+ data.tar.gz: 1ee0428c83686efdb168054b16d68af66443dfa60bd5f0bd8e2725f4d7d87ab4
5
5
  SHA512:
6
- metadata.gz: 5a70ad32a8ca26d122eae4d6e176d3d680f8943c08fa9c74379e3e03bbc99f1f7663b1b0abf6ecb5ef3d06f997107fd2c3d04d48ab32306f458a78419f92e39d
7
- data.tar.gz: 9861dfcd32bad6bcb08b398be0a1a1fc3e3f564e2cb8d17a58cb30cdd43218d1ce1600c7d2f0af559f38c0ce5868f639e14fb59f8dc00fd6ac13b551770cb197
6
+ metadata.gz: 1e7320c15ecd31d531bd961b03f20baa8c30a77946f98e58c25557ebcd1027b42b7c9af7c9ce8ae781089e25966a76d183209922ce8b2e7a780c141853cf6d96
7
+ data.tar.gz: b7abcbc0ea3109d64a4cc1cda9508ea0ee8ab777271e205abc1ae4903d1f2bf349b78053aa071fe5835790a790f4974be95800cf57ad2bfa8ab96182b358d312
@@ -30,6 +30,23 @@ module Legion
30
30
  # Orchestrator delegation
31
31
  def run_growth_cycle(**) = Runners::Orchestrator.run_growth_cycle(**)
32
32
  def growth_status(**) = Runners::Orchestrator.growth_status(**)
33
+
34
+ # Retrospective delegation
35
+ def session_report(**) = Runners::Retrospective.session_report(**)
36
+ def trend_analysis(**) = Runners::Retrospective.trend_analysis(**)
37
+ def learning_extraction(**) = Runners::Retrospective.learning_extraction(**)
38
+
39
+ # Governance delegation
40
+ def submit_proposal(**) = Runners::Governance.submit_proposal(**)
41
+ def vote_on_proposal(**) = Runners::Governance.vote_on_proposal(**)
42
+ def tally_votes(**) = Runners::Governance.tally_votes(**)
43
+ def approve_proposal(**) = Runners::Governance.approve_proposal(**)
44
+ def reject_proposal(**) = Runners::Governance.reject_proposal(**)
45
+ def governance_stats(**) = Runners::Governance.governance_stats(**)
46
+
47
+ # RiskAssessor delegation
48
+ def assess_risk(**) = Runners::RiskAssessor.assess_risk(**)
49
+ def risk_summary(**) = Runners::RiskAssessor.risk_summary(**)
33
50
  end
34
51
  end
35
52
  end
@@ -48,6 +48,20 @@ module Legion
48
48
 
49
49
  # Reference cognitive models
50
50
  COGNITIVE_MODELS = %i[global_workspace free_energy dual_process somatic_marker working_memory].freeze
51
+
52
+ # Governance
53
+ QUORUM = 3
54
+ REJECTION_COOLDOWN_HOURS = 24
55
+ GOVERNANCE_STATUSES = %i[pending approved rejected expired].freeze
56
+
57
+ # Risk assessment
58
+ RISK_TIERS = %i[low medium high critical].freeze
59
+ RISK_RECOMMENDATIONS = {
60
+ low: :auto_approve,
61
+ medium: :governance,
62
+ high: :human_required,
63
+ critical: :blocked
64
+ }.freeze
51
65
  end
52
66
  end
53
67
  end
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module MindGrowth
6
+ module Runners
7
+ module Governance
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
+ VOTE_VALUES = %i[approve reject].freeze
14
+
15
+ def submit_proposal(proposal_id:, **)
16
+ proposal = Runners::Proposer.get_proposal_object(proposal_id)
17
+ return { success: false, error: :not_found } unless proposal
18
+
19
+ return { success: false, error: :invalid_status, current_status: proposal.status } unless %i[proposed evaluating].include?(proposal.status)
20
+
21
+ proposal.transition!(:evaluating)
22
+ { success: true, proposal_id: proposal_id, status: :evaluating }
23
+ rescue ArgumentError => e
24
+ { success: false, error: e.message }
25
+ end
26
+
27
+ def vote_on_proposal(proposal_id:, vote:, agent_id: 'default', rationale: nil, **)
28
+ vote_sym = vote.to_sym
29
+ return { success: false, error: :invalid_vote } unless VOTE_VALUES.include?(vote_sym)
30
+
31
+ votes_mutex.synchronize do
32
+ votes_store[proposal_id] ||= []
33
+ votes_store[proposal_id] << { vote: vote_sym, agent_id: agent_id.to_s, rationale: rationale,
34
+ cast_at: Time.now.utc }
35
+ end
36
+
37
+ { success: true, proposal_id: proposal_id, vote: vote_sym, agent_id: agent_id.to_s }
38
+ end
39
+
40
+ def tally_votes(proposal_id:, **)
41
+ ballots = votes_mutex.synchronize { (votes_store[proposal_id] || []).dup }
42
+
43
+ approve_count = ballots.count { |b| b[:vote] == :approve }
44
+ reject_count = ballots.count { |b| b[:vote] == :reject }
45
+ total = ballots.size
46
+
47
+ verdict = if total < Helpers::Constants::QUORUM
48
+ :pending
49
+ elsif approve_count > reject_count
50
+ :approved
51
+ else
52
+ :rejected
53
+ end
54
+
55
+ { success: true, proposal_id: proposal_id, approve_count: approve_count,
56
+ reject_count: reject_count, total: total, verdict: verdict }
57
+ end
58
+
59
+ def approve_proposal(proposal_id:, _reason: nil, **)
60
+ proposal = Runners::Proposer.get_proposal_object(proposal_id)
61
+ return { success: false, error: :not_found } unless proposal
62
+
63
+ proposal.transition!(:approved)
64
+ { success: true, proposal_id: proposal_id, status: :approved }
65
+ rescue ArgumentError => e
66
+ { success: false, error: e.message }
67
+ end
68
+
69
+ def reject_proposal(proposal_id:, reason: nil, **)
70
+ proposal = Runners::Proposer.get_proposal_object(proposal_id)
71
+ return { success: false, error: :not_found } unless proposal
72
+
73
+ proposal.transition!(:rejected)
74
+ { success: true, proposal_id: proposal_id, status: :rejected, reason: reason }
75
+ rescue ArgumentError => e
76
+ { success: false, error: e.message }
77
+ end
78
+
79
+ def governance_stats(**)
80
+ all_votes = votes_mutex.synchronize { votes_store.dup }
81
+
82
+ total_votes = all_votes.values.sum(&:size)
83
+ proposals_with_votes = all_votes.size
84
+
85
+ vote_summary = all_votes.transform_values do |ballots|
86
+ {
87
+ approve: ballots.count { |b| b[:vote] == :approve },
88
+ reject: ballots.count { |b| b[:vote] == :reject },
89
+ total: ballots.size
90
+ }
91
+ end
92
+
93
+ proposal_stats = Runners::Proposer.proposal_stats
94
+ by_status = proposal_stats[:stats][:by_status]
95
+
96
+ governance_breakdown = Helpers::Constants::GOVERNANCE_STATUSES.to_h do |s|
97
+ [s, by_status[s] || 0]
98
+ end
99
+
100
+ {
101
+ success: true,
102
+ total_votes: total_votes,
103
+ proposals_with_votes: proposals_with_votes,
104
+ vote_summary: vote_summary,
105
+ governance_breakdown: governance_breakdown
106
+ }
107
+ end
108
+
109
+ private
110
+
111
+ def votes_store
112
+ @votes_store ||= {}
113
+ end
114
+
115
+ def votes_mutex
116
+ @votes_mutex ||= Mutex.new
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module MindGrowth
6
+ module Runners
7
+ module Retrospective
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
+ # Generates a summary of growth activity: proposals by status, recent builds, failures
14
+ def session_report(**)
15
+ proposals = Runners::Proposer.proposal_stats
16
+ recent = Runners::Proposer.list_proposals(limit: 10)
17
+
18
+ built = recent[:proposals].select { |p| %i[passing wired active].include?(p[:status]) }
19
+ failed = recent[:proposals].select { |p| %i[build_failed rejected pruned].include?(p[:status]) }
20
+ in_progress = recent[:proposals].select { |p| %i[proposed evaluating approved building testing].include?(p[:status]) }
21
+
22
+ {
23
+ success: true,
24
+ summary: {
25
+ total_proposals: proposals[:stats][:total],
26
+ by_status: proposals[:stats][:by_status],
27
+ recent_built: built.map { |p| { id: p[:id], name: p[:name], status: p[:status] } },
28
+ recent_failed: failed.map { |p| { id: p[:id], name: p[:name], status: p[:status] } },
29
+ in_progress: in_progress.map { |p| { id: p[:id], name: p[:name], status: p[:status] } }
30
+ },
31
+ generated_at: Time.now.utc
32
+ }
33
+ end
34
+
35
+ # Tracks extension count, quality, coverage over time
36
+ # Returns snapshot metrics suitable for time-series storage
37
+ def trend_analysis(extensions: [], **)
38
+ profile = Runners::Analyzer.cognitive_profile(existing_extensions: extensions.empty? ? nil : extensions)
39
+ ranked = extensions.empty? ? [] : Helpers::FitnessEvaluator.rank(extensions)
40
+
41
+ avg_fitness = ranked.empty? ? 0.0 : (ranked.sum { |e| e[:fitness] } / ranked.size).round(3)
42
+ prune_count = ranked.count { |e| e[:fitness] < Helpers::Constants::PRUNE_THRESHOLD }
43
+ healthy_count = ranked.count { |e| e[:fitness] >= Helpers::Constants::IMPROVEMENT_THRESHOLD }
44
+
45
+ {
46
+ success: true,
47
+ snapshot: {
48
+ extension_count: ranked.size,
49
+ overall_coverage: profile[:overall_coverage],
50
+ model_coverage: profile[:model_coverage]&.map { |m| { model: m[:model], coverage: m[:coverage] } },
51
+ avg_fitness: avg_fitness,
52
+ healthy_extensions: healthy_count,
53
+ prune_candidates: prune_count,
54
+ improvement_candidates: ranked.size - healthy_count - prune_count
55
+ },
56
+ generated_at: Time.now.utc
57
+ }
58
+ end
59
+
60
+ # Identifies patterns from build failures to improve future LLM prompts
61
+ def learning_extraction(**)
62
+ all_proposals = Runners::Proposer.list_proposals(limit: 100)
63
+ proposals = all_proposals[:proposals]
64
+
65
+ failed = proposals.select { |p| p[:status] == :build_failed }
66
+ rejected = proposals.select { |p| p[:status] == :rejected }
67
+ succeeded = proposals.select { |p| %i[passing wired active].include?(p[:status]) }
68
+
69
+ # Category success rates
70
+ category_stats = compute_category_stats(proposals)
71
+
72
+ # Extract patterns from failures
73
+ failure_patterns = extract_failure_patterns(failed)
74
+
75
+ {
76
+ success: true,
77
+ learnings: {
78
+ total_analyzed: proposals.size,
79
+ success_rate: proposals.empty? ? 0.0 : (succeeded.size.to_f / proposals.size).round(3),
80
+ rejection_rate: proposals.empty? ? 0.0 : (rejected.size.to_f / proposals.size).round(3),
81
+ build_failure_rate: proposals.empty? ? 0.0 : (failed.size.to_f / proposals.size).round(3),
82
+ category_stats: category_stats,
83
+ failure_patterns: failure_patterns,
84
+ recommendations: generate_recommendations(category_stats, failure_patterns)
85
+ },
86
+ generated_at: Time.now.utc
87
+ }
88
+ end
89
+
90
+ private
91
+
92
+ def compute_category_stats(proposals)
93
+ by_category = proposals.group_by { |p| p[:category] }
94
+ by_category.transform_values do |cat_proposals|
95
+ succeeded = cat_proposals.count { |p| %i[passing wired active].include?(p[:status]) }
96
+ {
97
+ total: cat_proposals.size,
98
+ succeeded: succeeded,
99
+ success_rate: cat_proposals.empty? ? 0.0 : (succeeded.to_f / cat_proposals.size).round(3)
100
+ }
101
+ end
102
+ end
103
+
104
+ def extract_failure_patterns(failed_proposals)
105
+ return [] if failed_proposals.empty?
106
+
107
+ # Group failures by category to identify problematic areas
108
+ by_category = failed_proposals.group_by { |p| p[:category] }
109
+ patterns = by_category.map do |category, proposals|
110
+ { category: category, failure_count: proposals.size,
111
+ names: proposals.map { |p| p[:name] } }
112
+ end
113
+ patterns.sort_by { |p| -p[:failure_count] }
114
+ end
115
+
116
+ def generate_recommendations(category_stats, failure_patterns)
117
+ recs = []
118
+
119
+ # Recommend avoiding categories with high failure rates
120
+ category_stats.each do |category, stats|
121
+ if stats[:total] >= 3 && stats[:success_rate] < 0.3
122
+ recs << { type: :avoid_category, category: category,
123
+ reason: "low success rate (#{(stats[:success_rate] * 100).round}%)" }
124
+ end
125
+
126
+ # Recommend focus on categories with high success rates
127
+ if stats[:total] >= 3 && stats[:success_rate] > 0.8
128
+ recs << { type: :focus_category, category: category,
129
+ reason: "high success rate (#{(stats[:success_rate] * 100).round}%)" }
130
+ end
131
+ end
132
+
133
+ # Flag recurring failure patterns
134
+ failure_patterns.each do |pattern|
135
+ if pattern[:failure_count] >= 3
136
+ recs << { type: :investigate_failures, category: pattern[:category],
137
+ reason: "#{pattern[:failure_count]} build failures" }
138
+ end
139
+ end
140
+
141
+ recs
142
+ end
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module Extensions
5
+ module MindGrowth
6
+ module Runners
7
+ module RiskAssessor
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
+ HIGH_BLAST_CATEGORIES = %i[safety coordination].freeze
14
+ MEDIUM_BLAST_CATEGORIES = %i[cognition].freeze
15
+ HOT_PATH_CATEGORIES = %i[perception memory].freeze
16
+
17
+ def assess_risk(proposal_id:, **)
18
+ proposal = Runners::Proposer.get_proposal_object(proposal_id)
19
+ return { success: false, error: :not_found } unless proposal
20
+
21
+ dimensions = evaluate_dimensions(proposal)
22
+ tier = calculate_tier(dimensions)
23
+ recommendation = Helpers::Constants::RISK_RECOMMENDATIONS[tier]
24
+
25
+ { success: true, proposal_id: proposal_id, risk_tier: tier,
26
+ dimensions: dimensions, recommendation: recommendation }
27
+ end
28
+
29
+ def risk_summary(proposals: nil, **)
30
+ ids = if proposals
31
+ Array(proposals).map { |p| p.is_a?(Hash) ? p[:id] : p.to_s }
32
+ else
33
+ Runners::Proposer.list_proposals(limit: 100)[:proposals].map { |p| p[:id] }
34
+ end
35
+
36
+ results = ids.filter_map do |id|
37
+ result = assess_risk(proposal_id: id)
38
+ next unless result[:success]
39
+
40
+ result
41
+ end
42
+
43
+ grouped = Helpers::Constants::RISK_TIERS.to_h { |tier| [tier, []] }
44
+ results.each { |r| grouped[r[:risk_tier]] << r }
45
+
46
+ { success: true, total: results.size, by_tier: grouped }
47
+ end
48
+
49
+ private
50
+
51
+ def evaluate_dimensions(proposal)
52
+ helper_count = Array(proposal.helpers).size
53
+ category = proposal.category.to_sym
54
+
55
+ {
56
+ complexity: complexity_level(helper_count, Array(proposal.runner_methods).size),
57
+ blast_radius: blast_radius_level(category),
58
+ reversibility: :high,
59
+ performance_impact: performance_impact_level(category)
60
+ }
61
+ end
62
+
63
+ def complexity_level(helper_count, runner_count)
64
+ total = helper_count + runner_count
65
+ if total >= 7
66
+ :high
67
+ elsif total >= 4
68
+ :medium
69
+ else
70
+ :low
71
+ end
72
+ end
73
+
74
+ def blast_radius_level(category)
75
+ if HIGH_BLAST_CATEGORIES.include?(category)
76
+ :high
77
+ elsif MEDIUM_BLAST_CATEGORIES.include?(category)
78
+ :medium
79
+ else
80
+ :low
81
+ end
82
+ end
83
+
84
+ def performance_impact_level(category)
85
+ HOT_PATH_CATEGORIES.include?(category) ? :medium : :low
86
+ end
87
+
88
+ def calculate_tier(dimensions)
89
+ # Reversibility is a positive attribute (high = easily reversed) — exclude from risk calc
90
+ risk_values = dimensions.except(:reversibility).values
91
+
92
+ if risk_values.include?(:critical)
93
+ :critical
94
+ elsif risk_values.include?(:high)
95
+ :high
96
+ elsif risk_values.include?(:medium)
97
+ :medium
98
+ else
99
+ :low
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -3,7 +3,7 @@
3
3
  module Legion
4
4
  module Extensions
5
5
  module MindGrowth
6
- VERSION = '0.1.5'
6
+ VERSION = '0.1.7'
7
7
  end
8
8
  end
9
9
  end
@@ -17,6 +17,9 @@ require 'legion/extensions/mind_growth/runners/validator'
17
17
  require 'legion/extensions/mind_growth/runners/orchestrator'
18
18
  require 'legion/extensions/mind_growth/runners/wirer'
19
19
  require 'legion/extensions/mind_growth/runners/integration_tester'
20
+ require 'legion/extensions/mind_growth/runners/retrospective'
21
+ require 'legion/extensions/mind_growth/runners/governance'
22
+ require 'legion/extensions/mind_growth/runners/risk_assessor'
20
23
  require 'legion/extensions/mind_growth/client'
21
24
 
22
25
  module Legion