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 +4 -4
- data/CHANGELOG.md +22 -0
- data/lib/legion/cli/knowledge_command.rb +242 -0
- data/lib/legion/cli/setup_command.rb +20 -2
- data/lib/legion/cli.rb +5 -1
- data/lib/legion/version.rb +1 -1
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ff2178b95e28c31128ad05f307d30f9cc4eb805210144dd7b579e3e526046045
|
|
4
|
+
data.tar.gz: d49fdcfa4b32c25cb0757abdac503e26cac31b5f134e4764219d79afe7b1141b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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:
|
|
31
|
-
gems: %w[
|
|
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,
|
|
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)'
|
data/lib/legion/version.rb
CHANGED
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.
|
|
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
|