legionio 1.5.18 → 1.5.21

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: fa969cacd00cbd7fcd3234d1d7b46e720da00a4193b4faa967d0f4e9b662b4e4
4
- data.tar.gz: 2afe90f9456b0c5abf07f7180d562dd84ac7c901e81aea1b841288f3aae929d6
3
+ metadata.gz: ff2178b95e28c31128ad05f307d30f9cc4eb805210144dd7b579e3e526046045
4
+ data.tar.gz: d49fdcfa4b32c25cb0757abdac503e26cac31b5f134e4764219d79afe7b1141b
5
5
  SHA512:
6
- metadata.gz: bf23f83209770f3e9b7c66bd59ccae187f24c136b94059b5bc54a58e9e80188280a94f01ce557eab19eb1a903bcd9e1a9d69cf7a46077ab89297f934ca1b5244
7
- data.tar.gz: cf041438573f2eb546929ef68e35c527b52af9f552e129fd79b6c94f75862361eeee9d63d4f1fc2b26fecbd70078cc97614c6e928c7fa45cb317b3de858c4eea
6
+ metadata.gz: d4636401d967346d0db79ed299ed6bcb0bccd854a1ff58af1f88dfba545610bca4d6ea1f95c992ad206a90091b8510f214392f236d9f35fca8e347f0f76d61c9
7
+ data.tar.gz: e477f818ab50a7edcbdf8be7b5e5e62ac4c65d9394f224401104cc646140d9dfa7e6ab356fdea1dc69c764ca3f1144a36e8c42998978b3fa73620358166b4e2d
data/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  # Legion Changelog
2
2
 
3
+ ## [1.5.21] - 2026-03-26
4
+
5
+ ### Changed
6
+ - `legionio setup agentic` now installs the full cognitive stack (63 gems): core libs, all agentic domains, all AI providers, and key operational extensions
7
+ - Added `brains` and `give-me-all-the-brains` as aliases for the `agentic` subcommand
8
+
9
+ ## [1.5.20] - 2026-03-26
10
+
11
+ ### Added
12
+ - `legion knowledge health` — local/Apollo/sync health report
13
+ - `legion knowledge maintain` — orphan chunk detection and cleanup (dry-run by default)
14
+ - `legion knowledge quality` — hot/cold/low-confidence chunk quality report
15
+
16
+ ## [1.5.19] - 2026-03-26
17
+
18
+ ### Added
19
+ - `legion knowledge` CLI subcommand: query, retrieve, ingest, status (closes #36)
20
+ - `legion knowledge query QUESTION` — synthesized LLM answer + ranked source chunks
21
+ - `legion knowledge retrieve QUESTION` — raw source chunks without synthesis
22
+ - `legion knowledge ingest PATH` — ingest file or directory corpus
23
+ - `legion knowledge status` — show corpus file count and size
24
+
3
25
  ## [1.5.18] - 2026-03-25
4
26
 
5
27
  ### Added
@@ -0,0 +1,242 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Legion
4
+ module CLI
5
+ class Knowledge < Thor
6
+ def self.exit_on_failure?
7
+ true
8
+ end
9
+
10
+ class_option :json, type: :boolean, default: false, desc: 'Output as JSON'
11
+ class_option :no_color, type: :boolean, default: false, desc: 'Disable color output'
12
+
13
+ desc 'query QUESTION', 'Query the knowledge base with optional LLM synthesis'
14
+ option :top_k, type: :numeric, default: 5, desc: 'Number of source chunks'
15
+ option :synthesize, type: :boolean, default: true, desc: 'Synthesize an LLM answer'
16
+ option :verbose, type: :boolean, default: false, desc: 'Show full source metadata'
17
+ def query(question)
18
+ require_knowledge!
19
+ result = knowledge_query.query(question: question, top_k: options[:top_k],
20
+ synthesize: options[:synthesize])
21
+ out = formatter
22
+ if options[:json]
23
+ out.json(result)
24
+ elsif result[:success]
25
+ out.header('Knowledge Query')
26
+ if result[:answer]
27
+ out.spacer
28
+ puts result[:answer]
29
+ out.spacer
30
+ end
31
+ print_sources(result[:sources] || [], out, verbose: options[:verbose])
32
+ else
33
+ out.warn("Query failed: #{result[:error]}")
34
+ end
35
+ end
36
+ default_task :help
37
+
38
+ desc 'retrieve QUESTION', 'Retrieve source chunks without LLM synthesis'
39
+ option :top_k, type: :numeric, default: 5, desc: 'Number of source chunks'
40
+ def retrieve(question)
41
+ require_knowledge!
42
+ result = knowledge_query.retrieve(question: question, top_k: options[:top_k])
43
+ out = formatter
44
+ if options[:json]
45
+ out.json(result)
46
+ elsif result[:success]
47
+ out.header("Knowledge Retrieve (#{(result[:sources] || []).size} chunks)")
48
+ print_sources(result[:sources] || [], out, verbose: true)
49
+ else
50
+ out.warn("Retrieve failed: #{result[:error]}")
51
+ end
52
+ end
53
+
54
+ desc 'ingest PATH', 'Ingest a file or directory into the knowledge base'
55
+ option :force, type: :boolean, default: false, desc: 'Re-ingest even unchanged files'
56
+ option :dry_run, type: :boolean, default: false, desc: 'Preview without writing'
57
+ def ingest(path)
58
+ require_ingest!
59
+ result = if ::File.directory?(path)
60
+ knowledge_ingest.ingest_corpus(path: path, force: options[:force],
61
+ dry_run: options[:dry_run])
62
+ else
63
+ knowledge_ingest.ingest_file(file_path: path, force: options[:force],
64
+ dry_run: options[:dry_run])
65
+ end
66
+ out = formatter
67
+ if options[:json]
68
+ out.json(result)
69
+ elsif result[:success]
70
+ out.success('Ingest complete')
71
+ out.detail(result.except(:success))
72
+ else
73
+ out.warn("Ingest failed: #{result[:error]}")
74
+ end
75
+ end
76
+
77
+ desc 'status', 'Show knowledge base status'
78
+ def status
79
+ require_ingest!
80
+ result = knowledge_ingest.scan_corpus(path: ::Dir.pwd)
81
+ out = formatter
82
+ if options[:json]
83
+ out.json(result)
84
+ else
85
+ out.header('Knowledge Status')
86
+ out.detail({
87
+ 'Path' => result[:path].to_s,
88
+ 'Files' => result[:file_count].to_s,
89
+ 'Total size' => "#{result[:total_bytes]} bytes"
90
+ })
91
+ end
92
+ end
93
+
94
+ desc 'health', 'Show knowledge base health report (local, Apollo, sync)'
95
+ option :corpus_path, type: :string, desc: 'Path to corpus directory (falls back to settings)'
96
+ def health
97
+ require_maintenance!
98
+ path = resolve_corpus_path
99
+ result = knowledge_maintenance.health(path: path)
100
+ out = formatter
101
+ if options[:json]
102
+ out.json(result)
103
+ elsif result[:success]
104
+ out.header('Knowledge Health')
105
+ out.spacer
106
+ out.header('Local')
107
+ out.detail(result[:local])
108
+ out.spacer
109
+ out.header('Apollo')
110
+ out.detail(result[:apollo])
111
+ out.spacer
112
+ out.header('Sync')
113
+ out.detail(result[:sync])
114
+ else
115
+ out.warn("Health check failed: #{result[:error]}")
116
+ end
117
+ end
118
+
119
+ desc 'maintain', 'Detect and clean up orphaned knowledge chunks'
120
+ option :corpus_path, type: :string, desc: 'Path to corpus directory (falls back to settings)'
121
+ option :dry_run, type: :boolean, default: true, desc: 'Preview without archiving (default: true)'
122
+ def maintain
123
+ require_maintenance!
124
+ path = resolve_corpus_path
125
+ result = knowledge_maintenance.cleanup_orphans(path: path, dry_run: options[:dry_run])
126
+ out = formatter
127
+ if options[:json]
128
+ out.json(result)
129
+ elsif result[:success]
130
+ out.header("Knowledge Maintain#{' (dry run)' if options[:dry_run]}")
131
+ out.detail({
132
+ 'Orphan files' => (result[:orphan_files] || []).join(', '),
133
+ 'Archived' => result[:archived].to_s,
134
+ 'Files cleaned' => result[:files_cleaned].to_s,
135
+ 'Dry run' => result[:dry_run].to_s
136
+ })
137
+ else
138
+ out.warn("Maintenance failed: #{result[:error]}")
139
+ end
140
+ end
141
+
142
+ desc 'quality', 'Show knowledge quality report (hot, cold, low-confidence chunks)'
143
+ option :limit, type: :numeric, default: 10, desc: 'Max entries per category'
144
+ def quality
145
+ require_maintenance!
146
+ result = knowledge_maintenance.quality_report(limit: options[:limit])
147
+ out = formatter
148
+ if options[:json]
149
+ out.json(result)
150
+ elsif result[:success]
151
+ out.header('Knowledge Quality Report')
152
+ out.spacer
153
+ print_chunk_section('Hot Chunks (most accessed)', result[:hot_chunks], out)
154
+ print_chunk_section('Cold Chunks (never accessed)', result[:cold_chunks], out)
155
+ print_chunk_section('Low Confidence', result[:low_confidence], out)
156
+ out.spacer
157
+ out.header('Summary')
158
+ out.detail(result[:summary])
159
+ else
160
+ out.warn("Quality report failed: #{result[:error]}")
161
+ end
162
+ end
163
+
164
+ no_commands do # rubocop:disable Metrics/BlockLength
165
+ def formatter
166
+ @formatter ||= Output::Formatter.new(json: options[:json], color: !options[:no_color])
167
+ end
168
+
169
+ def require_knowledge!
170
+ return if defined?(Legion::Extensions::Knowledge::Runners::Query)
171
+
172
+ raise CLI::Error, 'lex-knowledge extension is not loaded. Install and enable it first.'
173
+ end
174
+
175
+ def require_ingest!
176
+ return if defined?(Legion::Extensions::Knowledge::Runners::Ingest)
177
+
178
+ raise CLI::Error, 'lex-knowledge extension is not loaded. Install and enable it first.'
179
+ end
180
+
181
+ def require_maintenance!
182
+ return if defined?(Legion::Extensions::Knowledge::Runners::Maintenance)
183
+
184
+ raise CLI::Error, 'lex-knowledge extension is not loaded. Install and enable it first.'
185
+ end
186
+
187
+ def knowledge_query
188
+ Legion::Extensions::Knowledge::Runners::Query
189
+ end
190
+
191
+ def knowledge_ingest
192
+ Legion::Extensions::Knowledge::Runners::Ingest
193
+ end
194
+
195
+ def knowledge_maintenance
196
+ Legion::Extensions::Knowledge::Runners::Maintenance
197
+ end
198
+
199
+ def resolve_corpus_path
200
+ if options[:corpus_path]
201
+ options[:corpus_path]
202
+ elsif defined?(Legion::Settings)
203
+ Legion::Settings.dig(:knowledge, :corpus_path) || ::Dir.pwd
204
+ else
205
+ ::Dir.pwd
206
+ end
207
+ end
208
+
209
+ def print_sources(sources, out, verbose:)
210
+ return out.warn('No sources found') if sources.empty?
211
+
212
+ out.header("Sources (#{sources.size})")
213
+ sources.each_with_index do |s, i|
214
+ score = format('%.2f', s[:score].to_f)
215
+ heading = s[:heading].to_s.empty? ? '' : " \u00a7 #{s[:heading]}"
216
+ puts " #{i + 1}. #{s[:source_file]}#{heading} score: #{score}"
217
+ puts " #{truncate(s[:content].to_s, 100)}" if verbose
218
+ end
219
+ end
220
+
221
+ def print_chunk_section(title, chunks, out)
222
+ out.header(title)
223
+ if chunks.empty?
224
+ out.warn(' (none)')
225
+ else
226
+ chunks.each do |c|
227
+ puts " id=#{c[:id]} confidence=#{c[:confidence]} #{c[:source_file]}"
228
+ end
229
+ end
230
+ out.spacer
231
+ end
232
+
233
+ def truncate(text, max)
234
+ return text if text.length <= max
235
+ return text[0, max] if max < 4
236
+
237
+ "#{text[0, max - 3]}..."
238
+ end
239
+ end
240
+ end
241
+ end
242
+ end
@@ -27,8 +27,24 @@ module Legion
27
27
 
28
28
  PACKS = {
29
29
  agentic: {
30
- description: 'Full cognitive stack: GAIA + LLM + MCP + Apollo',
31
- gems: %w[legion-gaia legion-llm]
30
+ description: 'Full cognitive stack: core libs, agentic domains, AI providers, and operational extensions',
31
+ gems: %w[
32
+ legion-apollo legion-gaia legion-llm legion-mcp legion-rbac
33
+ lex-acp lex-adapter lex-agentic-affect lex-agentic-attention
34
+ lex-agentic-defense lex-agentic-executive lex-agentic-homeostasis
35
+ lex-agentic-imagination lex-agentic-inference lex-agentic-integration
36
+ lex-agentic-language lex-agentic-learning lex-agentic-memory
37
+ lex-agentic-self lex-agentic-social lex-apollo lex-audit lex-autofix
38
+ lex-azure-ai lex-bedrock lex-claude lex-codegen lex-coldstart
39
+ lex-conditioner lex-cortex lex-cost-scanner lex-dataset lex-detect
40
+ lex-eval lex-exec lex-extinction lex-factory lex-finops lex-foundry
41
+ lex-gemini lex-governance lex-kerberos lex-knowledge lex-llm-gateway
42
+ lex-metering lex-mesh lex-microsoft_teams lex-mind-growth lex-node
43
+ lex-onboard lex-openai lex-pilot-infra-monitor
44
+ lex-pilot-knowledge-assist lex-privatecore lex-prompt lex-react
45
+ lex-swarm lex-swarm-github lex-synapse lex-telemetry lex-tick
46
+ lex-transformer lex-xai
47
+ ]
32
48
  },
33
49
  llm: {
34
50
  description: 'LLM routing and provider integration (no cognitive stack)',
@@ -114,6 +130,8 @@ module Legion
114
130
  def agentic
115
131
  install_pack(:agentic)
116
132
  end
133
+ map 'give-me-all-the-brains' => :agentic
134
+ map 'brains' => :agentic
117
135
 
118
136
  desc 'llm', 'Install LLM routing and provider integration'
119
137
  option :dry_run, type: :boolean, default: false, desc: 'Show what would be installed without installing'
data/lib/legion/cli.rb CHANGED
@@ -42,6 +42,7 @@ module Legion
42
42
  autoload :Eval, 'legion/cli/eval_command'
43
43
  autoload :Update, 'legion/cli/update_command'
44
44
  autoload :Init, 'legion/cli/init_command'
45
+ autoload :Knowledge, 'legion/cli/knowledge_command'
45
46
  autoload :Setup, 'legion/cli/setup_command'
46
47
  autoload :Skill, 'legion/cli/skill_command'
47
48
  autoload :Prompt, 'legion/cli/prompt_command'
@@ -256,6 +257,9 @@ module Legion
256
257
  desc 'apollo SUBCOMMAND', 'Apollo knowledge graph'
257
258
  subcommand 'apollo', Legion::CLI::Apollo
258
259
 
260
+ desc 'knowledge SUBCOMMAND', 'Search and manage the document knowledge base'
261
+ subcommand 'knowledge', Legion::CLI::Knowledge
262
+
259
263
  desc 'schedule SUBCOMMAND', 'Manage schedules'
260
264
  subcommand 'schedule', Legion::CLI::Schedule
261
265
 
@@ -348,7 +352,7 @@ module Legion
348
352
 
349
353
  desc 'tree', 'Print a tree of all available commands'
350
354
  def tree
351
- legion_print_command_tree(self.class, 'legion', '')
355
+ legion_print_command_tree(self.class, ::File.basename($PROGRAM_NAME), '')
352
356
  end
353
357
 
354
358
  desc 'ask TEXT', 'Quick AI prompt (shortcut for chat prompt)'
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Legion
4
- VERSION = '1.5.18'
4
+ VERSION = '1.5.21'
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.5.18
4
+ version: 1.5.21
5
5
  platform: ruby
6
6
  authors:
7
7
  - Esity
@@ -630,6 +630,7 @@ files:
630
630
  - lib/legion/cli/init/environment_detector.rb
631
631
  - lib/legion/cli/init_command.rb
632
632
  - lib/legion/cli/interactive.rb
633
+ - lib/legion/cli/knowledge_command.rb
633
634
  - lib/legion/cli/lex/actor.rb
634
635
  - lib/legion/cli/lex/exchange.rb
635
636
  - lib/legion/cli/lex/message.rb