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
|
@@ -16,6 +16,27 @@ module Rubik
|
|
|
16
16
|
|
|
17
17
|
def self.register(shell)
|
|
18
18
|
shell.register_command("history", description: "List recent session history", audience: :both) do |args, ctx|
|
|
19
|
+
run_history(args, ctx)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Plan 91 — explicit multi-line paste mode: collects lines until a
|
|
23
|
+
# lone "." line, then dispatches the joined text as one prompt turn.
|
|
24
|
+
shell.register_command("paste", description: "Multi-line paste mode (finish with lone '.')") do |_args, ctx|
|
|
25
|
+
run_paste(ctx)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
shell.register_command("replay", description: "Replay session file") do |args, ctx|
|
|
29
|
+
run_replay(args, ctx)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
shell.register_command("session", description: "Manage recorded sessions (list|resume|info|prune)",
|
|
33
|
+
usage: USAGE_SESSION) do |args, ctx|
|
|
34
|
+
dispatch_session(args, ctx)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
class << self
|
|
39
|
+
def run_history(args, ctx)
|
|
19
40
|
limit = (args.first || 20).to_i
|
|
20
41
|
hist = ctx.recorder.read_history(limit)
|
|
21
42
|
hist.each_with_index do |item, idx|
|
|
@@ -23,14 +44,12 @@ module Rubik
|
|
|
23
44
|
end
|
|
24
45
|
end
|
|
25
46
|
|
|
26
|
-
|
|
27
|
-
# lone "." line, then dispatches the joined text as one prompt turn.
|
|
28
|
-
shell.register_command("paste", description: "Multi-line paste mode (finish with lone '.')") do |_args, ctx|
|
|
47
|
+
def run_paste(ctx)
|
|
29
48
|
ctx.request_paste!
|
|
30
49
|
ctx.out.puts "[Rubik] Paste modu bir sonraki prompt'ta başlayacak."
|
|
31
50
|
end
|
|
32
51
|
|
|
33
|
-
|
|
52
|
+
def run_replay(args, ctx)
|
|
34
53
|
path = args.first
|
|
35
54
|
replayer = ctx.session_replayer
|
|
36
55
|
if replayer.replay_file(path)
|
|
@@ -40,77 +59,85 @@ module Rubik
|
|
|
40
59
|
end
|
|
41
60
|
end
|
|
42
61
|
|
|
43
|
-
|
|
44
|
-
usage: USAGE_SESSION) do |args, ctx|
|
|
62
|
+
def dispatch_session(args, ctx)
|
|
45
63
|
sub = args.first&.downcase || "list"
|
|
46
64
|
root_d = ctx.buffer_manager&.root_dir || Dir.pwd
|
|
47
65
|
home_d = ctx.config&.home_dir || ENV.fetch("HOME", nil)
|
|
48
66
|
|
|
49
67
|
case sub
|
|
50
68
|
when "list"
|
|
51
|
-
|
|
52
|
-
if sessions.empty?
|
|
53
|
-
ctx.out.puts "No recorded sessions found for this workspace."
|
|
54
|
-
else
|
|
55
|
-
ctx.out.puts "=== Recorded Sessions (#{sessions.size}) ==="
|
|
56
|
-
sessions.each_with_index do |s, idx|
|
|
57
|
-
mark = s[:path] == ctx.recorder.session_file ? " [CURRENT]" : ""
|
|
58
|
-
ctx.out.puts " #{idx + 1}. #{s[:id]} (#{s[:scope]}) - #{s[:event_count]} events - Last: \"#{s[:last_prompt]}\"#{mark}"
|
|
59
|
-
end
|
|
60
|
-
end
|
|
69
|
+
run_list(ctx, root_d, home_d)
|
|
61
70
|
when "resume", "continue", "load"
|
|
62
|
-
|
|
63
|
-
if target_id.nil?
|
|
64
|
-
picked = interactive_session_pick(ctx)
|
|
65
|
-
if picked == :picker_cancelled
|
|
66
|
-
ctx.out.puts "[Rubik::Session] Resume iptal edildi."
|
|
67
|
-
next
|
|
68
|
-
end
|
|
69
|
-
target_id = picked || :latest
|
|
70
|
-
end
|
|
71
|
-
res = ctx.resume_session(target_id)
|
|
72
|
-
ctx.out.puts "Resume failed: #{res[:error]}" unless res[:ok]
|
|
71
|
+
run_resume(args[1], ctx)
|
|
73
72
|
when "info"
|
|
74
|
-
ctx
|
|
75
|
-
ctx.out.puts "Total Events in Session: #{ctx.recorder.events.size}"
|
|
73
|
+
run_info(ctx)
|
|
76
74
|
when "prune", "clean", "cleanup"
|
|
77
75
|
run_prune(args.drop(1), ctx)
|
|
78
76
|
when "save"
|
|
79
|
-
|
|
80
|
-
if target_name.nil? || target_name.strip.empty?
|
|
81
|
-
ctx.out.puts "Usage: !session save <name>"
|
|
82
|
-
else
|
|
83
|
-
safe_name = File.basename(target_name.strip.sub(/\.jsonl$/, ""))
|
|
84
|
-
if safe_name.empty? || safe_name.start_with?(".")
|
|
85
|
-
ctx.out.puts "Error: Invalid session name '#{target_name}'."
|
|
86
|
-
next
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
target_dir = File.join(root_d, ".rubik", "sessions")
|
|
90
|
-
FileUtils.mkdir_p(target_dir)
|
|
91
|
-
target_file = File.join(target_dir, "#{safe_name}.jsonl")
|
|
92
|
-
|
|
93
|
-
if ctx.recorder.session_file && File.exist?(ctx.recorder.session_file)
|
|
94
|
-
FileUtils.cp(ctx.recorder.session_file, target_file)
|
|
95
|
-
else
|
|
96
|
-
File.open(target_file, "w") do |f|
|
|
97
|
-
ctx.recorder.events.each { |ev| f.puts(JSON.dump(ev)) }
|
|
98
|
-
end
|
|
99
|
-
end
|
|
100
|
-
ctx.recorder.switch_session_file(target_file, cleanup_empty_current: false)
|
|
101
|
-
ctx.out.puts "[Rubik::Session] Session saved to #{target_file}"
|
|
102
|
-
end
|
|
77
|
+
run_save(args[1], ctx, root_d)
|
|
103
78
|
else
|
|
104
79
|
ctx.out.puts USAGE_SESSION
|
|
105
80
|
end
|
|
106
81
|
end
|
|
107
|
-
end
|
|
108
82
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
83
|
+
def run_list(ctx, root_d, home_d)
|
|
84
|
+
sessions = Rubik::Session::Manager.list_sessions(root_d, home_d, config: ctx.config)
|
|
85
|
+
if sessions.empty?
|
|
86
|
+
ctx.out.puts "No recorded sessions found for this workspace."
|
|
87
|
+
else
|
|
88
|
+
ctx.out.puts "=== Recorded Sessions (#{sessions.size}) ==="
|
|
89
|
+
sessions.each_with_index do |s, idx|
|
|
90
|
+
mark = s[:path] == ctx.recorder.session_file ? " [CURRENT]" : ""
|
|
91
|
+
ctx.out.puts " #{idx + 1}. #{s[:id]} (#{s[:scope]}) - #{s[:event_count]} events - Last: \"#{s[:last_prompt]}\"#{mark}"
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def run_resume(target_id, ctx)
|
|
97
|
+
if target_id.nil?
|
|
98
|
+
picked = interactive_session_pick(ctx)
|
|
99
|
+
if picked == :picker_cancelled
|
|
100
|
+
ctx.out.puts "[Rubik::Session] Resume iptal edildi."
|
|
101
|
+
return
|
|
102
|
+
end
|
|
103
|
+
target_id = picked || :latest
|
|
104
|
+
end
|
|
105
|
+
res = ctx.resume_session(target_id)
|
|
106
|
+
ctx.out.puts "Resume failed: #{res[:error]}" unless res[:ok]
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def run_info(ctx)
|
|
110
|
+
ctx.out.puts "Active Session File: #{ctx.recorder.session_file}"
|
|
111
|
+
ctx.out.puts "Total Events in Session: #{ctx.recorder.events.size}"
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def run_save(target_name, ctx, root_d)
|
|
115
|
+
if target_name.nil? || target_name.strip.empty?
|
|
116
|
+
ctx.out.puts "Usage: !session save <name>"
|
|
117
|
+
return
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
safe_name = File.basename(target_name.strip.sub(/\.jsonl$/, ""))
|
|
121
|
+
if safe_name.empty? || safe_name.start_with?(".")
|
|
122
|
+
ctx.out.puts "Error: Invalid session name '#{target_name}'."
|
|
123
|
+
return
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
target_dir = File.join(root_d, ".rubik", "sessions")
|
|
127
|
+
FileUtils.mkdir_p(target_dir)
|
|
128
|
+
target_file = File.join(target_dir, "#{safe_name}.jsonl")
|
|
129
|
+
|
|
130
|
+
if ctx.recorder.session_file && File.exist?(ctx.recorder.session_file)
|
|
131
|
+
FileUtils.cp(ctx.recorder.session_file, target_file)
|
|
132
|
+
else
|
|
133
|
+
File.open(target_file, "w") do |f|
|
|
134
|
+
ctx.recorder.events.each { |ev| f.puts(JSON.dump(ev)) }
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
ctx.recorder.switch_session_file(target_file, cleanup_empty_current: false)
|
|
138
|
+
ctx.out.puts "[Rubik::Session] Session saved to #{target_file}"
|
|
139
|
+
end
|
|
140
|
+
|
|
114
141
|
# `!session prune [days] [--apply] [--all]` — dry-run by default:
|
|
115
142
|
# prints the blast radius of files that WOULD be deleted and only
|
|
116
143
|
# unlinks once the operator passes --apply. The active session file
|
|
@@ -154,6 +181,10 @@ module Rubik
|
|
|
154
181
|
days == days.to_i ? "#{days.to_i}d" : "#{days.round(1)}d"
|
|
155
182
|
end
|
|
156
183
|
|
|
184
|
+
# Interactive session picker (Antigravity /resume style): arrow-key
|
|
185
|
+
# selection over recorded sessions on capable TTYs. Returns the
|
|
186
|
+
# session id, :picker_cancelled on Ctrl+C (caller aborts the
|
|
187
|
+
# resume), or nil in pipes/tests so `resume` falls back to :latest.
|
|
157
188
|
def interactive_session_pick(ctx)
|
|
158
189
|
return nil unless Rubik::UI::Capability.interactive?(input: ctx.input, output: ctx.output)
|
|
159
190
|
|