rubik 0.4.1 → 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.
@@ -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 = [true, "true"].include?(args["force"]) || [true, "true"].include?(args[: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? && !conflicts.empty?
71
- ToolOutput.error(messages.join("\n"))
72
- elsif applied.empty?
73
- # Inside the dirty? branch with zero applies and zero conflicts,
74
- # every buffered entry was discarded as an invalid path
75
- # (apply!'s garbage-skip) — the edits are NOT on disk, and citing
76
- # an earlier apply here would be a lie.
77
- "No unapplied buffer changes to write: the buffered entries were discarded as invalid paths and are NOT on disk. " \
78
- "Re-check the target paths and buffer them again."
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
- type: "string",
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
- type: "array", items: { type: "string" },
124
- description: "Explicit non-mutating functional probe arguments appended to the exact proposed command; required for unknown executables"
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 = if args["auto_probe"].nil?
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
- Rubik::Runtime::Discovery.probe_lsp(lang, root_dir: @root_dir)
170
- when "test_runner"
171
- Rubik::Runtime::Discovery.discover_test_runners(root_dir: @root_dir, lang: lang).first
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
- restored = target == "lsp" ? @lsp_manager&.clients&.[](lang) : nil
236
- root_ctx.provide(service_key, restored || previous_services[service_key])
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
@@ -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=1 -> always (CI with color-capable logs)
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 io.respond_to?(:tty?) ? io.tty? : false
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
- color?(io, config) && io.respond_to?(:tty?) && io.tty? && interactive_env?
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 ENV["CLICOLOR_FORCE"] && ENV["CLICOLOR_FORCE"].to_s == "1"
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
- return :never unless io.respond_to?(:tty?) && io.tty?
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/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rubik
4
- VERSION = "0.4.1"
4
+ VERSION = "0.4.2"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubik
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Özgür Ulukır