brute 0.1.7 → 0.1.9

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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/lib/brute/agent_stream.rb +16 -1
  3. data/lib/brute/message_store.rb +269 -0
  4. data/lib/brute/middleware/compaction_check.rb +5 -2
  5. data/lib/brute/middleware/message_tracking.rb +209 -0
  6. data/lib/brute/middleware/otel/span.rb +75 -0
  7. data/lib/brute/middleware/otel/token_usage.rb +30 -0
  8. data/lib/brute/middleware/otel/tool_calls.rb +39 -0
  9. data/lib/brute/middleware/otel/tool_results.rb +37 -0
  10. data/lib/brute/middleware/otel.rb +29 -0
  11. data/lib/brute/middleware/tool_use_guard.rb +66 -23
  12. data/lib/brute/orchestrator.rb +46 -13
  13. data/lib/brute/prompts/autonomy.rb +21 -0
  14. data/lib/brute/prompts/base.rb +23 -0
  15. data/lib/brute/prompts/build_switch.rb +19 -0
  16. data/lib/brute/prompts/code_references.rb +21 -0
  17. data/lib/brute/prompts/code_style.rb +16 -0
  18. data/lib/brute/prompts/conventions.rb +20 -0
  19. data/lib/brute/prompts/doing_tasks.rb +11 -0
  20. data/lib/brute/prompts/editing_approach.rb +20 -0
  21. data/lib/brute/prompts/editing_constraints.rb +24 -0
  22. data/lib/brute/prompts/environment.rb +25 -0
  23. data/lib/brute/prompts/frontend_tasks.rb +21 -0
  24. data/lib/brute/prompts/git_safety.rb +19 -0
  25. data/lib/brute/prompts/identity.rb +11 -0
  26. data/lib/brute/prompts/instructions.rb +18 -0
  27. data/lib/brute/prompts/max_steps.rb +30 -0
  28. data/lib/brute/prompts/objectivity.rb +16 -0
  29. data/lib/brute/prompts/plan_reminder.rb +40 -0
  30. data/lib/brute/prompts/proactiveness.rb +19 -0
  31. data/lib/brute/prompts/security_and_safety.rb +17 -0
  32. data/lib/brute/prompts/skills.rb +22 -0
  33. data/lib/brute/prompts/task_management.rb +59 -0
  34. data/lib/brute/prompts/text/agents/compaction.txt +15 -0
  35. data/lib/brute/prompts/text/agents/explore.txt +17 -0
  36. data/lib/brute/prompts/text/agents/summary.txt +11 -0
  37. data/lib/brute/prompts/text/agents/title.txt +40 -0
  38. data/lib/brute/prompts/text/doing_tasks/anthropic.txt +11 -0
  39. data/lib/brute/prompts/text/doing_tasks/default.txt +6 -0
  40. data/lib/brute/prompts/text/doing_tasks/google.txt +9 -0
  41. data/lib/brute/prompts/text/identity/anthropic.txt +5 -0
  42. data/lib/brute/prompts/text/identity/default.txt +3 -0
  43. data/lib/brute/prompts/text/identity/google.txt +1 -0
  44. data/lib/brute/prompts/text/identity/openai.txt +3 -0
  45. data/lib/brute/prompts/text/tone_and_style/anthropic.txt +5 -0
  46. data/lib/brute/prompts/text/tone_and_style/default.txt +9 -0
  47. data/lib/brute/prompts/text/tone_and_style/google.txt +6 -0
  48. data/lib/brute/prompts/text/tone_and_style/openai.txt +17 -0
  49. data/lib/brute/prompts/text/tool_usage/anthropic.txt +16 -0
  50. data/lib/brute/prompts/text/tool_usage/default.txt +4 -0
  51. data/lib/brute/prompts/text/tool_usage/google.txt +4 -0
  52. data/lib/brute/prompts/tone_and_style.rb +11 -0
  53. data/lib/brute/prompts/tool_usage.rb +11 -0
  54. data/lib/brute/session.rb +109 -34
  55. data/lib/brute/skill.rb +118 -0
  56. data/lib/brute/system_prompt.rb +119 -64
  57. data/lib/brute/tools/question.rb +59 -0
  58. data/lib/brute/version.rb +1 -1
  59. data/lib/brute.rb +62 -2
  60. metadata +52 -2
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "yaml"
4
+
5
+ module Brute
6
+ # Discovers and loads SKILL.md files from standard directories.
7
+ #
8
+ # A skill is a markdown file with YAML frontmatter:
9
+ #
10
+ # ---
11
+ # name: debugging
12
+ # description: Systematic debugging workflow for isolating and fixing bugs
13
+ # ---
14
+ #
15
+ # When debugging, follow these steps...
16
+ #
17
+ # Skills are scanned from (in order):
18
+ # 1. .brute/skills/**/SKILL.md (project-local)
19
+ # 2. ~/.config/brute/skills/**/SKILL.md (global)
20
+ #
21
+ # The directory name containing SKILL.md becomes the skill name if frontmatter
22
+ # doesn't specify one.
23
+ #
24
+ module Skill
25
+ Info = Struct.new(:name, :description, :location, :content, keyword_init: true)
26
+
27
+ FILENAME = "SKILL.md"
28
+
29
+ # Scan all skill directories and return an array of Info structs.
30
+ def self.all(cwd: Dir.pwd)
31
+ skills = {}
32
+
33
+ scan_dirs(cwd).each do |dir|
34
+ Dir.glob(File.join(dir, "**", FILENAME)).sort.each do |path|
35
+ info = load(path)
36
+ next unless info
37
+ # First found wins (project-local overrides global)
38
+ skills[info.name] ||= info
39
+ end
40
+ end
41
+
42
+ skills.values.sort_by(&:name)
43
+ end
44
+
45
+ # Get a single skill by name.
46
+ def self.get(name, cwd: Dir.pwd)
47
+ all(cwd: cwd).detect { |s| s.name == name }
48
+ end
49
+
50
+ # Format skills as XML for the system prompt.
51
+ def self.fmt(skills)
52
+ return nil if skills.empty?
53
+
54
+ lines = ["<available_skills>"]
55
+ skills.each do |skill|
56
+ lines << " <skill>"
57
+ lines << " <name>#{skill.name}</name>"
58
+ lines << " <description>#{skill.description}</description>"
59
+ lines << " </skill>"
60
+ end
61
+ lines << "</available_skills>"
62
+ lines.join("\n")
63
+ end
64
+
65
+ # Parse a SKILL.md file into an Info struct.
66
+ # Returns nil if the file is invalid or missing required fields.
67
+ def self.load(path)
68
+ raw = File.read(path)
69
+ frontmatter, content = parse_frontmatter(raw)
70
+ return nil unless frontmatter
71
+
72
+ name = frontmatter["name"] || File.basename(File.dirname(path))
73
+ description = frontmatter["description"]
74
+ return nil unless description && !description.strip.empty?
75
+
76
+ Info.new(
77
+ name: name.to_s.strip,
78
+ description: description.to_s.strip,
79
+ location: path,
80
+ content: content.to_s.strip,
81
+ )
82
+ rescue => e
83
+ warn "Failed to load skill #{path}: #{e.message}"
84
+ nil
85
+ end
86
+
87
+ # Directories to scan for skills, in priority order.
88
+ def self.scan_dirs(cwd)
89
+ dirs = []
90
+
91
+ # Project-local
92
+ project = File.join(cwd, ".brute", "skills")
93
+ dirs << project if File.directory?(project)
94
+
95
+ # Global
96
+ global = File.join(Dir.home, ".config", "brute", "skills")
97
+ dirs << global if File.directory?(global)
98
+
99
+ dirs
100
+ end
101
+
102
+ # Split YAML frontmatter from markdown body.
103
+ # Returns [hash, string] or [nil, nil].
104
+ def self.parse_frontmatter(raw)
105
+ return [nil, nil] unless raw.start_with?("---")
106
+
107
+ parts = raw.split(/^---\s*$/, 3)
108
+ return [nil, nil] if parts.length < 3
109
+
110
+ frontmatter = YAML.safe_load(parts[1])
111
+ return [nil, nil] unless frontmatter.is_a?(Hash)
112
+
113
+ [frontmatter, parts[2]]
114
+ end
115
+
116
+ private_class_method :scan_dirs, :parse_frontmatter
117
+ end
118
+ end
@@ -1,88 +1,143 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Brute
4
- # Builds the system prompt dynamically based on available tools, environment,
5
- # custom rules, and working directory context.
4
+ # Deferred system prompt builder.
5
+ #
6
+ # The block passed to +build+ is stored — not executed — until +prepare+
7
+ # is called with a runtime context hash (provider_name, model_name, cwd, etc).
8
+ #
9
+ # sp = Brute::SystemPrompt.build do |prompt, ctx|
10
+ # prompt << Brute::Prompts::Identity.call(ctx)
11
+ # prompt << Brute::Prompts::ToneAndStyle.call(ctx)
12
+ # prompt << Brute::Prompts::Environment.call(ctx)
13
+ # end
14
+ #
15
+ # result = sp.prepare(provider_name: "anthropic", model_name: "claude-sonnet-4-20250514", cwd: Dir.pwd)
16
+ # result.to_s # single joined string
17
+ # result.sections # array of strings (one per p.system call)
6
18
  #
7
- # Modeled after forgecode's SystemPrompt which composes a static agent
8
- # personality block with dynamic environment/tool information.
9
19
  class SystemPrompt
10
- def initialize(cwd: Dir.pwd, tools: [], custom_rules: nil)
11
- @cwd = cwd
12
- @tools = tools
13
- @custom_rules = custom_rules
20
+ # Build a deferred system prompt. The block is stored and called later by +prepare+.
21
+ def self.build(&block)
22
+ new(block)
14
23
  end
15
24
 
16
- def build
17
- sections = []
18
- sections << identity_section
19
- sections << tools_section
20
- sections << guidelines_section
21
- sections << environment_section
22
- sections << custom_rules_section if @custom_rules
23
- sections.compact.join("\n\n")
24
- end
25
+ # Return the default system prompt. Selects the right provider stack at
26
+ # prepare-time, then appends conditional sections based on runtime state.
27
+ def self.default
28
+ build do |prompt, ctx|
29
+ # Provider-specific base stack
30
+ provider = ctx[:provider_name].to_s
31
+ STACKS.fetch(provider, STACKS["default"]).each do |mod|
32
+ prompt << mod.call(ctx)
33
+ end
25
34
 
26
- private
35
+ # Conditional: agent-specific reminders
36
+ if ctx[:agent] == "plan"
37
+ prompt << Prompts::PlanReminder.call(ctx)
38
+ end
27
39
 
28
- def identity_section
29
- <<~SECTION
30
- # Identity
40
+ if ctx[:agent_switched] == "build"
41
+ prompt << Prompts::BuildSwitch.call(ctx)
42
+ end
31
43
 
32
- You are Brute, an expert software engineering agent. You help users with coding tasks
33
- by reading, writing, and editing files, running shell commands, and searching codebases.
34
- You are methodical, precise, and always verify your work.
35
- SECTION
44
+ if ctx[:max_steps_reached]
45
+ prompt << Prompts::MaxSteps.call(ctx)
46
+ end
47
+ end
36
48
  end
37
49
 
38
- def tools_section
39
- tool_list = LLM::Function.registry.filter_map { |fn|
40
- "- **#{fn.name}**: #{fn.description.to_s.split(". ").first}."
41
- }.join("\n")
50
+ # Pre-configured prompt stacks per provider.
51
+ # Each maps a provider name to an ordered list of prompt modules.
52
+ STACKS = {
53
+ # Claude — full-featured with task management and detailed tool policy
54
+ "anthropic" => [
55
+ Prompts::Identity,
56
+ Prompts::ToneAndStyle,
57
+ Prompts::Objectivity,
58
+ Prompts::TaskManagement,
59
+ Prompts::DoingTasks,
60
+ Prompts::ToolUsage,
61
+ Prompts::Conventions,
62
+ Prompts::GitSafety,
63
+ Prompts::CodeReferences,
64
+ Prompts::Environment,
65
+ Prompts::Skills,
66
+ Prompts::Instructions,
67
+ ],
42
68
 
43
- <<~SECTION
44
- # Available Tools
69
+ # GPT-4 / o1 / o3 — pragmatic engineer persona, editing focus, autonomy
70
+ "openai" => [
71
+ Prompts::Identity,
72
+ Prompts::EditingApproach,
73
+ Prompts::Autonomy,
74
+ Prompts::EditingConstraints,
75
+ Prompts::FrontendTasks,
76
+ Prompts::ToneAndStyle,
77
+ Prompts::Conventions,
78
+ Prompts::GitSafety,
79
+ Prompts::CodeReferences,
80
+ Prompts::Environment,
81
+ Prompts::Skills,
82
+ Prompts::Instructions,
83
+ ],
45
84
 
46
- #{tool_list}
47
- SECTION
48
- end
85
+ # Gemini — formal/structured, explicit workflows, security focus
86
+ "google" => [
87
+ Prompts::Identity,
88
+ Prompts::Conventions,
89
+ Prompts::DoingTasks,
90
+ Prompts::ToneAndStyle,
91
+ Prompts::SecurityAndSafety,
92
+ Prompts::ToolUsage,
93
+ Prompts::GitSafety,
94
+ Prompts::CodeReferences,
95
+ Prompts::Environment,
96
+ Prompts::Skills,
97
+ Prompts::Instructions,
98
+ ],
49
99
 
50
- def guidelines_section
51
- <<~SECTION
52
- # Guidelines
100
+ # Fallback — conservative, concise, fewer than 4 lines
101
+ "default" => [
102
+ Prompts::Identity,
103
+ Prompts::ToneAndStyle,
104
+ Prompts::Proactiveness,
105
+ Prompts::Conventions,
106
+ Prompts::CodeStyle,
107
+ Prompts::DoingTasks,
108
+ Prompts::ToolUsage,
109
+ Prompts::GitSafety,
110
+ Prompts::CodeReferences,
111
+ Prompts::Environment,
112
+ Prompts::Skills,
113
+ Prompts::Instructions,
114
+ ],
115
+ }.freeze
53
116
 
54
- - **Always read before editing**: Use `read` to examine a file before using `patch` or `write` to modify it.
55
- - **Verify your changes**: After editing, re-read the file or run tests to confirm correctness.
56
- - **Use todo_write for multi-step tasks**: Break complex work into steps and track progress.
57
- - **Use fs_search to find code**: Don't guess file locations — search first.
58
- - **Use shell for git, tests, builds**: Run `git diff`, `git status`, test suites, etc.
59
- - **Be precise with patch**: The `old_string` must match the file content exactly, including whitespace.
60
- - **Prefer patch over write**: For existing files, use `patch` to change specific sections rather than rewriting the entire file.
61
- - **Use undo to recover**: If a write or patch goes wrong, use `undo` to restore the previous version.
62
- - **Delegate research**: Use `delegate` for complex analysis that needs focused investigation.
63
- SECTION
117
+ def initialize(block)
118
+ @block = block
64
119
  end
65
120
 
66
- def environment_section
67
- files = Dir.entries(@cwd).reject { |f| f.start_with?(".") }.sort.first(50)
68
-
69
- <<~SECTION
70
- # Environment
71
-
72
- - **Working directory**: #{@cwd}
73
- - **OS**: #{RUBY_PLATFORM}
74
- - **Ruby**: #{RUBY_VERSION}
75
- - **Date**: #{Time.now.strftime("%Y-%m-%d")}
76
- - **Files in cwd**: #{files.join(", ")}
77
- SECTION
121
+ # Execute the stored block with the given context and return a Result.
122
+ def prepare(ctx)
123
+ sections = []
124
+ @block.call(sections, ctx)
125
+ Result.new(sections.compact.reject { |s| s.respond_to?(:empty?) && s.empty? })
78
126
  end
79
127
 
80
- def custom_rules_section
81
- <<~SECTION
82
- # Project-Specific Rules
128
+ # Immutable result of a prepared system prompt.
129
+ Result = Struct.new(:sections) do
130
+ def to_s
131
+ sections.join("\n\n")
132
+ end
133
+
134
+ def each(&block)
135
+ sections.each(&block)
136
+ end
83
137
 
84
- #{@custom_rules}
85
- SECTION
138
+ def empty?
139
+ sections.empty?
140
+ end
86
141
  end
87
142
  end
88
143
  end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Brute
4
+ module Tools
5
+ class Question < LLM::Tool
6
+ name "question"
7
+ description "Ask the user questions during execution. Use this to gather preferences, " \
8
+ "clarify ambiguous instructions, get decisions on implementation choices, or " \
9
+ "offer choices about direction. Users can always select \"Other\" to provide " \
10
+ "custom text input. Answers are returned as arrays of labels; set multiple: true " \
11
+ "to allow selecting more than one. If you recommend a specific option, make it " \
12
+ "the first option and add \"(Recommended)\" at the end of the label."
13
+
14
+ params do |s|
15
+ s.object(
16
+ questions: s.array(
17
+ s.object(
18
+ question: s.string.required,
19
+ header: s.string.required,
20
+ options: s.array(
21
+ s.object(
22
+ label: s.string.required,
23
+ description: s.string.required,
24
+ )
25
+ ).required,
26
+ multiple: s.boolean,
27
+ )
28
+ ).required
29
+ )
30
+ end
31
+
32
+ def call(questions:)
33
+ handler = Thread.current[:on_question]
34
+ unless handler
35
+ return { error: true, message: "Cannot ask questions in non-interactive mode" }
36
+ end
37
+
38
+ queue = Queue.new
39
+ handler.call(questions, queue)
40
+ answers = queue.pop
41
+
42
+ format_answers(questions, answers)
43
+ end
44
+
45
+ private
46
+
47
+ def format_answers(questions, answers)
48
+ pairs = questions.each_with_index.map do |q, i|
49
+ q = q.transform_keys(&:to_s) if q.is_a?(Hash)
50
+ header = q["header"]
51
+ answer = answers[i] || []
52
+ "\"#{header}\" = #{answer.join(', ')}"
53
+ end
54
+
55
+ { response: "User answered: #{pairs.join('; ')}" }
56
+ end
57
+ end
58
+ end
59
+ end
data/lib/brute/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Brute
4
- VERSION = "0.1.7"
4
+ VERSION = "0.1.9"
5
5
  end
data/lib/brute.rb CHANGED
@@ -27,7 +27,32 @@ require_relative 'brute/file_mutation_queue'
27
27
  require_relative 'brute/doom_loop'
28
28
  require_relative 'brute/hooks'
29
29
  require_relative 'brute/compactor'
30
+ require_relative 'brute/prompts/base'
31
+ require_relative 'brute/prompts/identity'
32
+ require_relative 'brute/prompts/tone_and_style'
33
+ require_relative 'brute/prompts/objectivity'
34
+ require_relative 'brute/prompts/task_management'
35
+ require_relative 'brute/prompts/doing_tasks'
36
+ require_relative 'brute/prompts/tool_usage'
37
+ require_relative 'brute/prompts/conventions'
38
+ require_relative 'brute/prompts/git_safety'
39
+ require_relative 'brute/prompts/code_references'
40
+ require_relative 'brute/prompts/environment'
41
+ require_relative 'brute/prompts/instructions'
42
+ require_relative 'brute/prompts/editing_approach'
43
+ require_relative 'brute/prompts/autonomy'
44
+ require_relative 'brute/prompts/editing_constraints'
45
+ require_relative 'brute/prompts/frontend_tasks'
46
+ require_relative 'brute/prompts/proactiveness'
47
+ require_relative 'brute/prompts/code_style'
48
+ require_relative 'brute/prompts/security_and_safety'
49
+ require_relative 'brute/prompts/skills'
50
+ require_relative 'brute/prompts/plan_reminder'
51
+ require_relative 'brute/prompts/max_steps'
52
+ require_relative 'brute/prompts/build_switch'
53
+ require_relative 'brute/skill'
30
54
  require_relative 'brute/system_prompt'
55
+ require_relative 'brute/message_store'
31
56
  require_relative 'brute/session'
32
57
  require_relative 'brute/pipeline'
33
58
  require_relative 'brute/agent_stream'
@@ -44,10 +69,12 @@ require_relative 'brute/middleware/doom_loop_detection'
44
69
  require_relative 'brute/middleware/token_tracking'
45
70
  require_relative 'brute/middleware/compaction_check'
46
71
  require_relative 'brute/middleware/session_persistence'
72
+ require_relative 'brute/middleware/message_tracking'
47
73
  require_relative 'brute/middleware/tracing'
48
74
  require_relative 'brute/middleware/tool_error_tracking'
49
75
  require_relative 'brute/middleware/reasoning_normalizer'
50
76
  require_relative "brute/middleware/tool_use_guard"
77
+ require_relative "brute/middleware/otel"
51
78
 
52
79
  # Tools
53
80
  require_relative 'brute/tools/fs_read'
@@ -61,6 +88,7 @@ require_relative 'brute/tools/net_fetch'
61
88
  require_relative 'brute/tools/todo_write'
62
89
  require_relative 'brute/tools/todo_read'
63
90
  require_relative 'brute/tools/delegate'
91
+ require_relative 'brute/tools/question'
64
92
 
65
93
  # Orchestrator (depends on tools, middleware, and infrastructure)
66
94
  require_relative 'brute/orchestrator'
@@ -78,7 +106,8 @@ module Brute
78
106
  Tools::NetFetch,
79
107
  Tools::TodoWrite,
80
108
  Tools::TodoRead,
81
- Tools::Delegate
109
+ Tools::Delegate,
110
+ Tools::Question
82
111
  ].freeze
83
112
 
84
113
  # Default provider, resolved from environment.
@@ -91,13 +120,15 @@ module Brute
91
120
  end
92
121
 
93
122
  # Create a new orchestrator with sensible defaults.
94
- def self.agent(cwd: Dir.pwd, tools: TOOLS, session: nil, reasoning: {}, **callbacks)
123
+ def self.agent(cwd: Dir.pwd, model: nil, tools: TOOLS, session: nil, reasoning: {}, agent_name: nil, **callbacks)
95
124
  Orchestrator.new(
96
125
  provider: provider,
126
+ model: model,
97
127
  tools: tools,
98
128
  cwd: cwd,
99
129
  session: session,
100
130
  reasoning: reasoning,
131
+ agent_name: agent_name,
101
132
  **callbacks
102
133
  )
103
134
  end
@@ -111,6 +142,35 @@ module Brute
111
142
  'xai' => ->(key) { LLM.xai(key: key) }
112
143
  }.freeze
113
144
 
145
+ # List provider names that have API keys configured in the environment.
146
+ def self.configured_providers
147
+ PROVIDERS.keys.select { |name| api_key_for(name) }
148
+ end
149
+
150
+ # Build a provider instance for the given name using available API keys.
151
+ # Returns nil if no key is found.
152
+ def self.provider_for(name)
153
+ key = api_key_for(name)
154
+ return nil unless key
155
+
156
+ factory = PROVIDERS[name]
157
+ return nil unless factory
158
+
159
+ factory.call(key)
160
+ end
161
+
162
+ # Look up the API key for a given provider name.
163
+ def self.api_key_for(name)
164
+ # Explicit generic key always works
165
+ return ENV["LLM_API_KEY"] if ENV["LLM_API_KEY"]
166
+
167
+ case name
168
+ when "anthropic" then ENV["ANTHROPIC_API_KEY"]
169
+ when "openai" then ENV["OPENAI_API_KEY"]
170
+ when "google" then ENV["GOOGLE_API_KEY"]
171
+ end
172
+ end
173
+
114
174
  # Resolve the LLM provider from environment variables.
115
175
  #
116
176
  # Checks in order:
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: brute
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.7
4
+ version: 0.1.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brute Contributors
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
10
+ date: 1980-01-01 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: async
@@ -92,10 +92,17 @@ files:
92
92
  - lib/brute/doom_loop.rb
93
93
  - lib/brute/file_mutation_queue.rb
94
94
  - lib/brute/hooks.rb
95
+ - lib/brute/message_store.rb
95
96
  - lib/brute/middleware/base.rb
96
97
  - lib/brute/middleware/compaction_check.rb
97
98
  - lib/brute/middleware/doom_loop_detection.rb
98
99
  - lib/brute/middleware/llm_call.rb
100
+ - lib/brute/middleware/message_tracking.rb
101
+ - lib/brute/middleware/otel.rb
102
+ - lib/brute/middleware/otel/span.rb
103
+ - lib/brute/middleware/otel/token_usage.rb
104
+ - lib/brute/middleware/otel/tool_calls.rb
105
+ - lib/brute/middleware/otel/tool_results.rb
99
106
  - lib/brute/middleware/reasoning_normalizer.rb
100
107
  - lib/brute/middleware/retry.rb
101
108
  - lib/brute/middleware/session_persistence.rb
@@ -107,7 +114,49 @@ files:
107
114
  - lib/brute/patches/anthropic_tool_role.rb
108
115
  - lib/brute/patches/buffer_nil_guard.rb
109
116
  - lib/brute/pipeline.rb
117
+ - lib/brute/prompts/autonomy.rb
118
+ - lib/brute/prompts/base.rb
119
+ - lib/brute/prompts/build_switch.rb
120
+ - lib/brute/prompts/code_references.rb
121
+ - lib/brute/prompts/code_style.rb
122
+ - lib/brute/prompts/conventions.rb
123
+ - lib/brute/prompts/doing_tasks.rb
124
+ - lib/brute/prompts/editing_approach.rb
125
+ - lib/brute/prompts/editing_constraints.rb
126
+ - lib/brute/prompts/environment.rb
127
+ - lib/brute/prompts/frontend_tasks.rb
128
+ - lib/brute/prompts/git_safety.rb
129
+ - lib/brute/prompts/identity.rb
130
+ - lib/brute/prompts/instructions.rb
131
+ - lib/brute/prompts/max_steps.rb
132
+ - lib/brute/prompts/objectivity.rb
133
+ - lib/brute/prompts/plan_reminder.rb
134
+ - lib/brute/prompts/proactiveness.rb
135
+ - lib/brute/prompts/security_and_safety.rb
136
+ - lib/brute/prompts/skills.rb
137
+ - lib/brute/prompts/task_management.rb
138
+ - lib/brute/prompts/text/agents/compaction.txt
139
+ - lib/brute/prompts/text/agents/explore.txt
140
+ - lib/brute/prompts/text/agents/summary.txt
141
+ - lib/brute/prompts/text/agents/title.txt
142
+ - lib/brute/prompts/text/doing_tasks/anthropic.txt
143
+ - lib/brute/prompts/text/doing_tasks/default.txt
144
+ - lib/brute/prompts/text/doing_tasks/google.txt
145
+ - lib/brute/prompts/text/identity/anthropic.txt
146
+ - lib/brute/prompts/text/identity/default.txt
147
+ - lib/brute/prompts/text/identity/google.txt
148
+ - lib/brute/prompts/text/identity/openai.txt
149
+ - lib/brute/prompts/text/tone_and_style/anthropic.txt
150
+ - lib/brute/prompts/text/tone_and_style/default.txt
151
+ - lib/brute/prompts/text/tone_and_style/google.txt
152
+ - lib/brute/prompts/text/tone_and_style/openai.txt
153
+ - lib/brute/prompts/text/tool_usage/anthropic.txt
154
+ - lib/brute/prompts/text/tool_usage/default.txt
155
+ - lib/brute/prompts/text/tool_usage/google.txt
156
+ - lib/brute/prompts/tone_and_style.rb
157
+ - lib/brute/prompts/tool_usage.rb
110
158
  - lib/brute/session.rb
159
+ - lib/brute/skill.rb
111
160
  - lib/brute/snapshot_store.rb
112
161
  - lib/brute/system_prompt.rb
113
162
  - lib/brute/todo_store.rb
@@ -119,6 +168,7 @@ files:
119
168
  - lib/brute/tools/fs_undo.rb
120
169
  - lib/brute/tools/fs_write.rb
121
170
  - lib/brute/tools/net_fetch.rb
171
+ - lib/brute/tools/question.rb
122
172
  - lib/brute/tools/shell.rb
123
173
  - lib/brute/tools/todo_read.rb
124
174
  - lib/brute/tools/todo_write.rb