rubik 0.3.5 → 0.4.1
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 +99 -3
- data/lib/rubik/ast/c_extractor.rb +1 -1
- data/lib/rubik/ast/fallback_cleaner.rb +3 -1
- data/lib/rubik/ast/graph.rb +20 -1
- data/lib/rubik/ast/graph_extensions.rb +89 -1
- data/lib/rubik/ast/python_extractor.rb +5 -5
- data/lib/rubik/ast/rust_extractor.rb +1 -1
- data/lib/rubik/ast/typescript_extractor.rb +3 -3
- data/lib/rubik/diagnostics/parser.rb +65 -13
- data/lib/rubik/llm/openai_compatible.rb +2 -1
- data/lib/rubik/llm/registry.rb +81 -74
- data/lib/rubik/lsp/manager.rb +15 -5
- data/lib/rubik/orchestrator/step_executor.rb +139 -155
- 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/prompt/token_estimator.rb +16 -12
- 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/classifier/classifier.rb +3 -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 +5 -2
- data/lib/rubik/tools/base.rb +2 -1
- data/lib/rubik/tools/truncator.rb +21 -8
- data/lib/rubik/ui/renderer.rb +1 -0
- data/lib/rubik/version.rb +1 -1
- metadata +1 -1
|
@@ -145,7 +145,7 @@ module Rubik
|
|
|
145
145
|
# blocks stripped; nil when nothing human-readable remains. Shared
|
|
146
146
|
# with UI::Renderer (Plan 51, Decision D2).
|
|
147
147
|
def thought_first_line(content)
|
|
148
|
-
clean_content = content.to_s.sub(/```(?:json)?\s*\{
|
|
148
|
+
clean_content = content.to_s.sub(/```(?:json)?\s*\{[^`]*\}\s*```/m, "").strip
|
|
149
149
|
return nil if clean_content.empty?
|
|
150
150
|
|
|
151
151
|
clean_content.lines.first.to_s.strip
|
|
@@ -243,6 +243,7 @@ module Rubik
|
|
|
243
243
|
# with UI::Renderer (Plan 51, Decision D2).
|
|
244
244
|
def format_llm_thought(step_info)
|
|
245
245
|
return nil if @level == LEVEL_QUIET || @level == LEVEL_NORMAL
|
|
246
|
+
return nil if step_info[:streamed] == true
|
|
246
247
|
|
|
247
248
|
first_line = thought_first_line(step_info[:content])
|
|
248
249
|
return nil if first_line.nil? || first_line.empty?
|
|
@@ -144,7 +144,7 @@ module Rubik
|
|
|
144
144
|
:dependency_missing
|
|
145
145
|
elsif combined.match?(/(syntax error|unexpected token|compilation error|error\[E\d+\]|parse error|fatal error)/i)
|
|
146
146
|
:syntax_or_compile_error
|
|
147
|
-
elsif combined.match?(/(failures:|assertions|tests failed|failed tests|FAILED \(|FAIL:|\d+ failures, \d+ errors)/i)
|
|
147
|
+
elsif combined.match?(/(failures:|assertions|tests failed|failed tests|FAILED \(|FAIL:|\b\d+ failures, \d+ errors\b)/i)
|
|
148
148
|
:test_failure
|
|
149
149
|
else
|
|
150
150
|
:unknown_failure
|
|
@@ -164,7 +164,7 @@ module Rubik
|
|
|
164
164
|
def self.segment_annotation(segment)
|
|
165
165
|
rule = segment[:rule] || "unknown"
|
|
166
166
|
reason = segment[:reason].to_s.strip
|
|
167
|
-
return "#{rule} — option #{segment[:option]} overrides to DANGEROUS" if segment[:option] && reason.match?(/overridden by option
|
|
167
|
+
return "#{rule} — option #{segment[:option]} overrides to DANGEROUS" if segment[:option] && reason.match?(/overridden by option \S+ to dangerous\z/i)
|
|
168
168
|
|
|
169
169
|
# Base-rule reasons restate matched_rule ("base rule: rm (…)") — the
|
|
170
170
|
# rule is already displayed; keep only the elevation evidence.
|
|
@@ -22,6 +22,8 @@ module Rubik
|
|
|
22
22
|
DEV_PATHS = ["/dev/null", "/dev/stdin", "/dev/stdout", "/dev/stderr", "/dev/fd/"].freeze
|
|
23
23
|
WRITE_OPERATORS = [">", ">>", "2>", "&>", ">&", "2>>", "*>"].freeze
|
|
24
24
|
READ_OPERATORS = ["<"].freeze
|
|
25
|
+
# PERF: Hoist home token array to avoid per-path array allocations in system_path? check.
|
|
26
|
+
HOME_TOKENS = ["~", "~/", "$HOME", "${HOME}"].freeze
|
|
25
27
|
|
|
26
28
|
def self.default_database
|
|
27
29
|
@default_database ||= CommandDatabase.new
|
|
@@ -351,7 +353,7 @@ module Rubik
|
|
|
351
353
|
|
|
352
354
|
return true if p =~ %r{\A/(?:\*|\.+)?/?\z}
|
|
353
355
|
return true if p =~ %r{\A[a-zA-Z]:(?:[\\/]|\*|\.+)?\z}i
|
|
354
|
-
return true if
|
|
356
|
+
return true if HOME_TOKENS.include?(p)
|
|
355
357
|
|
|
356
358
|
if p =~ %r{\A([a-zA-Z]):[/\\](.*)\z}
|
|
357
359
|
parts = []
|
|
@@ -14,19 +14,19 @@ module Rubik
|
|
|
14
14
|
/AIzaSy[a-zA-Z0-9_-]{33}/,
|
|
15
15
|
/\bbearer\s+[a-zA-Z0-9_\-.]{20,}/i,
|
|
16
16
|
/-----BEGIN [A-Z0-9_\s-]+PRIVATE KEY-----[\s\S]*?-----END [A-Z0-9_\s-]+PRIVATE KEY-----/,
|
|
17
|
-
%r{(https?://[
|
|
17
|
+
%r{(https?://[^\s/@:]+:)([^\s/@:]+)@}
|
|
18
18
|
].freeze
|
|
19
19
|
|
|
20
20
|
ASSIGNMENT_KEY_REGEX = /\b(password|passwd|api_key|secret)(\s*[:=]\s*)(['"][^'"]*['"]|[^\s,;'"]+)/i
|
|
21
21
|
ASSIGNMENT_TOKEN_REGEX = /\b(token)(\s*[:=]\s*)(['"][^'"]*['"]|[^\s,;'"]+)/i
|
|
22
22
|
|
|
23
23
|
# PERF: Pre-compiled constants to avoid re-allocation inside hot loops
|
|
24
|
-
URL_PATTERN = %r{(https?://[
|
|
24
|
+
URL_PATTERN = %r{(https?://[^\s/@:]+:)([^\s/@:]+)@}
|
|
25
25
|
URL_MASK = "\\1#{MASK_STRING}@".freeze
|
|
26
26
|
STATIC_PATTERNS = PATTERNS.reject { |pattern| pattern == URL_PATTERN }.freeze
|
|
27
27
|
# Skip static replacement passes for assignment-only and URL-only inputs.
|
|
28
28
|
STATIC_PATTERNS_COMBINED_REGEX = Regexp.union(STATIC_PATTERNS).freeze
|
|
29
|
-
QUICK_CHECK_REGEX = %r{sk-|AKIA|ghp_|github_pat_|AIza|bearer|PRIVATE KEY|://[
|
|
29
|
+
QUICK_CHECK_REGEX = %r{sk-|AKIA|ghp_|github_pat_|AIza|bearer|PRIVATE KEY|://[^\s/@:]+:|password|passwd|api_key|secret|token}i
|
|
30
30
|
|
|
31
31
|
# Anahtar-maskeleme desenleri (Session::Recorder#mask_payload ile aynı
|
|
32
32
|
# aile): sır görünümlü anahtarların değerleri desen eşleşmese bile
|
data/lib/rubik/storage/layout.rb
CHANGED
|
@@ -48,7 +48,9 @@ module Rubik
|
|
|
48
48
|
|
|
49
49
|
# [a-z0-9._-] dışını tireye çevirir; boş kalırsa "default".
|
|
50
50
|
def slugify(name)
|
|
51
|
-
slug = name.to_s.downcase.gsub(/[^a-z0-9._-]+/, "-")
|
|
51
|
+
slug = name.to_s.downcase.gsub(/[^a-z0-9._-]+/, "-")
|
|
52
|
+
slug = slug.delete_prefix("-").delete_prefix(".") while slug.start_with?("-", ".")
|
|
53
|
+
slug = slug.delete_suffix("-").delete_suffix(".") while slug.end_with?("-", ".")
|
|
52
54
|
slug.empty? ? "default" : slug
|
|
53
55
|
end
|
|
54
56
|
|
data/lib/rubik/storage/render.rb
CHANGED
|
@@ -38,7 +38,7 @@ module Rubik
|
|
|
38
38
|
return header << "_(empty — no #{type} entities yet)_\n" if summaries.empty?
|
|
39
39
|
|
|
40
40
|
summaries.each do |s|
|
|
41
|
-
title = s[:title].to_s.gsub("|", "\\|").slice(0, 80)
|
|
41
|
+
title = s[:title].to_s.gsub("\\", "\\\\\\\\").gsub("|", "\\|").slice(0, 80)
|
|
42
42
|
header << "| #{s[:row]} | `#{s[:id]}` | #{s[:status]} | #{s[:updated_at]} | #{title} |\n"
|
|
43
43
|
end
|
|
44
44
|
header
|
|
@@ -42,8 +42,11 @@ module Rubik
|
|
|
42
42
|
# çözümleyicisi "wiki.42" gibi iki parçalı tip.satır formlarını ref
|
|
43
43
|
# alias'tan ÖNCE dener — çarpışma yanlış satırı ezmesi demekti.
|
|
44
44
|
def slug(text)
|
|
45
|
-
s = text.to_s.downcase.gsub(/[^a-z0-9]+/, "-")
|
|
46
|
-
s = s
|
|
45
|
+
s = text.to_s.downcase.gsub(/[^a-z0-9]+/, "-")
|
|
46
|
+
s = s.delete_prefix("-") while s.start_with?("-")
|
|
47
|
+
s = s.delete_suffix("-") while s.end_with?("-")
|
|
48
|
+
s = s[0, SLUG_MAX].to_s
|
|
49
|
+
s = s.delete_suffix("-") while s.end_with?("-")
|
|
47
50
|
s = "n#{s}" if s.match?(/\A\d+\z/)
|
|
48
51
|
s.empty? ? "untitled" : s
|
|
49
52
|
end
|
data/lib/rubik/tools/base.rb
CHANGED
|
@@ -55,7 +55,8 @@ module Rubik
|
|
|
55
55
|
filter = raw.to_s.strip.tr("\\", "/").sub(%r{^\./}, "")
|
|
56
56
|
return ["", nil] if filter.empty? || filter == "." || filter == "/"
|
|
57
57
|
|
|
58
|
-
norm_root = root_dir.to_s.tr("\\", "/")
|
|
58
|
+
norm_root = root_dir.to_s.tr("\\", "/")
|
|
59
|
+
norm_root = norm_root.delete_suffix("/") while norm_root.end_with?("/")
|
|
59
60
|
if filter.downcase == norm_root.downcase
|
|
60
61
|
return ["", nil]
|
|
61
62
|
elsif filter.downcase.start_with?("#{norm_root.downcase}/")
|
|
@@ -41,7 +41,10 @@ module Rubik
|
|
|
41
41
|
when :head_only
|
|
42
42
|
kept_lines = lines.first(max_l)
|
|
43
43
|
kept_str = kept_lines.join
|
|
44
|
-
|
|
44
|
+
if kept_str.bytesize > max_c
|
|
45
|
+
kept_str = kept_str.byteslice(0, max_c)
|
|
46
|
+
kept_str = kept_str.scrub("") unless kept_str.valid_encoding?
|
|
47
|
+
end
|
|
45
48
|
kept_line_count = kept_str.empty? ? 0 : kept_str.lines(sep).size
|
|
46
49
|
omitted_lines = [lines.size - kept_line_count, 0].max
|
|
47
50
|
omitted_chars = [str.bytesize - kept_str.bytesize, 0].max
|
|
@@ -51,7 +54,10 @@ module Rubik
|
|
|
51
54
|
when :tail_only
|
|
52
55
|
kept_lines = lines.last(max_l)
|
|
53
56
|
kept_str = kept_lines.join
|
|
54
|
-
|
|
57
|
+
if kept_str.bytesize > max_c
|
|
58
|
+
kept_str = kept_str.byteslice(-max_c, max_c)
|
|
59
|
+
kept_str = kept_str.scrub("") unless kept_str.valid_encoding?
|
|
60
|
+
end
|
|
55
61
|
kept_line_count = kept_str.empty? ? 0 : kept_str.lines(sep).size
|
|
56
62
|
omitted_lines = [lines.size - kept_line_count, 0].max
|
|
57
63
|
omitted_chars = [str.bytesize - kept_str.bytesize, 0].max
|
|
@@ -62,7 +68,10 @@ module Rubik
|
|
|
62
68
|
if max_l <= 1
|
|
63
69
|
kept_lines = lines.first(max_l)
|
|
64
70
|
kept_str = kept_lines.join
|
|
65
|
-
|
|
71
|
+
if kept_str.bytesize > max_c
|
|
72
|
+
kept_str = kept_str.byteslice(0, max_c)
|
|
73
|
+
kept_str = kept_str.scrub("") unless kept_str.valid_encoding?
|
|
74
|
+
end
|
|
66
75
|
kept_line_count = kept_str.empty? ? 0 : kept_str.lines(sep).size
|
|
67
76
|
omitted_lines = [lines.size - kept_line_count, 0].max
|
|
68
77
|
omitted_chars = [str.bytesize - kept_str.bytesize, 0].max
|
|
@@ -93,8 +102,14 @@ module Rubik
|
|
|
93
102
|
head_str = head_lines.join
|
|
94
103
|
tail_str = tail_lines.join
|
|
95
104
|
|
|
96
|
-
|
|
97
|
-
|
|
105
|
+
if head_str.bytesize > head_chars
|
|
106
|
+
head_str = head_str.byteslice(0, head_chars)
|
|
107
|
+
head_str = head_str.scrub("") unless head_str.valid_encoding?
|
|
108
|
+
end
|
|
109
|
+
if tail_str.bytesize > tail_chars
|
|
110
|
+
tail_str = tail_str.byteslice(-tail_chars, tail_chars)
|
|
111
|
+
tail_str = tail_str.scrub("") unless tail_str.valid_encoding?
|
|
112
|
+
end
|
|
98
113
|
|
|
99
114
|
omitted_lines = if lines.size == 1
|
|
100
115
|
0
|
|
@@ -106,9 +121,7 @@ module Rubik
|
|
|
106
121
|
omitted_chars = [str.bytesize - (head_str.bytesize + tail_str.bytesize), 0].max
|
|
107
122
|
notice = "\n\n... [Truncated: #{omitted_lines} lines / #{omitted_chars} chars omitted to preserve token budget] ...\n\n"
|
|
108
123
|
|
|
109
|
-
|
|
110
|
-
tail_final = tail_str.valid_encoding? ? tail_str : tail_str.scrub("")
|
|
111
|
-
{ text: "#{head_final}#{notice}#{tail_final}", truncated: true,
|
|
124
|
+
{ text: "#{head_str}#{notice}#{tail_str}", truncated: true,
|
|
112
125
|
omitted_lines: omitted_lines, omitted_chars: omitted_chars }
|
|
113
126
|
end
|
|
114
127
|
end
|
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