legionio 1.6.3 → 1.6.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: 72cd45444eead02f6fe56c876cc25c42c4f74111beaf76e5d898cbe94dbf2ea6
4
- data.tar.gz: f9f69b056a77e373eff39813f96f79882509dfdf7273bc81afee92dc556d84f6
3
+ metadata.gz: e025887526f8b9e258eb168a14a83d5c34e60668eadbe9e1ccd5c995e27d725c
4
+ data.tar.gz: a85e52dc5ec39293461e7df402f2ea8f1a410d293c28141fc935cfb6023b71df
5
5
  SHA512:
6
- metadata.gz: e86e061031320558c96fddc8bfabf2380a97cd51ee27a87d61cd7758e7cb432f8dc2b45257447146ad93ba16f4fd7100de3a90b2f3ecb95d41535a08efaf53af
7
- data.tar.gz: 40420f889daf45ddf2f75533e981063b33501423ae330e59114b40dfff899e345c1deeb82c9244183842c78a952b9ad1a39d9b10ad54056afd3a7f7b3a59f0ec
6
+ metadata.gz: 6ecefca3ab028b370f8e590cf4fa81290e3a9e106f53a5655d11928f40ec99a0d48451ab94c9516086fd81bd5c42d4523eae4d975b9df667d8db7010656884c6
7
+ data.tar.gz: c5f4aa31b5431e6fb012991fe0048ac75c0d25d95e6402983590d7954491ad53e78b18d2911cb7969427cd60e63a47975d7ca89ff7642857cd857328dec559e3
data/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # Legion Changelog
2
2
 
3
+ ## [1.6.7] - 2026-03-26
4
+
5
+ ### Fixed
6
+ - `setup_generated_functions` now runs only when `extensions: true` (inside the extensions gate) preventing unexpected boot side-effects in CLI flows that disable extensions
7
+ - Consumer tag entropy upgraded from `SecureRandom.hex(4)` (32-bit) to `SecureRandom.uuid` (122-bit) in both `prepare` and `subscribe` paths of subscription actor, eliminating the theoretical RabbitMQ `NOT_ALLOWED` tag collision
8
+
9
+ ## [1.6.6] - 2026-03-26
10
+
11
+ ### Added
12
+ - `legionio bootstrap SOURCE` command: combines `config import`, `config scaffold`, and `setup agentic` into one command
13
+ - Pre-flight checks for klist (Kerberos ticket), brew availability, and legionio binary
14
+ - `--skip-packs` flag to skip gem pack installation (config-only mode)
15
+ - `--start` flag to start redis + legionio via brew services after bootstrap
16
+ - `--force` flag to overwrite existing config files during bootstrap
17
+ - `--json` flag for machine-readable bootstrap output
18
+ - `shell_capture` helper extracted to make shell invocations stubbable in specs
19
+ - 62 specs covering preflight checks, pack extraction, config fetch/write delegation, pack install, summary output, all flags, and error handling
20
+
21
+ ## [1.6.5] - 2026-03-26
22
+
23
+ ### Added
24
+ - `Context.to_system_prompt` appends a live self-awareness section from `lex-agentic-self` Metacognition when the gem is loaded; logic extracted into `self_awareness_hint` helper to keep `to_system_prompt` within Metrics/CyclomaticComplexity limits; guarded with `defined?()` and `rescue StandardError`
25
+
26
+ ## [1.6.4] - 2026-03-26
27
+
28
+ ### Fixed
29
+ - fix consumer tag collision on boot: subscription actors using `Thread.current.object_id` produced duplicate tags when `FixedThreadPool` reused threads, causing RabbitMQ `NOT_ALLOWED` connection kill and cascading errors; replaced with `SecureRandom.hex(4)`
30
+
3
31
  ## [1.6.3] - 2026-03-26
4
32
 
5
33
  ### Changed
@@ -24,6 +52,12 @@
24
52
  ## [1.6.0] - 2026-03-26
25
53
 
26
54
  ### Added
55
+ - `legion codegen` CLI subcommand (status, list, show, approve, reject, retry, gaps, cycle)
56
+ - `/api/codegen/*` API routes for generated function management
57
+ - Boot loading for generated functions via GeneratedRegistry
58
+ - Function metadata DSL (function_outputs, function_category, function_tags, function_risk_tier, function_idempotent, function_requires, function_expose)
59
+ - ClassMethods for MCP tool exposure (expose_as_mcp_tool, mcp_tool_prefix)
60
+ - End-to-end integration test for self-generating functions
27
61
  - `legion knowledge monitor add/list/remove/status` — multi-directory corpus monitor management
28
62
  - `legion knowledge capture commit` — capture git commit as knowledge (hook-compatible)
29
63
  - `legion knowledge capture session` — capture session summary as knowledge (hook-compatible)
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ class API < Sinatra::Base
5
+ module Routes
6
+ module Codegen
7
+ def self.registered(app) # rubocop:disable Metrics/MethodLength
8
+ app.get '/api/codegen/status' do
9
+ halt 503, json_error('codegen_unavailable', 'codegen not available', status_code: 503) unless defined?(Legion::MCP::SelfGenerate)
10
+
11
+ json_response(Legion::MCP::SelfGenerate.status)
12
+ end
13
+
14
+ app.get '/api/codegen/generated' do
15
+ unless defined?(Legion::Extensions::Codegen::Helpers::GeneratedRegistry)
16
+ halt 503, json_error('codegen_unavailable', 'codegen not available', status_code: 503)
17
+ end
18
+
19
+ status_filter = params[:status]
20
+ records = Legion::Extensions::Codegen::Helpers::GeneratedRegistry.list(status: status_filter)
21
+ json_response(records)
22
+ end
23
+
24
+ app.get '/api/codegen/generated/:id' do |id|
25
+ unless defined?(Legion::Extensions::Codegen::Helpers::GeneratedRegistry)
26
+ halt 503, json_error('codegen_unavailable', 'codegen not available', status_code: 503)
27
+ end
28
+
29
+ record = Legion::Extensions::Codegen::Helpers::GeneratedRegistry.get(id: id)
30
+ halt 404, json_error('not_found', 'record not found', status_code: 404) unless record
31
+
32
+ json_response(record)
33
+ end
34
+
35
+ app.post '/api/codegen/generated/:id/approve' do |id|
36
+ unless defined?(Legion::Extensions::Codegen::Runners::ReviewHandler)
37
+ halt 503, json_error('codegen_unavailable', 'review handler not available', status_code: 503)
38
+ end
39
+
40
+ result = Legion::Extensions::Codegen::Runners::ReviewHandler.handle_verdict(
41
+ review: { generation_id: id, verdict: :approve, confidence: 1.0 }
42
+ )
43
+ json_response(result)
44
+ end
45
+
46
+ app.post '/api/codegen/generated/:id/reject' do |id|
47
+ unless defined?(Legion::Extensions::Codegen::Helpers::GeneratedRegistry)
48
+ halt 503, json_error('codegen_unavailable', 'codegen not available', status_code: 503)
49
+ end
50
+
51
+ Legion::Extensions::Codegen::Helpers::GeneratedRegistry.update_status(id: id, status: 'rejected')
52
+ json_response({ id: id, status: 'rejected' })
53
+ end
54
+
55
+ app.post '/api/codegen/generated/:id/retry' do |id|
56
+ unless defined?(Legion::Extensions::Codegen::Helpers::GeneratedRegistry)
57
+ halt 503, json_error('codegen_unavailable', 'codegen not available', status_code: 503)
58
+ end
59
+
60
+ Legion::Extensions::Codegen::Helpers::GeneratedRegistry.update_status(id: id, status: 'pending')
61
+ json_response({ id: id, status: 'pending' })
62
+ end
63
+
64
+ app.get '/api/codegen/gaps' do
65
+ data = if defined?(Legion::MCP::GapDetector)
66
+ Legion::MCP::GapDetector.detect_gaps
67
+ else
68
+ []
69
+ end
70
+ json_response(data)
71
+ end
72
+
73
+ app.post '/api/codegen/cycle' do
74
+ return json_response({ triggered: false, reason: 'self_generate not available' }) unless defined?(Legion::MCP::SelfGenerate)
75
+
76
+ Legion::MCP::SelfGenerate.instance_variable_set(:@last_cycle_at, nil)
77
+ result = Legion::MCP::SelfGenerate.run_cycle
78
+ json_response(result)
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
data/lib/legion/api.rb CHANGED
@@ -46,6 +46,7 @@ require_relative 'api/apollo'
46
46
  require_relative 'api/costs'
47
47
  require_relative 'api/traces'
48
48
  require_relative 'api/stats'
49
+ require_relative 'api/codegen'
49
50
  require_relative 'api/graphql' if defined?(GraphQL)
50
51
 
51
52
  module Legion
@@ -137,6 +138,7 @@ module Legion
137
138
  register Routes::Costs
138
139
  register Routes::Traces
139
140
  register Routes::Stats
141
+ register Routes::Codegen
140
142
  register Routes::GraphQL if defined?(Routes::GraphQL)
141
143
 
142
144
  use Legion::API::Middleware::RequestLogger
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module CLI
5
+ class CodegenCommand < Thor
6
+ namespace :codegen
7
+
8
+ desc 'status', 'Show codegen cycle stats, pending gaps, registry counts'
9
+ def status
10
+ if defined?(Legion::MCP::SelfGenerate)
11
+ data = Legion::MCP::SelfGenerate.status
12
+ say Legion::JSON.dump({ data: data })
13
+ else
14
+ say Legion::JSON.dump({ error: 'codegen not available' })
15
+ end
16
+ end
17
+
18
+ desc 'list', 'List generated functions'
19
+ method_option :status, type: :string, desc: 'Filter by status'
20
+ def list
21
+ unless defined?(Legion::Extensions::Codegen::Helpers::GeneratedRegistry)
22
+ say Legion::JSON.dump({ error: 'codegen registry not available' })
23
+ return
24
+ end
25
+
26
+ records = Legion::Extensions::Codegen::Helpers::GeneratedRegistry.list(status: options[:status])
27
+ say Legion::JSON.dump({ data: records })
28
+ end
29
+
30
+ desc 'show ID', 'Show details of a generated function'
31
+ def show(id)
32
+ unless defined?(Legion::Extensions::Codegen::Helpers::GeneratedRegistry)
33
+ say Legion::JSON.dump({ error: 'codegen registry not available' })
34
+ return
35
+ end
36
+
37
+ record = Legion::Extensions::Codegen::Helpers::GeneratedRegistry.get(id: id)
38
+ if record
39
+ say Legion::JSON.dump({ data: record })
40
+ else
41
+ say Legion::JSON.dump({ error: 'not found' })
42
+ end
43
+ end
44
+
45
+ desc 'approve ID', 'Manually approve a parked generated function'
46
+ def approve(id)
47
+ unless defined?(Legion::Extensions::Codegen::Runners::ReviewHandler)
48
+ say Legion::JSON.dump({ error: 'review handler not available' })
49
+ return
50
+ end
51
+
52
+ result = Legion::Extensions::Codegen::Runners::ReviewHandler.handle_verdict(
53
+ review: { generation_id: id, verdict: :approve, confidence: 1.0 }
54
+ )
55
+ say Legion::JSON.dump({ data: result })
56
+ end
57
+
58
+ desc 'reject ID', 'Manually reject a generated function'
59
+ def reject(id)
60
+ unless defined?(Legion::Extensions::Codegen::Helpers::GeneratedRegistry)
61
+ say Legion::JSON.dump({ error: 'codegen registry not available' })
62
+ return
63
+ end
64
+
65
+ Legion::Extensions::Codegen::Helpers::GeneratedRegistry.update_status(id: id, status: 'rejected')
66
+ say Legion::JSON.dump({ data: { id: id, status: 'rejected' } })
67
+ end
68
+
69
+ desc 'retry ID', 'Re-queue a generated function for regeneration'
70
+ def retry_generation(id)
71
+ unless defined?(Legion::Extensions::Codegen::Helpers::GeneratedRegistry)
72
+ say Legion::JSON.dump({ error: 'codegen registry not available' })
73
+ return
74
+ end
75
+
76
+ Legion::Extensions::Codegen::Helpers::GeneratedRegistry.update_status(id: id, status: 'pending')
77
+ say Legion::JSON.dump({ data: { id: id, status: 'pending' } })
78
+ end
79
+ map 'retry' => :retry_generation
80
+
81
+ desc 'gaps', 'List detected capability gaps with priorities'
82
+ def gaps
83
+ if defined?(Legion::MCP::GapDetector)
84
+ detected = Legion::MCP::GapDetector.detect_gaps
85
+ say Legion::JSON.dump({ data: detected })
86
+ else
87
+ say Legion::JSON.dump({ error: 'gap detector not available' })
88
+ end
89
+ end
90
+
91
+ desc 'cycle', 'Manually trigger a generation cycle (bypass cooldown)'
92
+ def cycle
93
+ unless defined?(Legion::MCP::SelfGenerate)
94
+ say Legion::JSON.dump({ error: 'self_generate not available' })
95
+ return
96
+ end
97
+
98
+ Legion::MCP::SelfGenerate.instance_variable_set(:@last_cycle_at, nil)
99
+ result = Legion::MCP::SelfGenerate.run_cycle
100
+ say Legion::JSON.dump({ data: result })
101
+ end
102
+ end
103
+ end
104
+ end
data/lib/legion/cli.rb CHANGED
@@ -64,6 +64,7 @@ module Legion
64
64
  autoload :TraceCommand, 'legion/cli/trace_command'
65
65
  autoload :Features, 'legion/cli/features_command'
66
66
  autoload :Debug, 'legion/cli/debug_command'
67
+ autoload :CodegenCommand, 'legion/cli/codegen_command'
67
68
 
68
69
  module Groups
69
70
  autoload :Ai, 'legion/cli/groups/ai_group'
@@ -242,6 +243,9 @@ module Legion
242
243
  subcommand 'init', Legion::CLI::Init
243
244
 
244
245
  # --- Interactive & shortcuts ---
246
+ desc 'codegen SUBCOMMAND', 'Manage self-generating functions'
247
+ subcommand 'codegen', CodegenCommand
248
+
245
249
  desc 'tty', 'Rich terminal UI (onboarding, AI chat, dashboard)'
246
250
  subcommand 'tty', Legion::CLI::Tty
247
251
 
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative 'base'
4
4
  require 'date'
5
+ require 'securerandom'
5
6
 
6
7
  module Legion
7
8
  module Extensions
@@ -50,7 +51,7 @@ module Legion
50
51
  def prepare # rubocop:disable Metrics/AbcSize
51
52
  @queue = queue.new
52
53
  @queue.channel.prefetch(prefetch) if defined? prefetch
53
- consumer_tag = "#{Legion::Settings[:client][:name]}_#{lex_name}_#{runner_name}_#{Thread.current.object_id}"
54
+ consumer_tag = "#{Legion::Settings[:client][:name]}_#{lex_name}_#{runner_name}_#{SecureRandom.uuid}"
54
55
  @consumer = Bunny::Consumer.new(@queue.channel, @queue, consumer_tag, false, false)
55
56
  @consumer.on_delivery do |delivery_info, metadata, payload|
56
57
  message = process_message(payload, metadata, delivery_info)
@@ -150,7 +151,7 @@ module Legion
150
151
  def subscribe # rubocop:disable Metrics/AbcSize
151
152
  log.info "[Subscription] subscribing: #{lex_name}/#{runner_name}"
152
153
  sleep(delay_start) if delay_start.positive?
153
- consumer_tag = "#{Legion::Settings[:client][:name]}_#{lex_name}_#{runner_name}_#{Thread.current.object_id}"
154
+ consumer_tag = "#{Legion::Settings[:client][:name]}_#{lex_name}_#{runner_name}_#{SecureRandom.uuid}"
154
155
  on_cancellation = block { cancel }
155
156
 
156
157
  @consumer = @queue.subscribe(manual_ack: manual_ack, block: false, consumer_tag: consumer_tag, on_cancellation: on_cancellation) do |*rmq_message|
@@ -126,6 +126,7 @@ module Legion
126
126
  if extensions
127
127
  load_extensions
128
128
  Legion::Readiness.mark_ready(:extensions)
129
+ setup_generated_functions
129
130
  end
130
131
 
131
132
  Legion::Gaia.registry&.rediscover if gaia && defined?(Legion::Gaia) && Legion::Gaia.started?
@@ -612,6 +613,15 @@ module Legion
612
613
  Legion::Extensions.hook_extensions
613
614
  end
614
615
 
616
+ def setup_generated_functions
617
+ return unless defined?(Legion::Extensions::Codegen::Helpers::GeneratedRegistry)
618
+
619
+ loaded = Legion::Extensions::Codegen::Helpers::GeneratedRegistry.load_on_boot
620
+ Legion::Logging.info("Loaded #{loaded} generated functions") if defined?(Legion::Logging) && loaded.to_i.positive?
621
+ rescue StandardError => e
622
+ Legion::Logging.warn("setup_generated_functions failed: #{e.message}") if defined?(Legion::Logging)
623
+ end
624
+
615
625
  def setup_mtls_rotation
616
626
  enabled = Legion::Settings[:security]&.dig(:mtls, :enabled)
617
627
  return unless enabled
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Legion
4
- VERSION = '1.6.3'
4
+ VERSION = '1.6.7'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: legionio
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.6.3
4
+ version: 1.6.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
@@ -447,6 +447,7 @@ files:
447
447
  - lib/legion/api/capacity.rb
448
448
  - lib/legion/api/catalog.rb
449
449
  - lib/legion/api/chains.rb
450
+ - lib/legion/api/codegen.rb
450
451
  - lib/legion/api/coldstart.rb
451
452
  - lib/legion/api/costs.rb
452
453
  - lib/legion/api/events.rb
@@ -576,6 +577,7 @@ files:
576
577
  - lib/legion/cli/chat_command.rb
577
578
  - lib/legion/cli/check/privacy_check.rb
578
579
  - lib/legion/cli/check_command.rb
580
+ - lib/legion/cli/codegen_command.rb
579
581
  - lib/legion/cli/cohort.rb
580
582
  - lib/legion/cli/coldstart_command.rb
581
583
  - lib/legion/cli/commit_command.rb