rubik 0.2.0 → 0.3.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 +4 -0
- data/CHANGELOG.md +115 -0
- data/README.md +137 -268
- data/bin/rubik +9 -0
- data/lib/rubik/cli/workspace_trust.rb +78 -0
- data/lib/rubik/kernel/init.rb +15 -0
- data/lib/rubik/lsp/client.rb +3 -2
- data/lib/rubik/orchestrator/auto_apply.rb +1 -1
- data/lib/rubik/orchestrator/subagent/activation.rb +29 -4
- data/lib/rubik/orchestrator/subagent/builtin_types.rb +117 -0
- data/lib/rubik/orchestrator/subagent/dynamic_store.rb +119 -0
- data/lib/rubik/orchestrator/subagent/instance.rb +8 -36
- data/lib/rubik/orchestrator/subagent/journal.rb +77 -4
- data/lib/rubik/orchestrator/subagent/profile_contract.rb +187 -0
- data/lib/rubik/orchestrator/subagent/profile_snapshot.rb +72 -0
- data/lib/rubik/orchestrator/subagent/reuse.rb +2 -1
- data/lib/rubik/orchestrator/subagent/scoped_registry.rb +2 -0
- data/lib/rubik/orchestrator/subagent/spawn_transaction.rb +170 -0
- data/lib/rubik/orchestrator/subagent/start_gate.rb +98 -0
- data/lib/rubik/orchestrator/subagent/supervisor.rb +4 -67
- data/lib/rubik/orchestrator/subagent/type_definition.rb +84 -0
- data/lib/rubik/orchestrator/subagent/type_registry.rb +139 -159
- data/lib/rubik/orchestrator/turn_executor/subagent_channel.rb +4 -2
- data/lib/rubik/platform/process.rb +78 -1
- data/lib/rubik/prompt/context_builder.rb +54 -20
- data/lib/rubik/prompt/doctrine.rb +158 -22
- data/lib/rubik/prompt/token_estimator.rb +5 -3
- data/lib/rubik/prompt/tool_teachings.rb +19 -12
- data/lib/rubik/sandbox/driver/bubblewrap.rb +13 -59
- data/lib/rubik/sandbox/driver/docker.rb +14 -60
- data/lib/rubik/sandbox/driver/podman.rb +13 -56
- data/lib/rubik/sandbox/driver/process.rb +12 -64
- data/lib/rubik/security/guard.rb +1 -1
- data/lib/rubik/storage/store.rb +52 -11
- data/lib/rubik/tools/builtins/define_subagent.rb +69 -0
- data/lib/rubik/tools/builtins/storage.rb +8 -5
- data/lib/rubik/tools/builtins/subagent.rb +20 -53
- data/lib/rubik/tools/builtins/synthesis.rb +6 -1
- data/lib/rubik/tools/builtins.rb +1 -0
- data/lib/rubik/tools/diagnostic_reporter.rb +2 -1
- data/lib/rubik/tools/fuzzy_matcher.rb +39 -19
- data/lib/rubik/tools/truncator.rb +54 -21
- data/lib/rubik/version.rb +1 -1
- metadata +10 -1
|
@@ -81,29 +81,49 @@ module Rubik
|
|
|
81
81
|
|
|
82
82
|
# Computes Levenshtein edit distance between two strings using standard dynamic programming.
|
|
83
83
|
def self.levenshtein_distance(str1, str2)
|
|
84
|
-
return DidYouMean::Levenshtein.distance(str1.to_s, str2.to_s) if defined?(DidYouMean::Levenshtein) && DidYouMean::Levenshtein.respond_to?(:distance)
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
84
|
+
return DidYouMean::Levenshtein.distance(str1.to_s.scrub, str2.to_s.scrub) if defined?(DidYouMean::Levenshtein) && DidYouMean::Levenshtein.respond_to?(:distance)
|
|
85
|
+
|
|
86
|
+
# PERF: Use codepoints instead of String#chars to avoid single-character String allocations,
|
|
87
|
+
# single-buffer array mutation instead of allocating row arrays (new_d), and fast while loops
|
|
88
|
+
# instead of each_with_index/Array#min blocks (~35% faster, zero inner loop allocations).
|
|
89
|
+
# scrub guarantees valid UTF-8 so codepoints never raises on
|
|
90
|
+
# malformed input (the previous .chars path tolerated bad bytes).
|
|
91
|
+
s1 = str1.to_s.scrub.codepoints
|
|
92
|
+
s2 = str2.to_s.scrub.codepoints
|
|
93
|
+
len1 = s1.length
|
|
94
|
+
len2 = s2.length
|
|
95
|
+
return len2 if len1.zero?
|
|
96
|
+
return len1 if len2.zero?
|
|
97
|
+
|
|
98
|
+
d = Array.new(len1 + 1) { |i| i }
|
|
99
|
+
|
|
100
|
+
j = 0
|
|
101
|
+
while j < len2
|
|
102
|
+
char2 = s2[j]
|
|
103
|
+
j += 1
|
|
104
|
+
new_d0 = j
|
|
105
|
+
prev = d[0]
|
|
106
|
+
i = 0
|
|
107
|
+
while i < len1
|
|
108
|
+
char1 = s1[i]
|
|
96
109
|
cost = char1 == char2 ? 0 : 1
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
110
|
+
del = d[i + 1] + 1
|
|
111
|
+
ins = new_d0 + 1
|
|
112
|
+
sub = prev + cost
|
|
113
|
+
|
|
114
|
+
min_val = del
|
|
115
|
+
min_val = ins if ins < min_val
|
|
116
|
+
min_val = sub if sub < min_val
|
|
117
|
+
new_d0 = min_val
|
|
118
|
+
|
|
119
|
+
prev = d[i + 1]
|
|
120
|
+
d[i + 1] = new_d0
|
|
121
|
+
i += 1
|
|
102
122
|
end
|
|
103
|
-
d =
|
|
123
|
+
d[0] = j
|
|
104
124
|
end
|
|
105
125
|
|
|
106
|
-
d
|
|
126
|
+
d[len1]
|
|
107
127
|
end
|
|
108
128
|
|
|
109
129
|
# Finds the closest matching line window in `file_content` for `target_content`
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "English"
|
|
4
|
-
|
|
5
3
|
module Rubik
|
|
6
4
|
module Tools
|
|
7
5
|
module Truncator
|
|
8
6
|
DEFAULT_MAX_CHARS = 12_000
|
|
9
7
|
DEFAULT_MAX_LINES = 300
|
|
10
8
|
|
|
11
|
-
def self.truncate(text, max_chars: DEFAULT_MAX_CHARS, max_lines: DEFAULT_MAX_LINES, strategy: :head_tail)
|
|
9
|
+
def self.truncate(text, max_chars: DEFAULT_MAX_CHARS, max_lines: DEFAULT_MAX_LINES, strategy: :head_tail, separator: "\n")
|
|
12
10
|
str = text.to_s
|
|
13
11
|
# byteslice truncation below cuts on byte boundaries and can split a
|
|
14
12
|
# multibyte character, producing invalid UTF-8 that then poisons the
|
|
@@ -16,15 +14,14 @@ module Rubik
|
|
|
16
14
|
# estimation). Scrub exactly once, after the last byteslice.
|
|
17
15
|
str = str.scrub("") unless str.valid_encoding?
|
|
18
16
|
|
|
19
|
-
max_c = (max_chars || DEFAULT_MAX_CHARS).to_i
|
|
20
|
-
max_l = (max_lines || DEFAULT_MAX_LINES).to_i
|
|
17
|
+
max_c = [(max_chars || DEFAULT_MAX_CHARS).to_i, 0].max
|
|
18
|
+
max_l = [(max_lines || DEFAULT_MAX_LINES).to_i, 0].max
|
|
19
|
+
sep = separator || "\n"
|
|
21
20
|
|
|
22
|
-
# PERF: With the default
|
|
21
|
+
# PERF: With the default newline separator, calculate the line upper bound
|
|
23
22
|
# using zero-allocation C-level #count("\n") and short-circuit BEFORE
|
|
24
|
-
# calling #lines (which allocates an Array of line strings).
|
|
25
|
-
|
|
26
|
-
# separator, since String#lines uses $/ when no separator is supplied.
|
|
27
|
-
if $INPUT_RECORD_SEPARATOR == "\n"
|
|
23
|
+
# calling #lines (which allocates an Array of line strings).
|
|
24
|
+
if sep == "\n"
|
|
28
25
|
line_bound = if str.empty?
|
|
29
26
|
0
|
|
30
27
|
elsif str.end_with?("\n")
|
|
@@ -35,7 +32,8 @@ module Rubik
|
|
|
35
32
|
return { text: str, truncated: false, omitted_lines: 0, omitted_chars: 0 } if str.bytesize <= max_c && line_bound <= max_l
|
|
36
33
|
end
|
|
37
34
|
|
|
38
|
-
lines = str.lines
|
|
35
|
+
lines = str.lines(sep)
|
|
36
|
+
return { text: str, truncated: false, omitted_lines: 0, omitted_chars: 0 } if str.bytesize <= max_c && lines.size <= max_l
|
|
39
37
|
|
|
40
38
|
strategy_sym = strategy.to_s.downcase.to_sym
|
|
41
39
|
|
|
@@ -44,7 +42,8 @@ module Rubik
|
|
|
44
42
|
kept_lines = lines.first(max_l)
|
|
45
43
|
kept_str = kept_lines.join
|
|
46
44
|
kept_str = kept_str.byteslice(0, max_c) if kept_str.bytesize > max_c
|
|
47
|
-
|
|
45
|
+
kept_line_count = kept_str.empty? ? 0 : kept_str.lines(sep).size
|
|
46
|
+
omitted_lines = [lines.size - kept_line_count, 0].max
|
|
48
47
|
omitted_chars = [str.bytesize - kept_str.bytesize, 0].max
|
|
49
48
|
notice = "\n... [Truncated: omitted #{omitted_lines} lines / #{omitted_chars} chars] ...\n"
|
|
50
49
|
{ text: finalize(kept_str, notice, prefix: true), truncated: true,
|
|
@@ -53,29 +52,63 @@ module Rubik
|
|
|
53
52
|
kept_lines = lines.last(max_l)
|
|
54
53
|
kept_str = kept_lines.join
|
|
55
54
|
kept_str = kept_str.byteslice(-max_c, max_c) if kept_str.bytesize > max_c
|
|
56
|
-
|
|
55
|
+
kept_line_count = kept_str.empty? ? 0 : kept_str.lines(sep).size
|
|
56
|
+
omitted_lines = [lines.size - kept_line_count, 0].max
|
|
57
57
|
omitted_chars = [str.bytesize - kept_str.bytesize, 0].max
|
|
58
58
|
notice = "\n... [Truncated: omitted #{omitted_lines} lines / #{omitted_chars} chars] ...\n"
|
|
59
59
|
{ text: finalize(kept_str, notice, prefix: false), truncated: true,
|
|
60
60
|
omitted_lines: omitted_lines, omitted_chars: omitted_chars }
|
|
61
61
|
else # :head_tail
|
|
62
|
-
|
|
63
|
-
|
|
62
|
+
if max_l <= 1
|
|
63
|
+
kept_lines = lines.first(max_l)
|
|
64
|
+
kept_str = kept_lines.join
|
|
65
|
+
kept_str = kept_str.byteslice(0, max_c) if kept_str.bytesize > max_c
|
|
66
|
+
kept_line_count = kept_str.empty? ? 0 : kept_str.lines(sep).size
|
|
67
|
+
omitted_lines = [lines.size - kept_line_count, 0].max
|
|
68
|
+
omitted_chars = [str.bytesize - kept_str.bytesize, 0].max
|
|
69
|
+
notice = "\n\n... [Truncated: #{omitted_lines} lines / #{omitted_chars} chars omitted to preserve token budget] ...\n\n"
|
|
70
|
+
return { text: finalize(kept_str, notice, prefix: true), truncated: true,
|
|
71
|
+
omitted_lines: omitted_lines, omitted_chars: omitted_chars }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
head_lines_count = max_l / 2
|
|
75
|
+
tail_lines_count = max_l - head_lines_count
|
|
76
|
+
|
|
77
|
+
head_chars = max_c / 2
|
|
78
|
+
tail_chars = max_c - head_chars
|
|
64
79
|
|
|
65
|
-
|
|
66
|
-
|
|
80
|
+
if lines.size == 1
|
|
81
|
+
head_lines = lines
|
|
82
|
+
tail_lines = lines
|
|
83
|
+
elsif head_lines_count + tail_lines_count >= lines.size
|
|
84
|
+
head_n = [head_lines_count, lines.size / 2].min
|
|
85
|
+
tail_n = [tail_lines_count, lines.size - head_n].min
|
|
86
|
+
head_lines = lines.first(head_n)
|
|
87
|
+
tail_lines = lines.last(tail_n)
|
|
88
|
+
else
|
|
89
|
+
head_lines = lines.first(head_lines_count)
|
|
90
|
+
tail_lines = lines.last(tail_lines_count)
|
|
91
|
+
end
|
|
67
92
|
|
|
68
93
|
head_str = head_lines.join
|
|
69
94
|
tail_str = tail_lines.join
|
|
70
95
|
|
|
71
|
-
head_str = head_str.byteslice(0,
|
|
72
|
-
tail_str = tail_str.byteslice(-
|
|
96
|
+
head_str = head_str.byteslice(0, head_chars) if head_str.bytesize > head_chars
|
|
97
|
+
tail_str = tail_str.byteslice(-tail_chars, tail_chars) if tail_str.bytesize > tail_chars
|
|
73
98
|
|
|
74
|
-
omitted_lines =
|
|
99
|
+
omitted_lines = if lines.size == 1
|
|
100
|
+
0
|
|
101
|
+
else
|
|
102
|
+
head_k = head_str.empty? ? 0 : head_str.lines(sep).size
|
|
103
|
+
tail_k = tail_str.empty? ? 0 : tail_str.lines(sep).size
|
|
104
|
+
[lines.size - (head_k + tail_k), 0].max
|
|
105
|
+
end
|
|
75
106
|
omitted_chars = [str.bytesize - (head_str.bytesize + tail_str.bytesize), 0].max
|
|
76
107
|
notice = "\n\n... [Truncated: #{omitted_lines} lines / #{omitted_chars} chars omitted to preserve token budget] ...\n\n"
|
|
77
108
|
|
|
78
|
-
|
|
109
|
+
head_final = head_str.valid_encoding? ? head_str : head_str.scrub("")
|
|
110
|
+
tail_final = tail_str.valid_encoding? ? tail_str : tail_str.scrub("")
|
|
111
|
+
{ text: "#{head_final}#{notice}#{tail_final}", truncated: true,
|
|
79
112
|
omitted_lines: omitted_lines, omitted_chars: omitted_chars }
|
|
80
113
|
end
|
|
81
114
|
end
|
data/lib/rubik/version.rb
CHANGED
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
|
+
version: 0.3.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Özgür Ulukır
|
|
@@ -272,6 +272,7 @@ files:
|
|
|
272
272
|
- lib/rubik/buffer/manager.rb
|
|
273
273
|
- lib/rubik/buffer/path_validator.rb
|
|
274
274
|
- lib/rubik/buffer/snapshot.rb
|
|
275
|
+
- lib/rubik/cli/workspace_trust.rb
|
|
275
276
|
- lib/rubik/coeffects.rb
|
|
276
277
|
- lib/rubik/concurrency.rb
|
|
277
278
|
- lib/rubik/concurrency/boundary.rb
|
|
@@ -357,12 +358,14 @@ files:
|
|
|
357
358
|
- lib/rubik/orchestrator/stream_manager.rb
|
|
358
359
|
- lib/rubik/orchestrator/subagent/activation.rb
|
|
359
360
|
- lib/rubik/orchestrator/subagent/budget.rb
|
|
361
|
+
- lib/rubik/orchestrator/subagent/builtin_types.rb
|
|
360
362
|
- lib/rubik/orchestrator/subagent/completion.rb
|
|
361
363
|
- lib/rubik/orchestrator/subagent/concurrency_gate.rb
|
|
362
364
|
- lib/rubik/orchestrator/subagent/context_compaction.rb
|
|
363
365
|
- lib/rubik/orchestrator/subagent/conversation_id.rb
|
|
364
366
|
- lib/rubik/orchestrator/subagent/deferred_spec.rb
|
|
365
367
|
- lib/rubik/orchestrator/subagent/dependency.rb
|
|
368
|
+
- lib/rubik/orchestrator/subagent/dynamic_store.rb
|
|
366
369
|
- lib/rubik/orchestrator/subagent/execution_reads.rb
|
|
367
370
|
- lib/rubik/orchestrator/subagent/instance.rb
|
|
368
371
|
- lib/rubik/orchestrator/subagent/journal.rb
|
|
@@ -371,6 +374,8 @@ files:
|
|
|
371
374
|
- lib/rubik/orchestrator/subagent/narration.rb
|
|
372
375
|
- lib/rubik/orchestrator/subagent/notices.rb
|
|
373
376
|
- lib/rubik/orchestrator/subagent/operation_tracker.rb
|
|
377
|
+
- lib/rubik/orchestrator/subagent/profile_contract.rb
|
|
378
|
+
- lib/rubik/orchestrator/subagent/profile_snapshot.rb
|
|
374
379
|
- lib/rubik/orchestrator/subagent/questioner.rb
|
|
375
380
|
- lib/rubik/orchestrator/subagent/reaper.rb
|
|
376
381
|
- lib/rubik/orchestrator/subagent/resilience.rb
|
|
@@ -378,6 +383,8 @@ files:
|
|
|
378
383
|
- lib/rubik/orchestrator/subagent/reuse.rb
|
|
379
384
|
- lib/rubik/orchestrator/subagent/run_cycle.rb
|
|
380
385
|
- lib/rubik/orchestrator/subagent/scoped_registry.rb
|
|
386
|
+
- lib/rubik/orchestrator/subagent/spawn_transaction.rb
|
|
387
|
+
- lib/rubik/orchestrator/subagent/start_gate.rb
|
|
381
388
|
- lib/rubik/orchestrator/subagent/step_runner.rb
|
|
382
389
|
- lib/rubik/orchestrator/subagent/storage_id.rb
|
|
383
390
|
- lib/rubik/orchestrator/subagent/supervisor.rb
|
|
@@ -386,6 +393,7 @@ files:
|
|
|
386
393
|
- lib/rubik/orchestrator/subagent/tool_dispatch.rb
|
|
387
394
|
- lib/rubik/orchestrator/subagent/transcript_formatter.rb
|
|
388
395
|
- lib/rubik/orchestrator/subagent/transcript_reader.rb
|
|
396
|
+
- lib/rubik/orchestrator/subagent/type_definition.rb
|
|
389
397
|
- lib/rubik/orchestrator/subagent/type_registry.rb
|
|
390
398
|
- lib/rubik/orchestrator/subagent/waiting.rb
|
|
391
399
|
- lib/rubik/orchestrator/subagent_notice_inbox.rb
|
|
@@ -717,6 +725,7 @@ files:
|
|
|
717
725
|
- lib/rubik/tools.rb
|
|
718
726
|
- lib/rubik/tools/base.rb
|
|
719
727
|
- lib/rubik/tools/builtins.rb
|
|
728
|
+
- lib/rubik/tools/builtins/define_subagent.rb
|
|
720
729
|
- lib/rubik/tools/builtins/dmesg.rb
|
|
721
730
|
- lib/rubik/tools/builtins/exec.rb
|
|
722
731
|
- lib/rubik/tools/builtins/files.rb
|