brute 0.1.5 → 0.1.6
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/lib/brute/diff.rb +26 -0
- data/lib/brute/middleware/tool_error_tracking.rb +14 -8
- data/lib/brute/middleware/tracing.rb +35 -6
- data/lib/brute/tools/fs_patch.rb +14 -12
- data/lib/brute/tools/fs_write.rb +8 -6
- data/lib/brute/version.rb +1 -1
- data/lib/brute.rb +59 -59
- metadata +21 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 48b59415efb00c3e5da0f9264aa04291f477b4255fb35407a2ed0e68fec69fc2
|
|
4
|
+
data.tar.gz: 668d46ac81b5ff1f8dfd3f832f08736aa38820fda44c4432940b7f82e1261079
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c9f9b4f883b6b77a7e81980528e9ed0c703280114ff8b7ee26409b69c8127ec699740c43deea027394045c33a7b18a0a59dd72d7ccdf0694eaa62754b75ed2d9
|
|
7
|
+
data.tar.gz: 1ee08e461182f1065e9fe01313a823fa8af31f820341d680d4b43619016745db28876c061352eab33f25049cd6a87d9c44b039f87eae5c837101cc8cd07bd159
|
data/lib/brute/diff.rb
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'diff/lcs'
|
|
4
|
+
require 'diff/lcs/hunk'
|
|
5
|
+
|
|
6
|
+
module Brute
|
|
7
|
+
module Diff
|
|
8
|
+
# Generate a unified diff string from two texts.
|
|
9
|
+
def self.unified(old_text, new_text, context: 3)
|
|
10
|
+
old_lines = old_text.lines
|
|
11
|
+
new_lines = new_text.lines
|
|
12
|
+
diffs = ::Diff::LCS.diff(old_lines, new_lines)
|
|
13
|
+
return '' if diffs.empty?
|
|
14
|
+
|
|
15
|
+
output = +''
|
|
16
|
+
file_length_difference = 0
|
|
17
|
+
diffs.each do |piece|
|
|
18
|
+
hunk = ::Diff::LCS::Hunk.new(old_lines, new_lines, piece, context, file_length_difference)
|
|
19
|
+
file_length_difference = hunk.file_length_difference
|
|
20
|
+
output << hunk.diff(:unified)
|
|
21
|
+
output << "\n"
|
|
22
|
+
end
|
|
23
|
+
output
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -2,16 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
module Brute
|
|
4
4
|
module Middleware
|
|
5
|
-
# Tracks per-tool error counts
|
|
6
|
-
# the error ceiling is reached.
|
|
5
|
+
# Tracks per-tool error counts and total tool call count across LLM
|
|
6
|
+
# calls, and signals when the error ceiling is reached.
|
|
7
7
|
#
|
|
8
8
|
# This middleware doesn't execute tools itself — it inspects the tool
|
|
9
9
|
# results that were sent as input to the LLM call (env[:tool_results])
|
|
10
|
-
# and counts failures.
|
|
10
|
+
# and counts failures and totals.
|
|
11
11
|
#
|
|
12
12
|
# When any tool exceeds max_failures, it sets env[:metadata][:tool_error_limit_reached]
|
|
13
13
|
# so the orchestrator can decide to stop.
|
|
14
14
|
#
|
|
15
|
+
# Also stores env[:metadata][:tool_calls] with the cumulative number of
|
|
16
|
+
# tool invocations in the current session.
|
|
17
|
+
#
|
|
15
18
|
class ToolErrorTracking < Base
|
|
16
19
|
DEFAULT_MAX_FAILURES = 3
|
|
17
20
|
|
|
@@ -19,27 +22,30 @@ module Brute
|
|
|
19
22
|
super(app)
|
|
20
23
|
@max_failures = max_failures
|
|
21
24
|
@errors = Hash.new(0) # tool_name → count
|
|
25
|
+
@total_tool_calls = 0
|
|
22
26
|
end
|
|
23
27
|
|
|
24
28
|
def call(env)
|
|
25
|
-
# PRE: count errors from tool results that are about to be sent
|
|
29
|
+
# PRE: count errors and totals from tool results that are about to be sent
|
|
26
30
|
if (results = env[:tool_results])
|
|
31
|
+
@total_tool_calls += results.size
|
|
32
|
+
|
|
27
33
|
results.each do |name, result|
|
|
28
|
-
if result.is_a?(Hash) && result[:error]
|
|
29
|
-
@errors[name] += 1
|
|
30
|
-
end
|
|
34
|
+
@errors[name] += 1 if result.is_a?(Hash) && result[:error]
|
|
31
35
|
end
|
|
32
36
|
end
|
|
33
37
|
|
|
38
|
+
env[:metadata][:tool_calls] = @total_tool_calls
|
|
34
39
|
env[:metadata][:tool_errors] = @errors.dup
|
|
35
40
|
env[:metadata][:tool_error_limit_reached] = @errors.any? { |_, c| c >= @max_failures }
|
|
36
41
|
|
|
37
42
|
@app.call(env)
|
|
38
43
|
end
|
|
39
44
|
|
|
40
|
-
# Reset
|
|
45
|
+
# Reset counts (e.g., between user turns).
|
|
41
46
|
def reset!
|
|
42
47
|
@errors.clear
|
|
48
|
+
@total_tool_calls = 0
|
|
43
49
|
end
|
|
44
50
|
end
|
|
45
51
|
end
|
|
@@ -2,31 +2,60 @@
|
|
|
2
2
|
|
|
3
3
|
module Brute
|
|
4
4
|
module Middleware
|
|
5
|
-
# Logs timing and token usage for every LLM call
|
|
5
|
+
# Logs timing and token usage for every LLM call, and tracks cumulative
|
|
6
|
+
# timing data in env[:metadata][:timing].
|
|
6
7
|
#
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
8
|
+
# As the outermost middleware, it sees the full pipeline elapsed time per
|
|
9
|
+
# call. It also tracks total wall-clock time across all calls in a turn
|
|
10
|
+
# (including tool execution gaps between LLM calls).
|
|
11
|
+
#
|
|
12
|
+
# A new turn is detected when env[:tool_results] is nil (the orchestrator
|
|
13
|
+
# sets this on the first call of each run()).
|
|
14
|
+
#
|
|
15
|
+
# Stores in env[:metadata][:timing]:
|
|
16
|
+
# total_elapsed: wall-clock since the turn began (includes tool gaps)
|
|
17
|
+
# total_llm_elapsed: cumulative time spent inside LLM calls only
|
|
18
|
+
# llm_call_count: number of LLM calls so far
|
|
19
|
+
# last_call_elapsed: duration of the most recent LLM call
|
|
10
20
|
#
|
|
11
21
|
class Tracing < Base
|
|
12
22
|
def initialize(app, logger:)
|
|
13
23
|
super(app)
|
|
14
24
|
@logger = logger
|
|
15
25
|
@call_count = 0
|
|
26
|
+
@total_llm_elapsed = 0.0
|
|
27
|
+
@turn_start = nil
|
|
16
28
|
end
|
|
17
29
|
|
|
18
30
|
def call(env)
|
|
19
31
|
@call_count += 1
|
|
32
|
+
|
|
33
|
+
# Detect new turn: tool_results is nil on the first pipeline call
|
|
34
|
+
if env[:tool_results].nil?
|
|
35
|
+
@turn_start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
36
|
+
@total_llm_elapsed = 0.0
|
|
37
|
+
end
|
|
38
|
+
|
|
20
39
|
messages = env[:context].messages.to_a
|
|
21
40
|
@logger.debug("[brute] LLM call ##{@call_count} (#{messages.size} messages in context)")
|
|
22
41
|
|
|
23
42
|
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
24
43
|
response = @app.call(env)
|
|
25
|
-
|
|
44
|
+
now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
45
|
+
elapsed = now - start
|
|
46
|
+
|
|
47
|
+
@total_llm_elapsed += elapsed
|
|
26
48
|
|
|
27
|
-
tokens = response.respond_to?(:usage) ? response.usage&.total_tokens :
|
|
49
|
+
tokens = response.respond_to?(:usage) ? response.usage&.total_tokens : '?'
|
|
28
50
|
@logger.info("[brute] LLM response ##{@call_count}: #{tokens} tokens, #{elapsed.round(2)}s")
|
|
29
51
|
|
|
52
|
+
env[:metadata][:timing] = {
|
|
53
|
+
total_elapsed: now - (@turn_start || start),
|
|
54
|
+
total_llm_elapsed: @total_llm_elapsed,
|
|
55
|
+
llm_call_count: @call_count,
|
|
56
|
+
last_call_elapsed: elapsed
|
|
57
|
+
}
|
|
58
|
+
|
|
30
59
|
response
|
|
31
60
|
end
|
|
32
61
|
end
|
data/lib/brute/tools/fs_patch.rb
CHANGED
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
module Brute
|
|
4
4
|
module Tools
|
|
5
5
|
class FSPatch < LLM::Tool
|
|
6
|
-
name
|
|
7
|
-
description
|
|
8
|
-
|
|
6
|
+
name 'patch'
|
|
7
|
+
description 'Replace a specific string in a file. The old_string must match exactly ' \
|
|
8
|
+
'(including whitespace and indentation). Always read a file before patching it.'
|
|
9
9
|
|
|
10
|
-
param :file_path, String,
|
|
11
|
-
param :old_string, String,
|
|
12
|
-
param :new_string, String,
|
|
13
|
-
param :replace_all, Boolean,
|
|
10
|
+
param :file_path, String, 'Path to the file to patch', required: true
|
|
11
|
+
param :old_string, String, 'The exact text to find and replace', required: true
|
|
12
|
+
param :new_string, String, 'The replacement text', required: true
|
|
13
|
+
param :replace_all, Boolean, 'Replace all occurrences (default: false)'
|
|
14
14
|
|
|
15
15
|
def call(file_path:, old_string:, new_string:, replace_all: false)
|
|
16
16
|
path = File.expand_path(file_path)
|
|
@@ -23,13 +23,15 @@ module Brute
|
|
|
23
23
|
Brute::SnapshotStore.save(path)
|
|
24
24
|
|
|
25
25
|
updated = if replace_all
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
original.gsub(old_string, new_string)
|
|
27
|
+
else
|
|
28
|
+
original.sub(old_string, new_string)
|
|
29
|
+
end
|
|
30
30
|
|
|
31
31
|
File.write(path, updated)
|
|
32
|
-
|
|
32
|
+
diff = Brute::Diff.unified(original, updated)
|
|
33
|
+
count = replace_all ? original.scan(old_string).size : 1
|
|
34
|
+
{ success: true, file_path: path, replacements: count, diff: diff }
|
|
33
35
|
end
|
|
34
36
|
end
|
|
35
37
|
end
|
data/lib/brute/tools/fs_write.rb
CHANGED
|
@@ -1,24 +1,26 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require 'fileutils'
|
|
4
4
|
|
|
5
5
|
module Brute
|
|
6
6
|
module Tools
|
|
7
7
|
class FSWrite < LLM::Tool
|
|
8
|
-
name
|
|
8
|
+
name 'write'
|
|
9
9
|
description "Write content to a file. Creates parent directories if they don't exist. " \
|
|
10
|
-
|
|
10
|
+
'Use this for creating new files or completely replacing file contents.'
|
|
11
11
|
|
|
12
|
-
param :file_path, String,
|
|
13
|
-
param :content, String,
|
|
12
|
+
param :file_path, String, 'Path to the file to write', required: true
|
|
13
|
+
param :content, String, 'The full content to write to the file', required: true
|
|
14
14
|
|
|
15
15
|
def call(file_path:, content:)
|
|
16
16
|
path = File.expand_path(file_path)
|
|
17
17
|
Brute::FileMutationQueue.serialize(path) do
|
|
18
|
+
old_content = File.exist?(path) ? File.read(path) : ''
|
|
18
19
|
Brute::SnapshotStore.save(path)
|
|
19
20
|
FileUtils.mkdir_p(File.dirname(path))
|
|
20
21
|
File.write(path, content)
|
|
21
|
-
|
|
22
|
+
diff = Brute::Diff.unified(old_content, content)
|
|
23
|
+
{ success: true, file_path: path, bytes: content.bytesize, diff: diff }
|
|
22
24
|
end
|
|
23
25
|
end
|
|
24
26
|
end
|
data/lib/brute/version.rb
CHANGED
data/lib/brute.rb
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require
|
|
3
|
+
require 'llm'
|
|
4
|
+
require 'timeout'
|
|
5
|
+
require 'logger'
|
|
6
6
|
|
|
7
7
|
# Brute — a coding agent built on llm.rb
|
|
8
8
|
#
|
|
@@ -11,7 +11,7 @@ require "logger"
|
|
|
11
11
|
#
|
|
12
12
|
# Tracing → Retry → Session → Tokens → Compaction → ToolErrors → DoomLoop → Reasoning → [LLM Call]
|
|
13
13
|
#
|
|
14
|
-
require_relative
|
|
14
|
+
require_relative 'brute/version'
|
|
15
15
|
|
|
16
16
|
module Brute
|
|
17
17
|
module Tools; end
|
|
@@ -20,51 +20,51 @@ module Brute
|
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
# Infrastructure
|
|
23
|
-
require_relative
|
|
24
|
-
require_relative
|
|
25
|
-
require_relative
|
|
26
|
-
require_relative
|
|
27
|
-
require_relative
|
|
28
|
-
require_relative
|
|
29
|
-
require_relative
|
|
30
|
-
require_relative
|
|
31
|
-
require_relative
|
|
32
|
-
require_relative
|
|
23
|
+
require_relative 'brute/diff'
|
|
24
|
+
require_relative 'brute/snapshot_store'
|
|
25
|
+
require_relative 'brute/todo_store'
|
|
26
|
+
require_relative 'brute/file_mutation_queue'
|
|
27
|
+
require_relative 'brute/doom_loop'
|
|
28
|
+
require_relative 'brute/hooks'
|
|
29
|
+
require_relative 'brute/compactor'
|
|
30
|
+
require_relative 'brute/system_prompt'
|
|
31
|
+
require_relative 'brute/session'
|
|
32
|
+
require_relative 'brute/pipeline'
|
|
33
|
+
require_relative 'brute/agent_stream'
|
|
33
34
|
|
|
34
35
|
# Provider patches
|
|
35
|
-
require_relative
|
|
36
|
-
require_relative
|
|
36
|
+
require_relative 'brute/patches/anthropic_tool_role'
|
|
37
|
+
require_relative 'brute/patches/buffer_nil_guard'
|
|
37
38
|
|
|
38
39
|
# Middleware (Rack-style)
|
|
39
|
-
require_relative
|
|
40
|
-
require_relative
|
|
41
|
-
require_relative
|
|
42
|
-
require_relative
|
|
43
|
-
require_relative
|
|
44
|
-
require_relative
|
|
45
|
-
require_relative
|
|
46
|
-
require_relative
|
|
47
|
-
require_relative
|
|
48
|
-
require_relative
|
|
40
|
+
require_relative 'brute/middleware/base'
|
|
41
|
+
require_relative 'brute/middleware/llm_call'
|
|
42
|
+
require_relative 'brute/middleware/retry'
|
|
43
|
+
require_relative 'brute/middleware/doom_loop_detection'
|
|
44
|
+
require_relative 'brute/middleware/token_tracking'
|
|
45
|
+
require_relative 'brute/middleware/compaction_check'
|
|
46
|
+
require_relative 'brute/middleware/session_persistence'
|
|
47
|
+
require_relative 'brute/middleware/tracing'
|
|
48
|
+
require_relative 'brute/middleware/tool_error_tracking'
|
|
49
|
+
require_relative 'brute/middleware/reasoning_normalizer'
|
|
49
50
|
|
|
50
51
|
# Tools
|
|
51
|
-
require_relative
|
|
52
|
-
require_relative
|
|
53
|
-
require_relative
|
|
54
|
-
require_relative
|
|
55
|
-
require_relative
|
|
56
|
-
require_relative
|
|
57
|
-
require_relative
|
|
58
|
-
require_relative
|
|
59
|
-
require_relative
|
|
60
|
-
require_relative
|
|
61
|
-
require_relative
|
|
52
|
+
require_relative 'brute/tools/fs_read'
|
|
53
|
+
require_relative 'brute/tools/fs_write'
|
|
54
|
+
require_relative 'brute/tools/fs_patch'
|
|
55
|
+
require_relative 'brute/tools/fs_remove'
|
|
56
|
+
require_relative 'brute/tools/fs_search'
|
|
57
|
+
require_relative 'brute/tools/fs_undo'
|
|
58
|
+
require_relative 'brute/tools/shell'
|
|
59
|
+
require_relative 'brute/tools/net_fetch'
|
|
60
|
+
require_relative 'brute/tools/todo_write'
|
|
61
|
+
require_relative 'brute/tools/todo_read'
|
|
62
|
+
require_relative 'brute/tools/delegate'
|
|
62
63
|
|
|
63
64
|
# Orchestrator (depends on tools, middleware, and infrastructure)
|
|
64
|
-
require_relative
|
|
65
|
+
require_relative 'brute/orchestrator'
|
|
65
66
|
|
|
66
67
|
module Brute
|
|
67
|
-
|
|
68
68
|
# The complete set of tools available to the agent.
|
|
69
69
|
TOOLS = [
|
|
70
70
|
Tools::FSRead,
|
|
@@ -77,7 +77,7 @@ module Brute
|
|
|
77
77
|
Tools::NetFetch,
|
|
78
78
|
Tools::TodoWrite,
|
|
79
79
|
Tools::TodoRead,
|
|
80
|
-
Tools::Delegate
|
|
80
|
+
Tools::Delegate
|
|
81
81
|
].freeze
|
|
82
82
|
|
|
83
83
|
# Default provider, resolved from environment.
|
|
@@ -102,12 +102,12 @@ module Brute
|
|
|
102
102
|
end
|
|
103
103
|
|
|
104
104
|
PROVIDERS = {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
105
|
+
'anthropic' => ->(key) { LLM.anthropic(key: key).tap { Patches::AnthropicToolRole.apply! } },
|
|
106
|
+
'openai' => ->(key) { LLM.openai(key: key) },
|
|
107
|
+
'google' => ->(key) { LLM.google(key: key) },
|
|
108
|
+
'deepseek' => ->(key) { LLM.deepseek(key: key) },
|
|
109
|
+
'ollama' => ->(key) { LLM.ollama(key: key) },
|
|
110
|
+
'xai' => ->(key) { LLM.xai(key: key) }
|
|
111
111
|
}.freeze
|
|
112
112
|
|
|
113
113
|
# Resolve the LLM provider from environment variables.
|
|
@@ -120,24 +120,24 @@ module Brute
|
|
|
120
120
|
#
|
|
121
121
|
# Returns nil if no key is found. Error is deferred to Orchestrator#run.
|
|
122
122
|
def self.resolve_provider
|
|
123
|
-
if ENV[
|
|
124
|
-
key = ENV[
|
|
125
|
-
name = ENV.fetch(
|
|
126
|
-
elsif ENV[
|
|
127
|
-
key = ENV[
|
|
128
|
-
name =
|
|
129
|
-
elsif ENV[
|
|
130
|
-
key = ENV[
|
|
131
|
-
name =
|
|
132
|
-
elsif ENV[
|
|
133
|
-
key = ENV[
|
|
134
|
-
name =
|
|
123
|
+
if ENV['LLM_API_KEY']
|
|
124
|
+
key = ENV['LLM_API_KEY']
|
|
125
|
+
name = ENV.fetch('LLM_PROVIDER', 'anthropic').downcase
|
|
126
|
+
elsif ENV['ANTHROPIC_API_KEY']
|
|
127
|
+
key = ENV['ANTHROPIC_API_KEY']
|
|
128
|
+
name = 'anthropic'
|
|
129
|
+
elsif ENV['OPENAI_API_KEY']
|
|
130
|
+
key = ENV['OPENAI_API_KEY']
|
|
131
|
+
name = 'openai'
|
|
132
|
+
elsif ENV['GOOGLE_API_KEY']
|
|
133
|
+
key = ENV['GOOGLE_API_KEY']
|
|
134
|
+
name = 'google'
|
|
135
135
|
else
|
|
136
136
|
return nil
|
|
137
137
|
end
|
|
138
138
|
|
|
139
139
|
factory = PROVIDERS[name]
|
|
140
|
-
raise "Unknown LLM provider: #{name}. Available: #{PROVIDERS.keys.join(
|
|
140
|
+
raise "Unknown LLM provider: #{name}. Available: #{PROVIDERS.keys.join(', ')}" unless factory
|
|
141
141
|
|
|
142
142
|
factory.call(key)
|
|
143
143
|
end
|
metadata
CHANGED
|
@@ -1,30 +1,30 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: brute
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Brute Contributors
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 1980-01-
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
|
-
name:
|
|
13
|
+
name: async
|
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
|
15
15
|
requirements:
|
|
16
16
|
- - "~>"
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: '
|
|
18
|
+
version: '2.0'
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - "~>"
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: '
|
|
25
|
+
version: '2.0'
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
|
-
name:
|
|
27
|
+
name: diff-lcs
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
29
29
|
requirements:
|
|
30
30
|
- - "~>"
|
|
@@ -37,6 +37,20 @@ dependencies:
|
|
|
37
37
|
- - "~>"
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
39
|
version: '2.0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: llm.rb
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '4.11'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '4.11'
|
|
40
54
|
- !ruby/object:Gem::Dependency
|
|
41
55
|
name: minitest
|
|
42
56
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -74,6 +88,7 @@ files:
|
|
|
74
88
|
- lib/brute.rb
|
|
75
89
|
- lib/brute/agent_stream.rb
|
|
76
90
|
- lib/brute/compactor.rb
|
|
91
|
+
- lib/brute/diff.rb
|
|
77
92
|
- lib/brute/doom_loop.rb
|
|
78
93
|
- lib/brute/file_mutation_queue.rb
|
|
79
94
|
- lib/brute/hooks.rb
|