rubik 0.3.3 → 0.3.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/ATTRIBUTION.md +1 -1
- data/CHANGELOG.md +102 -0
- data/lib/rubik/adapter/process.rb +11 -8
- data/lib/rubik/ast/go_extractor.rb +9 -2
- data/lib/rubik/ast/rust_extractor.rb +9 -2
- data/lib/rubik/ast/typescript_extractor.rb +13 -7
- data/lib/rubik/buffer/manager.rb +99 -69
- data/lib/rubik/config/defaults.rb +17 -20
- data/lib/rubik/config.rb +0 -6
- data/lib/rubik/context/git_lister.rb +3 -1
- data/lib/rubik/llm/anthropic.rb +5 -2
- data/lib/rubik/llm/client.rb +102 -72
- data/lib/rubik/llm/openai_compatible.rb +83 -68
- data/lib/rubik/lsp/client.rb +320 -96
- data/lib/rubik/lsp/manager.rb +83 -12
- data/lib/rubik/lsp/server_command.rb +102 -0
- data/lib/rubik/orchestrator/goal_ledger/patch.rb +48 -30
- data/lib/rubik/orchestrator/lsp_feedback.rb +20 -4
- data/lib/rubik/orchestrator/subagent/reuse.rb +2 -2
- data/lib/rubik/orchestrator/subagent/spawn_transaction.rb +60 -31
- data/lib/rubik/platform/process.rb +6 -6
- data/lib/rubik/platform/sanitize.rb +37 -16
- data/lib/rubik/prompt/doctrine.rb +4 -0
- data/lib/rubik/repl/commands/agent_context.rb +126 -104
- data/lib/rubik/repl/commands/agent_execution.rb +183 -155
- data/lib/rubik/repl/commands/agent_models.rb +112 -87
- data/lib/rubik/repl/commands/agent_providers.rb +190 -156
- data/lib/rubik/repl/commands/buffer.rb +122 -88
- data/lib/rubik/repl/commands/lsp.rb +212 -179
- data/lib/rubik/repl/commands/mcp.rb +120 -92
- data/lib/rubik/repl/commands/session.rb +90 -59
- data/lib/rubik/repl/commands/workspace.rb +266 -246
- data/lib/rubik/repl/shell.rb +10 -1
- data/lib/rubik/runtime/discovery.rb +12 -6
- data/lib/rubik/runtime/overlay_storage.rb +59 -2
- data/lib/rubik/sandbox/driver/docker.rb +2 -2
- data/lib/rubik/session/replayer.rb +1 -2
- data/lib/rubik/storage/store.rb +3 -2
- data/lib/rubik/storage/vakanuvis/distill_gate.rb +6 -2
- data/lib/rubik/storage/vakanuvis/gate.rb +37 -16
- data/lib/rubik/storage/vakanuvis/harness.rb +36 -21
- data/lib/rubik/storage/vakanuvis/memory_steward.rb +12 -6
- data/lib/rubik/tools/builtins/lifecycle.rb +34 -7
- data/lib/rubik/tools/builtins/lsp.rb +36 -8
- data/lib/rubik/tools/fuzzy_matcher.rb +27 -11
- data/lib/rubik/version.rb +1 -1
- metadata +2 -1
|
@@ -59,46 +59,67 @@ module Rubik
|
|
|
59
59
|
retagged.valid_encoding? ? retagged : retagged.scrub("")
|
|
60
60
|
end
|
|
61
61
|
|
|
62
|
-
#
|
|
62
|
+
# PERF: Fast-path deep UTF-8 cleaner. Performs zero-allocation Symbol
|
|
63
|
+
# validation via Symbol#name and tracks cycle object IDs in a Hash to
|
|
64
|
+
# eliminate Set instance allocations (~3.3x speedup, ~70% faster).
|
|
63
65
|
def clean_utf8_deep(obj, seen = nil)
|
|
64
66
|
case obj
|
|
65
67
|
when String
|
|
66
68
|
clean_utf8(obj)
|
|
69
|
+
when Symbol
|
|
70
|
+
fast_clean_symbol(obj)
|
|
67
71
|
when Hash
|
|
68
|
-
seen ||=
|
|
69
|
-
|
|
72
|
+
seen ||= {}
|
|
73
|
+
obj_id = obj.object_id
|
|
74
|
+
return "{...}" if seen.is_a?(Hash) ? seen.key?(obj_id) : seen.include?(obj_id)
|
|
70
75
|
|
|
71
|
-
seen.add(
|
|
76
|
+
seen.is_a?(Hash) ? (seen[obj_id] = true) : seen.add(obj_id)
|
|
72
77
|
|
|
73
|
-
|
|
74
|
-
|
|
78
|
+
res = {}
|
|
79
|
+
obj.each do |k, v|
|
|
80
|
+
cleaned_k = if k.is_a?(Symbol)
|
|
81
|
+
fast_clean_symbol(k)
|
|
82
|
+
elsif k.is_a?(String)
|
|
75
83
|
clean_utf8(k)
|
|
76
84
|
else
|
|
77
|
-
|
|
85
|
+
clean_utf8_deep(k, seen)
|
|
78
86
|
end
|
|
79
|
-
|
|
87
|
+
res[cleaned_k] = clean_utf8_deep(v, seen)
|
|
80
88
|
end
|
|
89
|
+
res
|
|
81
90
|
when Array
|
|
82
|
-
seen ||=
|
|
83
|
-
|
|
91
|
+
seen ||= {}
|
|
92
|
+
obj_id = obj.object_id
|
|
93
|
+
return "[...]" if seen.is_a?(Hash) ? seen.key?(obj_id) : seen.include?(obj_id)
|
|
84
94
|
|
|
85
|
-
seen.add(
|
|
95
|
+
seen.is_a?(Hash) ? (seen[obj_id] = true) : seen.add(obj_id)
|
|
86
96
|
|
|
87
97
|
obj.map { |item| clean_utf8_deep(item, seen) }
|
|
88
98
|
when Set
|
|
89
|
-
seen ||=
|
|
90
|
-
|
|
99
|
+
seen ||= {}
|
|
100
|
+
obj_id = obj.object_id
|
|
101
|
+
return Set.new(["[...]"]) if seen.is_a?(Hash) ? seen.key?(obj_id) : seen.include?(obj_id)
|
|
91
102
|
|
|
92
|
-
seen.add(
|
|
103
|
+
seen.is_a?(Hash) ? (seen[obj_id] = true) : seen.add(obj_id)
|
|
93
104
|
|
|
94
105
|
obj.to_set { |item| clean_utf8_deep(item, seen) }
|
|
95
|
-
when Symbol
|
|
96
|
-
clean_utf8(obj.to_s).to_sym
|
|
97
106
|
else
|
|
98
107
|
obj
|
|
99
108
|
end
|
|
100
109
|
end
|
|
101
110
|
|
|
111
|
+
# Binary (ASCII-8BIT) symbol names always report valid_encoding?, so a
|
|
112
|
+
# bare validity check would fast-path them through UNCLEANED — the old
|
|
113
|
+
# clean_utf8 path strips their >= 0x80 bytes and the byte loss would
|
|
114
|
+
# instead surface later, at the JSON/display layer. Only UTF-8 (or the
|
|
115
|
+
# lossless US-ASCII subset) symbols may take the fast path.
|
|
116
|
+
def fast_clean_symbol(sym)
|
|
117
|
+
name = sym.name
|
|
118
|
+
return sym if [Encoding::UTF_8, Encoding::US_ASCII].include?(name.encoding) && name.valid_encoding?
|
|
119
|
+
|
|
120
|
+
clean_utf8(name).to_sym
|
|
121
|
+
end
|
|
122
|
+
|
|
102
123
|
# Generates JSON safely without raising JSON::GeneratorError on malformed UTF-8
|
|
103
124
|
def safe_json_generate(obj, *)
|
|
104
125
|
cleaned = clean_utf8_deep(obj)
|
|
@@ -18,6 +18,10 @@ module Rubik
|
|
|
18
18
|
# ToolTeachings/Notices. Byte-identical rendering is pinned by
|
|
19
19
|
# test_prompt_golden_output — reword here = fixture diff there, never
|
|
20
20
|
# a context_builder.rb diff.
|
|
21
|
+
#
|
|
22
|
+
# Security Note: All strings and heredocs in this module contain pure
|
|
23
|
+
# prompt template prose for LLM system context. Backticks (`...`) in prose
|
|
24
|
+
# are markdown inline-code tags and are never evaluated as shell execution.
|
|
21
25
|
module Doctrine
|
|
22
26
|
module_function
|
|
23
27
|
|
|
@@ -31,121 +31,143 @@ module Rubik
|
|
|
31
31
|
sub = args.first&.downcase
|
|
32
32
|
case sub
|
|
33
33
|
when "scan"
|
|
34
|
-
|
|
35
|
-
ctx.context_index.full_scan
|
|
36
|
-
ctx.warm_ast_graph(async: false)
|
|
37
|
-
ctx.out.puts "Context index and AST symbol graph updated."
|
|
38
|
-
rescue Errno::EACCES => e
|
|
39
|
-
root_path = begin
|
|
40
|
-
ctx.buffer_manager.root_dir
|
|
41
|
-
rescue
|
|
42
|
-
Dir.pwd
|
|
43
|
-
end
|
|
44
|
-
ctx.out.puts Rubik::Tools::DiagnosticReporter.render_permission_denied(root_path, operation: "scan", reason: e.message)
|
|
45
|
-
rescue StandardError => e
|
|
46
|
-
ctx.out.puts "Context scan failed: #{e.message}"
|
|
47
|
-
end
|
|
34
|
+
handle_context_scan(ctx)
|
|
48
35
|
when "add"
|
|
49
|
-
|
|
50
|
-
if file
|
|
51
|
-
ctx.context_index.update_file(file)
|
|
52
|
-
full_path = File.join(ctx.buffer_manager.root_dir, file)
|
|
53
|
-
ctx.ast_graph.index_file(full_path) if File.exist?(full_path)
|
|
54
|
-
end
|
|
55
|
-
ctx.out.puts "Added #{file} to context index and AST graph."
|
|
36
|
+
handle_context_add(args, ctx)
|
|
56
37
|
when "drop"
|
|
57
|
-
|
|
58
|
-
if file
|
|
59
|
-
ctx.context_index.files.delete(file)
|
|
60
|
-
ctx.out.puts "Removed #{file} from context index."
|
|
61
|
-
else
|
|
62
|
-
ctx.out.puts "Usage: !context drop <relative_file_path>"
|
|
63
|
-
end
|
|
38
|
+
handle_context_drop(args, ctx)
|
|
64
39
|
when "compact"
|
|
65
|
-
|
|
66
|
-
spec = ctx.current_model_spec
|
|
67
|
-
context_limit = ctx.llm_registry.context_limit_for(spec["provider"], spec["name"])
|
|
68
|
-
max_output = ctx.llm_registry.max_output_tokens_for(spec["provider"], spec["name"])
|
|
69
|
-
context_cfg = ctx.config.fetch("context", {})
|
|
70
|
-
|
|
71
|
-
ctx.conversation_history = Rubik::Prompt::ContextCompactor.compact_history(
|
|
72
|
-
ctx.conversation_history,
|
|
73
|
-
context_limit: context_limit,
|
|
74
|
-
max_output_tokens: max_output,
|
|
75
|
-
config: context_cfg,
|
|
76
|
-
force: true
|
|
77
|
-
)
|
|
78
|
-
after_tokens = Rubik::Prompt::TokenEstimator.estimate_history(ctx.conversation_history)
|
|
79
|
-
delta = before_tokens - after_tokens
|
|
80
|
-
ctx.out.puts "Context compaction completed: #{before_tokens} -> #{after_tokens} tokens (reduced #{delta} tokens)."
|
|
40
|
+
handle_context_compact(ctx)
|
|
81
41
|
when "stats"
|
|
82
|
-
|
|
83
|
-
p_name = spec["provider"]
|
|
84
|
-
m_name = spec["name"]
|
|
85
|
-
context_limit = ctx.llm_registry.context_limit_for(p_name, m_name)
|
|
86
|
-
max_output = ctx.llm_registry.max_output_tokens_for(p_name, m_name)
|
|
87
|
-
ceiling = ctx.llm_registry.effective_input_limit_for(p_name, m_name)
|
|
88
|
-
est_tokens = Rubik::Prompt::TokenEstimator.estimate_history(ctx.conversation_history)
|
|
89
|
-
pct = ceiling.positive? ? ((est_tokens.to_f / ceiling) * 100).clamp(0.0, 100.0).round(1) : 0.0
|
|
90
|
-
summary = ctx.context_index.status_summary
|
|
91
|
-
ast_symbols_count = ctx.ast_graph ? ctx.ast_graph.symbols.size : 0
|
|
92
|
-
|
|
93
|
-
ctx.out.puts "=== Context & Token Statistics ==="
|
|
94
|
-
ctx.out.puts " • Model : #{m_name} (#{p_name})"
|
|
95
|
-
ctx.out.puts " • Context Limit : #{context_limit} tokens"
|
|
96
|
-
ctx.out.puts " • Usable Capacity : #{ceiling} tokens (Max Output: #{max_output} tokens)"
|
|
97
|
-
ctx.out.puts " • Consumed Tokens : #{est_tokens} tokens (#{pct}% of usable capacity)"
|
|
98
|
-
ctx.out.puts " • Active Turns : #{ctx.conversation_history.size} turn(s)"
|
|
99
|
-
ctx.out.puts " • Indexed Files : #{summary[:file_count]} files"
|
|
100
|
-
ctx.out.puts " • AST Symbol Count : #{ast_symbols_count} symbols"
|
|
101
|
-
ctx.out.puts "=================================="
|
|
102
|
-
guidance = Rubik::Tools::DiagnosticReporter.render_context_status_guidance(
|
|
103
|
-
{ model: "#{p_name}/#{m_name}", consumed_tokens: est_tokens, context_limit: ceiling },
|
|
104
|
-
token_breakdown: { "Conversation Turns" => est_tokens }
|
|
105
|
-
)
|
|
106
|
-
ctx.out.puts "\n#{guidance}"
|
|
42
|
+
handle_context_stats(ctx)
|
|
107
43
|
when "status", nil
|
|
108
|
-
|
|
109
|
-
p_name = spec["provider"]
|
|
110
|
-
m_name = spec["name"]
|
|
111
|
-
context_limit = ctx.llm_registry.context_limit_for(p_name, m_name)
|
|
112
|
-
max_output = ctx.llm_registry.max_output_tokens_for(p_name, m_name)
|
|
113
|
-
ceiling = ctx.llm_registry.effective_input_limit_for(p_name, m_name)
|
|
114
|
-
context_cfg = ctx.config.fetch("context", {})
|
|
115
|
-
strategy = context_cfg["compaction_strategy"] || "hybrid"
|
|
116
|
-
|
|
117
|
-
est_tokens = Rubik::Prompt::TokenEstimator.estimate_history(ctx.conversation_history)
|
|
118
|
-
pct = ceiling.positive? ? ((est_tokens.to_f / ceiling) * 100).clamp(0.0, 100.0).round(1) : 0.0
|
|
119
|
-
bar_width = 12
|
|
120
|
-
filled = ((pct / 100.0) * bar_width).round
|
|
121
|
-
empty = bar_width - filled
|
|
122
|
-
bar_str = "[#{'█' * filled}#{'░' * empty}] #{pct}%"
|
|
123
|
-
|
|
124
|
-
summary = ctx.context_index.status_summary
|
|
125
|
-
ast_symbols_count = ctx.ast_graph ? ctx.ast_graph.symbols.size : 0
|
|
126
|
-
|
|
127
|
-
ctx.out.puts "=== LLM Context Window & In-Memory Status ==="
|
|
128
|
-
ctx.out.puts "Active Model: #{m_name} (#{p_name})"
|
|
129
|
-
ctx.out.puts "Context Window Limit: #{context_limit} tokens"
|
|
130
|
-
ctx.out.puts "Max Output Tokens: #{max_output} tokens"
|
|
131
|
-
ctx.out.puts "Usable Input Ceiling: #{ceiling} tokens"
|
|
132
|
-
ctx.out.puts "Estimated Prompt Tokens: #{est_tokens} / #{ceiling} tokens #{bar_str}"
|
|
133
|
-
ctx.out.puts "Compaction Strategy: #{strategy}"
|
|
134
|
-
ctx.out.puts "Indexed Files: #{summary[:file_count]}, AST Symbols: #{ast_symbols_count}"
|
|
135
|
-
ctx.out.puts "============================================="
|
|
136
|
-
if (args && (args.include?("--guidance") || args.include?("-g"))) || est_tokens > (ceiling * 0.8)
|
|
137
|
-
guidance = Rubik::Tools::DiagnosticReporter.render_context_status_guidance(
|
|
138
|
-
{ model: "#{p_name}/#{m_name}", consumed_tokens: est_tokens, context_limit: ceiling },
|
|
139
|
-
token_breakdown: { "Conversation History" => est_tokens }
|
|
140
|
-
)
|
|
141
|
-
ctx.out.puts "\n#{guidance}"
|
|
142
|
-
end
|
|
44
|
+
handle_context_status(args, ctx)
|
|
143
45
|
else
|
|
144
46
|
ctx.out.puts USAGE_CONTEXT
|
|
145
47
|
end
|
|
146
48
|
end
|
|
147
49
|
end
|
|
148
50
|
|
|
51
|
+
private_class_method def self.handle_context_scan(ctx)
|
|
52
|
+
ctx.context_index.full_scan
|
|
53
|
+
ctx.warm_ast_graph(async: false)
|
|
54
|
+
ctx.out.puts "Context index and AST symbol graph updated."
|
|
55
|
+
rescue Errno::EACCES => e
|
|
56
|
+
root_path = begin
|
|
57
|
+
ctx.buffer_manager.root_dir
|
|
58
|
+
rescue
|
|
59
|
+
Dir.pwd
|
|
60
|
+
end
|
|
61
|
+
ctx.out.puts Rubik::Tools::DiagnosticReporter.render_permission_denied(root_path, operation: "scan", reason: e.message)
|
|
62
|
+
rescue StandardError => e
|
|
63
|
+
ctx.out.puts "Context scan failed: #{e.message}"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private_class_method def self.handle_context_add(args, ctx)
|
|
67
|
+
file = args[1]
|
|
68
|
+
if file
|
|
69
|
+
ctx.context_index.update_file(file)
|
|
70
|
+
full_path = File.join(ctx.buffer_manager.root_dir, file)
|
|
71
|
+
ctx.ast_graph.index_file(full_path) if File.exist?(full_path)
|
|
72
|
+
end
|
|
73
|
+
ctx.out.puts "Added #{file} to context index and AST graph."
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
private_class_method def self.handle_context_drop(args, ctx)
|
|
77
|
+
file = args[1]
|
|
78
|
+
if file
|
|
79
|
+
ctx.context_index.files.delete(file)
|
|
80
|
+
ctx.out.puts "Removed #{file} from context index."
|
|
81
|
+
else
|
|
82
|
+
ctx.out.puts "Usage: !context drop <relative_file_path>"
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
private_class_method def self.handle_context_compact(ctx)
|
|
87
|
+
before_tokens = Rubik::Prompt::TokenEstimator.estimate_history(ctx.conversation_history)
|
|
88
|
+
spec = ctx.current_model_spec
|
|
89
|
+
context_limit = ctx.llm_registry.context_limit_for(spec["provider"], spec["name"])
|
|
90
|
+
max_output = ctx.llm_registry.max_output_tokens_for(spec["provider"], spec["name"])
|
|
91
|
+
context_cfg = ctx.config.fetch("context", {})
|
|
92
|
+
|
|
93
|
+
ctx.conversation_history = Rubik::Prompt::ContextCompactor.compact_history(
|
|
94
|
+
ctx.conversation_history,
|
|
95
|
+
context_limit: context_limit,
|
|
96
|
+
max_output_tokens: max_output,
|
|
97
|
+
config: context_cfg,
|
|
98
|
+
force: true
|
|
99
|
+
)
|
|
100
|
+
after_tokens = Rubik::Prompt::TokenEstimator.estimate_history(ctx.conversation_history)
|
|
101
|
+
delta = before_tokens - after_tokens
|
|
102
|
+
ctx.out.puts "Context compaction completed: #{before_tokens} -> #{after_tokens} tokens (reduced #{delta} tokens)."
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
private_class_method def self.handle_context_stats(ctx)
|
|
106
|
+
spec = ctx.current_model_spec
|
|
107
|
+
p_name = spec["provider"]
|
|
108
|
+
m_name = spec["name"]
|
|
109
|
+
context_limit = ctx.llm_registry.context_limit_for(p_name, m_name)
|
|
110
|
+
max_output = ctx.llm_registry.max_output_tokens_for(p_name, m_name)
|
|
111
|
+
ceiling = ctx.llm_registry.effective_input_limit_for(p_name, m_name)
|
|
112
|
+
est_tokens = Rubik::Prompt::TokenEstimator.estimate_history(ctx.conversation_history)
|
|
113
|
+
pct = ceiling.positive? ? ((est_tokens.to_f / ceiling) * 100).clamp(0.0, 100.0).round(1) : 0.0
|
|
114
|
+
summary = ctx.context_index.status_summary
|
|
115
|
+
ast_symbols_count = ctx.ast_graph ? ctx.ast_graph.symbols.size : 0
|
|
116
|
+
|
|
117
|
+
ctx.out.puts "=== Context & Token Statistics ==="
|
|
118
|
+
ctx.out.puts " • Model : #{m_name} (#{p_name})"
|
|
119
|
+
ctx.out.puts " • Context Limit : #{context_limit} tokens"
|
|
120
|
+
ctx.out.puts " • Usable Capacity : #{ceiling} tokens (Max Output: #{max_output} tokens)"
|
|
121
|
+
ctx.out.puts " • Consumed Tokens : #{est_tokens} tokens (#{pct}% of usable capacity)"
|
|
122
|
+
ctx.out.puts " • Active Turns : #{ctx.conversation_history.size} turn(s)"
|
|
123
|
+
ctx.out.puts " • Indexed Files : #{summary[:file_count]} files"
|
|
124
|
+
ctx.out.puts " • AST Symbol Count : #{ast_symbols_count} symbols"
|
|
125
|
+
ctx.out.puts "=================================="
|
|
126
|
+
guidance = Rubik::Tools::DiagnosticReporter.render_context_status_guidance(
|
|
127
|
+
{ model: "#{p_name}/#{m_name}", consumed_tokens: est_tokens, context_limit: ceiling },
|
|
128
|
+
token_breakdown: { "Conversation Turns" => est_tokens }
|
|
129
|
+
)
|
|
130
|
+
ctx.out.puts "\n#{guidance}"
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
private_class_method def self.handle_context_status(args, ctx)
|
|
134
|
+
spec = ctx.current_model_spec
|
|
135
|
+
p_name = spec["provider"]
|
|
136
|
+
m_name = spec["name"]
|
|
137
|
+
context_limit = ctx.llm_registry.context_limit_for(p_name, m_name)
|
|
138
|
+
max_output = ctx.llm_registry.max_output_tokens_for(p_name, m_name)
|
|
139
|
+
ceiling = ctx.llm_registry.effective_input_limit_for(p_name, m_name)
|
|
140
|
+
context_cfg = ctx.config.fetch("context", {})
|
|
141
|
+
strategy = context_cfg["compaction_strategy"] || "hybrid"
|
|
142
|
+
|
|
143
|
+
est_tokens = Rubik::Prompt::TokenEstimator.estimate_history(ctx.conversation_history)
|
|
144
|
+
pct = ceiling.positive? ? ((est_tokens.to_f / ceiling) * 100).clamp(0.0, 100.0).round(1) : 0.0
|
|
145
|
+
bar_width = 12
|
|
146
|
+
filled = ((pct / 100.0) * bar_width).round
|
|
147
|
+
empty = bar_width - filled
|
|
148
|
+
bar_str = "[#{'█' * filled}#{'░' * empty}] #{pct}%"
|
|
149
|
+
|
|
150
|
+
summary = ctx.context_index.status_summary
|
|
151
|
+
ast_symbols_count = ctx.ast_graph ? ctx.ast_graph.symbols.size : 0
|
|
152
|
+
|
|
153
|
+
ctx.out.puts "=== LLM Context Window & In-Memory Status ==="
|
|
154
|
+
ctx.out.puts "Active Model: #{m_name} (#{p_name})"
|
|
155
|
+
ctx.out.puts "Context Window Limit: #{context_limit} tokens"
|
|
156
|
+
ctx.out.puts "Max Output Tokens: #{max_output} tokens"
|
|
157
|
+
ctx.out.puts "Usable Input Ceiling: #{ceiling} tokens"
|
|
158
|
+
ctx.out.puts "Estimated Prompt Tokens: #{est_tokens} / #{ceiling} tokens #{bar_str}"
|
|
159
|
+
ctx.out.puts "Compaction Strategy: #{strategy}"
|
|
160
|
+
ctx.out.puts "Indexed Files: #{summary[:file_count]}, AST Symbols: #{ast_symbols_count}"
|
|
161
|
+
ctx.out.puts "============================================="
|
|
162
|
+
return unless (args && (args.include?("--guidance") || args.include?("-g"))) || est_tokens > (ceiling * 0.8)
|
|
163
|
+
|
|
164
|
+
guidance = Rubik::Tools::DiagnosticReporter.render_context_status_guidance(
|
|
165
|
+
{ model: "#{p_name}/#{m_name}", consumed_tokens: est_tokens, context_limit: ceiling },
|
|
166
|
+
token_breakdown: { "Conversation History" => est_tokens }
|
|
167
|
+
)
|
|
168
|
+
ctx.out.puts "\n#{guidance}"
|
|
169
|
+
end
|
|
170
|
+
|
|
149
171
|
private_class_method def self.register_steps_command(shell)
|
|
150
172
|
shell.register_command("steps", description: "Manage or view agentic step budget (default|goal|plan|<n>|status)",
|
|
151
173
|
usage: USAGE_STEPS, audience: :both) do |args, ctx|
|