rubik 0.4.0 → 0.4.2
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 +118 -1
- data/lib/rubik/ast/c_extractor.rb +1 -1
- data/lib/rubik/ast/cache_store.rb +36 -5
- data/lib/rubik/ast/extraction_result.rb +6 -0
- data/lib/rubik/ast/fallback_cleaner.rb +3 -1
- data/lib/rubik/ast/graph.rb +31 -5
- data/lib/rubik/ast/ruby_extractor.rb +8 -2
- data/lib/rubik/ast/rust_extractor.rb +1 -1
- data/lib/rubik/ast/typescript_extractor.rb +3 -3
- data/lib/rubik/buffer/apply_contract.rb +6 -0
- data/lib/rubik/buffer/manager.rb +37 -27
- data/lib/rubik/diagnostics/parser.rb +16 -16
- data/lib/rubik/llm/openai_compatible.rb +2 -1
- data/lib/rubik/lsp/manager.rb +15 -5
- data/lib/rubik/mcp/client.rb +4 -0
- data/lib/rubik/mcp/manager.rb +51 -35
- data/lib/rubik/mcp/proxy_tool.rb +17 -0
- data/lib/rubik/mcp/transport/stdio.rb +142 -17
- data/lib/rubik/orchestrator/step_executor.rb +1 -1
- data/lib/rubik/orchestrator/subagent/completion.rb +1 -0
- data/lib/rubik/orchestrator/subagent/dependency.rb +1 -0
- data/lib/rubik/orchestrator/subagent/supervisor.rb +20 -3
- data/lib/rubik/orchestrator/subagent/waiting.rb +16 -9
- data/lib/rubik/orchestrator/turn_executor/subagent_channel.rb +1 -1
- data/lib/rubik/orchestrator/turn_executor/user_channel.rb +2 -2
- data/lib/rubik/orchestrator/turn_executor.rb +3 -2
- data/lib/rubik/platform/path.rb +2 -1
- data/lib/rubik/platform/sanitize.rb +4 -1
- data/lib/rubik/plugin/manager.rb +39 -15
- data/lib/rubik/plugin/package_inventory.rb +11 -1
- data/lib/rubik/plugin/storage.rb +5 -4
- data/lib/rubik/repair/loop.rb +29 -15
- data/lib/rubik/repl/step_formatter.rb +2 -1
- data/lib/rubik/runtime/diagnostic_engine.rb +1 -1
- data/lib/rubik/security/blocked_guidance.rb +1 -1
- data/lib/rubik/security/redactor.rb +3 -3
- data/lib/rubik/storage/layout.rb +3 -1
- data/lib/rubik/storage/render.rb +1 -1
- data/lib/rubik/storage/vakanuvis/vocabulary.rb +36 -2
- data/lib/rubik/tools/base.rb +2 -1
- data/lib/rubik/tools/builtins/lifecycle.rb +42 -47
- data/lib/rubik/ui/capability.rb +19 -5
- data/lib/rubik/ui/renderer.rb +1 -0
- data/lib/rubik/version.rb +1 -1
- metadata +1 -1
|
@@ -21,8 +21,7 @@ module Rubik
|
|
|
21
21
|
type: "object",
|
|
22
22
|
properties: {
|
|
23
23
|
reason: { type: "string", description: "Optional explanation of why buffer is being applied (e.g. 'prior to running tests')" },
|
|
24
|
-
force: { type: "boolean",
|
|
25
|
-
description: "If true, overwrites disk files even if modified externally by formatters (resolves [CONFLICT])" }
|
|
24
|
+
force: { type: "boolean", description: "If true, overwrites disk files even if modified externally by formatters (resolves [CONFLICT])" }
|
|
26
25
|
}
|
|
27
26
|
}
|
|
28
27
|
)
|
|
@@ -42,8 +41,7 @@ module Rubik
|
|
|
42
41
|
"(buffer.model_can_apply: false). The human operator will review and apply changes.")
|
|
43
42
|
end
|
|
44
43
|
|
|
45
|
-
force = [
|
|
46
|
-
[true, "true"].include?(args["overwrite"]) || [true, "true"].include?(args[:overwrite])
|
|
44
|
+
force = [args["force"], args[:force], args["overwrite"], args[:overwrite]].intersect?([true, "true"])
|
|
47
45
|
policy = force ? "overwrite" : nil
|
|
48
46
|
|
|
49
47
|
if @buffer_manager.dirty?
|
|
@@ -66,16 +64,24 @@ module Rubik
|
|
|
66
64
|
"To force overwrite with your buffer changes, call: apply_buffer(force: true)\n" \
|
|
67
65
|
"Call read_file(path: \"#{c}\", reload: true) to inspect external changes or resolve."
|
|
68
66
|
end
|
|
67
|
+
skipped = @buffer_manager.respond_to?(:last_skipped) ? (@buffer_manager.last_skipped || []) : []
|
|
68
|
+
skipped.each do |s|
|
|
69
|
+
path = s.is_a?(Hash) ? s[:path] : s.to_s
|
|
70
|
+
reason = s.is_a?(Hash) ? s[:reason] : "invalid path"
|
|
71
|
+
messages << "[SKIPPED] #{path}: discarded as invalid path (#{reason}) and NOT written to disk."
|
|
72
|
+
end
|
|
69
73
|
|
|
70
|
-
if applied.empty?
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
74
|
+
if applied.empty?
|
|
75
|
+
if conflicts.empty? && skipped.empty?
|
|
76
|
+
# Inside the dirty? branch with zero applies and zero conflicts,
|
|
77
|
+
# every buffered entry was discarded as an invalid path
|
|
78
|
+
# (apply!'s garbage-skip) — the edits are NOT on disk, and citing
|
|
79
|
+
# an earlier apply here would be a lie.
|
|
80
|
+
"No unapplied buffer changes to write: the buffered entries were discarded as invalid paths and are NOT on disk. " \
|
|
81
|
+
"Re-check the target paths and buffer them again."
|
|
82
|
+
else
|
|
83
|
+
ToolOutput.error(messages.join("\n"))
|
|
84
|
+
end
|
|
79
85
|
else
|
|
80
86
|
messages.join("\n")
|
|
81
87
|
end
|
|
@@ -102,31 +108,18 @@ module Rubik
|
|
|
102
108
|
params_schema: {
|
|
103
109
|
type: "object",
|
|
104
110
|
properties: {
|
|
105
|
-
target: {
|
|
106
|
-
|
|
107
|
-
enum: ["lsp", "test_runner", "linter", "command"],
|
|
108
|
-
description: "Runtime component type to repair or adapt"
|
|
109
|
-
},
|
|
110
|
-
language_or_type: {
|
|
111
|
-
type: "string",
|
|
112
|
-
description: "Language or service identifier (e.g., 'ruby', 'python', 'rust', 'go', 'zig', 'c', 'bash')"
|
|
113
|
-
},
|
|
111
|
+
target: { type: "string", enum: ["lsp", "test_runner", "linter", "command"], description: "Runtime component type to repair or adapt" },
|
|
112
|
+
language_or_type: { type: "string", description: "Language or service identifier (e.g., 'ruby', 'python', 'rust', 'go', 'zig', 'c', 'bash')" },
|
|
114
113
|
proposed_command: {
|
|
115
114
|
oneOf: [{ type: "string" },
|
|
116
|
-
{ type: "object", properties: { executable: { type: "string" },
|
|
117
|
-
args: { type: "array", items: { type: "string" } } },
|
|
115
|
+
{ type: "object", properties: { executable: { type: "string" }, args: { type: "array", items: { type: "string" } } },
|
|
118
116
|
required: ["executable", "args"], additionalProperties: false },
|
|
119
117
|
{ type: "array", items: { type: "string" }, minItems: 1 }],
|
|
120
118
|
description: "Replacement command: a legacy string or, for LSP, {executable, args} / argv array. Omit to probe automatically."
|
|
121
119
|
},
|
|
122
|
-
probe_args: {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
},
|
|
126
|
-
auto_probe: {
|
|
127
|
-
type: "boolean",
|
|
128
|
-
description: "Whether to automatically probe system binaries and candidate commands (default: true)"
|
|
129
|
-
}
|
|
120
|
+
probe_args: { type: "array", items: { type: "string" },
|
|
121
|
+
description: "Explicit non-mutating probe arguments appended to proposed command; required for unknown executables" },
|
|
122
|
+
auto_probe: { type: "boolean", description: "Whether to automatically probe system binaries and candidate commands (default: true)" }
|
|
130
123
|
},
|
|
131
124
|
required: ["target", "language_or_type"]
|
|
132
125
|
}
|
|
@@ -154,25 +147,17 @@ module Rubik
|
|
|
154
147
|
else
|
|
155
148
|
proposed_cmd = raw_command.to_s.strip
|
|
156
149
|
end
|
|
157
|
-
auto_probe =
|
|
158
|
-
args[:auto_probe].nil? || args[:auto_probe]
|
|
159
|
-
else
|
|
160
|
-
args["auto_probe"]
|
|
161
|
-
end
|
|
150
|
+
auto_probe = args.fetch("auto_probe", args.fetch(:auto_probe, true))
|
|
162
151
|
|
|
163
152
|
return ToolOutput.error("Target and language_or_type must be provided.") if target.empty? || lang.empty?
|
|
164
153
|
|
|
165
154
|
# 1. Automatic Probing if proposed_cmd is not specified or auto_probe is requested
|
|
166
155
|
if !command_provided?(proposed_cmd) && auto_probe
|
|
167
156
|
candidate = case target
|
|
168
|
-
when "lsp"
|
|
169
|
-
|
|
170
|
-
when "
|
|
171
|
-
|
|
172
|
-
when "linter"
|
|
173
|
-
Rubik::Runtime::Discovery.discover_formatters(root_dir: @root_dir, lang: lang).first
|
|
174
|
-
when "command"
|
|
175
|
-
Rubik::Runtime::Discovery.binary_available?(lang) ? lang : nil
|
|
157
|
+
when "lsp" then Rubik::Runtime::Discovery.probe_lsp(lang, root_dir: @root_dir)
|
|
158
|
+
when "test_runner" then Rubik::Runtime::Discovery.discover_test_runners(root_dir: @root_dir, lang: lang).first
|
|
159
|
+
when "linter" then Rubik::Runtime::Discovery.discover_formatters(root_dir: @root_dir, lang: lang).first
|
|
160
|
+
when "command" then Rubik::Runtime::Discovery.binary_available?(lang) ? lang : nil
|
|
176
161
|
end
|
|
177
162
|
|
|
178
163
|
if candidate
|
|
@@ -232,8 +217,18 @@ module Rubik
|
|
|
232
217
|
@repair_scopes.delete([root_ctx, service_key]) if @repair_scopes[[root_ctx, service_key]].equal?(promotion_ctx)
|
|
233
218
|
unless root_ctx.disposed?
|
|
234
219
|
if previous_services.key?(service_key)
|
|
235
|
-
|
|
236
|
-
|
|
220
|
+
if target == "lsp"
|
|
221
|
+
restored = @lsp_manager&.clients&.[](lang)
|
|
222
|
+
active = restored && (!restored.respond_to?(:alive?) || restored.alive?) &&
|
|
223
|
+
(!restored.respond_to?(:running?) || restored.running?)
|
|
224
|
+
if active
|
|
225
|
+
root_ctx.provide(service_key, restored)
|
|
226
|
+
else
|
|
227
|
+
root_ctx.unprovide(service_key)
|
|
228
|
+
end
|
|
229
|
+
else
|
|
230
|
+
root_ctx.provide(service_key, previous_services[service_key])
|
|
231
|
+
end
|
|
237
232
|
else
|
|
238
233
|
root_ctx.unprovide(service_key)
|
|
239
234
|
end
|
data/lib/rubik/ui/capability.rb
CHANGED
|
@@ -10,12 +10,14 @@ module Rubik
|
|
|
10
10
|
# Resolution order:
|
|
11
11
|
# 1. RUBIK_TEST=1 -> never (deterministic test output)
|
|
12
12
|
# 2. NO_COLOR set -> never (https://no-color.org)
|
|
13
|
-
# 3. CLICOLOR_FORCE
|
|
13
|
+
# 3. CLICOLOR_FORCE set (not 0/false) -> always (CI with color-capable logs)
|
|
14
14
|
# 4. ui.color config: always|never -> explicit override
|
|
15
15
|
# 5. output not a TTY -> never (pipe/redirect safety)
|
|
16
16
|
# 6. TERM=dumb -> never
|
|
17
17
|
# 7. Windows legacy conhost -> try enabling VT, else never
|
|
18
18
|
module Capability
|
|
19
|
+
FALSY_ENV_VALUES = ["0", "false", "no", "off"].freeze
|
|
20
|
+
|
|
19
21
|
module_function
|
|
20
22
|
|
|
21
23
|
# Strips ANSI SGR escape sequences from a string. Used on the recording
|
|
@@ -31,13 +33,16 @@ module Rubik
|
|
|
31
33
|
case resolve_mode(io, config)
|
|
32
34
|
when :always then true
|
|
33
35
|
when :never then false
|
|
34
|
-
else
|
|
36
|
+
else
|
|
37
|
+
real_io = unwrap_tee(io)
|
|
38
|
+
real_io.respond_to?(:tty?) ? real_io.tty? : false
|
|
35
39
|
end
|
|
36
40
|
end
|
|
37
41
|
|
|
38
42
|
# True when the target can animate a spinner line (real terminal only).
|
|
39
43
|
def spinner?(io, config = nil)
|
|
40
|
-
|
|
44
|
+
real_io = unwrap_tee(io)
|
|
45
|
+
color?(io, config) && real_io.respond_to?(:tty?) && real_io.tty? && interactive_env?
|
|
41
46
|
end
|
|
42
47
|
|
|
43
48
|
def interactive_env?
|
|
@@ -68,17 +73,26 @@ module Rubik
|
|
|
68
73
|
io.targets.compact.find(&:tty?)
|
|
69
74
|
end
|
|
70
75
|
|
|
76
|
+
def clicolor_force?
|
|
77
|
+
val = ENV.fetch("CLICOLOR_FORCE", nil)
|
|
78
|
+
return false if val.nil? || val.empty?
|
|
79
|
+
|
|
80
|
+
!FALSY_ENV_VALUES.include?(val.downcase)
|
|
81
|
+
end
|
|
82
|
+
|
|
71
83
|
def resolve_mode(io, config)
|
|
72
84
|
return :never if ENV["RUBIK_TEST"] == "1"
|
|
73
85
|
return :never if ENV["NO_COLOR"] && !ENV["NO_COLOR"].to_s.empty?
|
|
74
|
-
return :always if
|
|
86
|
+
return :always if clicolor_force?
|
|
75
87
|
|
|
76
88
|
cfg_mode = config&.fetch("ui.color", "auto").to_s.downcase if config
|
|
77
89
|
return :always if cfg_mode == "always"
|
|
78
90
|
return :never if cfg_mode == "never"
|
|
79
91
|
|
|
80
92
|
return :never if ENV["TERM"] && ENV["TERM"].downcase == "dumb"
|
|
81
|
-
|
|
93
|
+
|
|
94
|
+
real_io = unwrap_tee(io)
|
|
95
|
+
return :never unless real_io.respond_to?(:tty?) && real_io.tty?
|
|
82
96
|
|
|
83
97
|
enable_windows_virtual_terminal_if_needed
|
|
84
98
|
:auto
|
data/lib/rubik/ui/renderer.rb
CHANGED
|
@@ -172,6 +172,7 @@ module Rubik
|
|
|
172
172
|
def render_llm_thought(step_info)
|
|
173
173
|
@spinner.stop
|
|
174
174
|
return nil if level < Rubik::REPL::StepFormatter::LEVEL_VERBOSE
|
|
175
|
+
return nil if step_info[:streamed] == true
|
|
175
176
|
|
|
176
177
|
first_line = @legacy.thought_first_line(step_info[:content])
|
|
177
178
|
return nil if first_line.nil? || first_line.empty?
|
data/lib/rubik/version.rb
CHANGED