brute 3.2.2 → 4.1.0

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.
data/lib/brute/skill.rb CHANGED
@@ -6,7 +6,7 @@ require "bundler/setup"
6
6
  require "brute"
7
7
 
8
8
  module Brute
9
- # Discovers, validates, and loads SKILL.md files from standard directories.
9
+ # A single skill: metadata plus the address of its SKILL.md on disk.
10
10
  #
11
11
  # A skill is a directory containing a SKILL.md markdown file with YAML
12
12
  # frontmatter:
@@ -18,71 +18,60 @@ module Brute
18
18
  #
19
19
  # When debugging, follow these steps...
20
20
  #
21
- # Skills are scanned from (in order):
22
- # 1. .brute/skills/**/SKILL.md (project-local)
23
- # 2. ~/.config/brute/skills/**/SKILL.md (global)
21
+ # The object is a value object — it carries the parsed frontmatter, the
22
+ # body, and the file location, nothing else. Modeled on prime-agent's
23
+ # BaseSkill (packages/coding-agent/src/core/skills.ts).
24
24
  #
25
- # Parsing and validation mirror the Agent Skills specification
26
- # (https://agentskills.io/specification) and its reference validator
27
- # (https://github.com/agentskills/agentskills/tree/main/skills-ref). A skill
28
- # whose frontmatter violates a rule is skipped with a stderr warning naming
29
- # the violated rule, never raised.
25
+ # Discovery is class-level and caller-side: Skill.all scans (in order)
26
+ # 1. <cwd>/.brute/skills/**/SKILL.md (project-local, :project)
27
+ # 2. ~/.config/brute/skills/**/SKILL.md (global, :user)
28
+ # 3. explicit paths: (dirs or .md files, :path)
29
+ #
30
+ # First found wins on name collisions (with a stderr warning naming winner
31
+ # and loser), and the same file reached twice via symlinks is skipped.
30
32
  #
31
- module Skill
32
- Info = Struct.new(
33
- :name, :description, :location, :content,
34
- :license, :compatibility, :metadata, :allowed_tools,
35
- keyword_init: true,
36
- )
33
+ # Parsing and validation mirror the Agent Skills specification
34
+ # (https://agentskills.io/specification). A skill whose frontmatter violates
35
+ # a rule is skipped with a stderr warning naming the rule — never raised.
36
+ class Skill
37
+ attr_reader :name, :description, :file_path, :base_dir, :content,
38
+ :source, :license, :compatibility, :metadata, :allowed_tools
37
39
 
38
40
  FILENAME = "SKILL.md"
39
41
 
40
42
  # Frontmatter keys permitted by the spec. Anything else is a violation.
41
- ALLOWED_FIELDS = %w[name description license allowed-tools metadata compatibility].freeze
43
+ ALLOWED_FIELDS = %w[name description license allowed-tools metadata compatibility disable-model-invocation].freeze
42
44
 
43
45
  MAX_NAME_LENGTH = 64
44
46
  MAX_DESCRIPTION_LENGTH = 1024
45
47
  MAX_COMPATIBILITY_LENGTH = 500
46
48
 
47
- # Scan all skill directories and return an array of Info structs.
48
- def self.all(cwd: Dir.pwd)
49
- skills = {}
50
-
51
- scan_dirs(cwd).each do |dir|
52
- Dir.glob(File.join(dir, "**", FILENAME)).sort.each do |path|
53
- info = load(path)
54
- next unless info
55
- # First found wins (project-local overrides global)
56
- skills[info.name] ||= info
57
- end
58
- end
59
-
60
- skills.values.sort_by(&:name)
49
+ def initialize(name:, description:, file_path:, content: nil, source: :path,
50
+ license: nil, compatibility: nil, metadata: nil,
51
+ allowed_tools: nil, disable_model_invocation: false)
52
+ @name = name
53
+ @description = description
54
+ @file_path = file_path
55
+ @base_dir = File.dirname(file_path)
56
+ @content = content
57
+ @source = source
58
+ @license = license
59
+ @compatibility = compatibility
60
+ @metadata = metadata
61
+ @allowed_tools = allowed_tools
62
+ @disable_model_invocation = disable_model_invocation
61
63
  end
62
64
 
63
- # Get a single skill by name.
64
- def self.get(name, cwd: Dir.pwd)
65
- all(cwd: cwd).detect { |s| s.name == name }
66
- end
65
+ # Hidden from the prompt listing (explicit invocation only), but still
66
+ # handed to the agent as an object.
67
+ def disable_model_invocation? = @disable_model_invocation
67
68
 
68
- # Format skills as XML for the system prompt.
69
- def self.fmt(skills)
70
- return nil if skills.empty?
69
+ # Back-compat alias (Tools::SkillLoad era).
70
+ def location = file_path
71
71
 
72
- lines = ["<available_skills>"]
73
- skills.each do |skill|
74
- lines << " <skill>"
75
- lines << " <name>#{skill.name}</name>"
76
- lines << " <description>#{skill.description}</description>"
77
- lines << " </skill>"
78
- end
79
- lines << "</available_skills>"
80
- lines.join("\n")
81
- end
82
-
83
- # Parse and validate a SKILL.md file into an Info struct.
72
+ # Parse and validate a SKILL.md file into a Skill.
84
73
  # Returns nil (with a stderr warning) if the file is invalid.
85
- def self.load(path)
74
+ def self.load(path, source: :path)
86
75
  raw = File.read(path)
87
76
  frontmatter, content = parse_frontmatter(path, raw)
88
77
  return nil unless frontmatter
@@ -105,21 +94,86 @@ module Brute
105
94
  return nil
106
95
  end
107
96
 
108
- Info.new(
97
+ new(
109
98
  name: frontmatter["name"].to_s.strip,
110
99
  description: frontmatter["description"].to_s.strip,
111
- location: path,
100
+ file_path: path,
112
101
  content: content.to_s.strip,
102
+ source: source,
113
103
  license: frontmatter["license"]&.to_s,
114
104
  compatibility: frontmatter["compatibility"]&.to_s,
115
105
  metadata: frontmatter["metadata"],
116
106
  allowed_tools: parse_allowed_tools(frontmatter["allowed-tools"]),
107
+ disable_model_invocation: frontmatter["disable-model-invocation"] == true,
117
108
  )
118
109
  rescue => e
119
110
  warn "Failed to load skill #{path}: #{e.message}"
120
111
  nil
121
112
  end
122
113
 
114
+ # Scan all skill directories and return an array of Skills, sorted by name.
115
+ #
116
+ # Precedence is first-found-wins: project-local overrides global overrides
117
+ # explicit paths. Name collisions warn to stderr naming winner and loser;
118
+ # the same file reached via different symlinks is loaded only once.
119
+ def self.all(cwd: Dir.pwd, paths: [])
120
+ skills = {}
121
+ seen_files = {}
122
+
123
+ add = lambda do |path, source|
124
+ skill = load(path, source: source)
125
+ return unless skill
126
+
127
+ real = realpath(path)
128
+ return if seen_files[real]
129
+
130
+ if (winner = skills[skill.name])
131
+ warn "Skill name collision: '#{skill.name}' at #{path} ignored; " \
132
+ "already loaded from #{winner.file_path}"
133
+ return
134
+ end
135
+
136
+ seen_files[real] = true
137
+ skills[skill.name] = skill
138
+ end
139
+
140
+ project = File.join(cwd, ".brute", "skills")
141
+ glob(project) { |path| add.call(path, :project) }
142
+
143
+ global = File.join(Dir.home, ".config", "brute", "skills")
144
+ glob(global) { |path| add.call(path, :user) }
145
+
146
+ paths.each do |raw|
147
+ path = File.expand_path(raw.to_s.sub(/\A~(?=\/|\z)/, Dir.home))
148
+ if File.directory?(path)
149
+ glob(path) { |p| add.call(p, :path) }
150
+ elsif File.file?(path) && path.end_with?(".md")
151
+ add.call(path, :path)
152
+ else
153
+ warn "Skill path #{path} is not a directory or markdown file (ignored)"
154
+ end
155
+ end
156
+
157
+ skills.values.sort_by(&:name)
158
+ end
159
+
160
+ # Get a single skill by name through the same scan as .all.
161
+ def self.get(name, cwd: Dir.pwd, paths: [])
162
+ all(cwd: cwd, paths: paths).detect { |s| s.name == name }
163
+ end
164
+
165
+ def self.glob(dir, &block)
166
+ return unless File.directory?(dir)
167
+
168
+ Dir.glob(File.join(dir, "**", FILENAME)).sort.each(&block)
169
+ end
170
+
171
+ def self.realpath(path)
172
+ File.realpath(path)
173
+ rescue SystemCallError
174
+ path
175
+ end
176
+
123
177
  # Validate frontmatter against the spec. Returns an array of error strings
124
178
  # (empty means valid), each naming the violated rule.
125
179
  def self.validate(frontmatter, dir_name)
@@ -168,21 +222,6 @@ module Brute
168
222
  value.to_s.split(/\s+/).reject(&:empty?)
169
223
  end
170
224
 
171
- # Directories to scan for skills, in priority order.
172
- def self.scan_dirs(cwd)
173
- dirs = []
174
-
175
- # Project-local
176
- project = File.join(cwd, ".brute", "skills")
177
- dirs << project if File.directory?(project)
178
-
179
- # Global
180
- global = File.join(Dir.home, ".config", "brute", "skills")
181
- dirs << global if File.directory?(global)
182
-
183
- dirs
184
- end
185
-
186
225
  # Split YAML frontmatter from markdown body.
187
226
  # Returns [hash, string] or [nil, nil].
188
227
  def self.parse_frontmatter(path, raw)
@@ -200,8 +239,8 @@ module Brute
200
239
  [nil, nil]
201
240
  end
202
241
 
203
- private_class_method :scan_dirs, :parse_frontmatter,
204
- :validate, :validate_name, :validate_description, :parse_allowed_tools
242
+ private_class_method :glob, :realpath, :validate, :validate_name,
243
+ :validate_description, :parse_allowed_tools, :parse_frontmatter
205
244
  end
206
245
  end
207
246
 
@@ -222,9 +261,19 @@ describe "brute/skill" do
222
261
  it "loads a valid skill" do
223
262
  Dir.mktmpdir do |root|
224
263
  path = make_skill_dir(root, "debugging", "name: debugging\ndescription: Debug things")
225
- info = Brute::Skill.load(path)
226
- info.name.should == "debugging"
227
- info.description.should == "Debug things"
264
+ skill = Brute::Skill.load(path)
265
+ skill.name.should == "debugging"
266
+ skill.description.should == "Debug things"
267
+ end
268
+ end
269
+
270
+ it "exposes file_path, base_dir, and a location alias" do
271
+ Dir.mktmpdir do |root|
272
+ path = make_skill_dir(root, "debugging", "name: debugging\ndescription: x")
273
+ skill = Brute::Skill.load(path)
274
+ skill.file_path.should == path
275
+ skill.base_dir.should == File.dirname(path)
276
+ skill.location.should == path
228
277
  end
229
278
  end
230
279
 
@@ -266,9 +315,9 @@ describe "brute/skill" do
266
315
  it "loads a skill with an unexpected frontmatter field, dropping the extra" do
267
316
  Dir.mktmpdir do |root|
268
317
  path = make_skill_dir(root, "extra", "name: extra\ndescription: x\ntags: [a, b]")
269
- info = Brute::Skill.load(path)
270
- info.name.should == "extra"
271
- info.respond_to?(:tags).should.be.false
318
+ skill = Brute::Skill.load(path)
319
+ skill.name.should == "extra"
320
+ skill.respond_to?(:tags).should.be.false
272
321
  end
273
322
  end
274
323
 
@@ -278,11 +327,25 @@ describe "brute/skill" do
278
327
  root, "full",
279
328
  "name: full\ndescription: x\nlicense: MIT\ncompatibility: claude\nallowed-tools: read shell\nmetadata:\n team: core",
280
329
  )
281
- info = Brute::Skill.load(path)
282
- info.license.should == "MIT"
283
- info.compatibility.should == "claude"
284
- info.allowed_tools.should == %w[read shell]
285
- info.metadata.should == { "team" => "core" }
330
+ skill = Brute::Skill.load(path)
331
+ skill.license.should == "MIT"
332
+ skill.compatibility.should == "claude"
333
+ skill.allowed_tools.should == %w[read shell]
334
+ skill.metadata.should == { "team" => "core" }
335
+ end
336
+ end
337
+
338
+ it "parses disable-model-invocation" do
339
+ Dir.mktmpdir do |root|
340
+ path = make_skill_dir(root, "hidden", "name: hidden\ndescription: x\ndisable-model-invocation: true")
341
+ Brute::Skill.load(path).disable_model_invocation?.should.be.true
342
+ end
343
+ end
344
+
345
+ it "defaults disable_model_invocation to false" do
346
+ Dir.mktmpdir do |root|
347
+ path = make_skill_dir(root, "shown", "name: shown\ndescription: x")
348
+ Brute::Skill.load(path).disable_model_invocation?.should.be.false
286
349
  end
287
350
  end
288
351
 
@@ -292,4 +355,72 @@ describe "brute/skill" do
292
355
  Brute::Skill.load(path).should.be.nil
293
356
  end
294
357
  end
358
+
359
+ it "tags skills with their source" do
360
+ Dir.mktmpdir do |root|
361
+ make_skill_dir(root, "debugging", "name: debugging\ndescription: x")
362
+ Brute::Skill.all(cwd: root).first.source.should == :project
363
+ end
364
+ end
365
+
366
+ def with_home(dir)
367
+ old = ENV["HOME"]
368
+ ENV["HOME"] = dir
369
+ yield
370
+ ensure
371
+ ENV["HOME"] = old
372
+ end
373
+
374
+ it "project-local skills override same-named global ones" do
375
+ Dir.mktmpdir do |project|
376
+ Dir.mktmpdir do |home|
377
+ make_skill_dir(project, "shared", "name: shared\ndescription: project variant")
378
+ global_dir = File.join(home, ".config", "brute", "skills", "shared")
379
+ FileUtils.mkdir_p(global_dir)
380
+ File.write(File.join(global_dir, "SKILL.md"), "---\nname: shared\ndescription: global variant\n---\n\nBody\n")
381
+
382
+ with_home(home) do
383
+ skills = Brute::Skill.all(cwd: project)
384
+ skills.size.should == 1
385
+ skills.first.description.should == "project variant"
386
+ skills.first.source.should == :project
387
+ end
388
+ end
389
+ end
390
+ end
391
+
392
+ it "loads skills from explicit paths" do
393
+ Dir.mktmpdir do |root|
394
+ dir = File.join(root, "elsewhere", "custom")
395
+ FileUtils.mkdir_p(dir)
396
+ File.write(File.join(dir, "SKILL.md"), "---\nname: custom\ndescription: explicit\n---\n\nBody\n")
397
+
398
+ skills = Brute::Skill.all(cwd: root, paths: [File.join(root, "elsewhere")])
399
+ skills.map(&:name).should == ["custom"]
400
+ skills.first.source.should == :path
401
+ end
402
+ end
403
+
404
+ it "expands ~ in explicit paths" do
405
+ Dir.mktmpdir do |home|
406
+ dir = File.join(home, "skills", "homey")
407
+ FileUtils.mkdir_p(dir)
408
+ File.write(File.join(dir, "SKILL.md"), "---\nname: homey\ndescription: x\n---\n\nBody\n")
409
+
410
+ with_home(home) do
411
+ skills = Brute::Skill.all(cwd: home, paths: ["~/skills"])
412
+ skills.map(&:name).should.include("homey")
413
+ end
414
+ end
415
+ end
416
+
417
+ it "loads the same file only once when reached via a symlink" do
418
+ Dir.mktmpdir do |root|
419
+ make_skill_dir(root, "debugging", "name: debugging\ndescription: x")
420
+ link = File.join(root, ".brute", "skills", "linked")
421
+ File.symlink(File.join(root, ".brute", "skills", "debugging"), link)
422
+
423
+ Brute::Skill.all(cwd: root).size.should == 1
424
+ end
425
+ end
295
426
  end
@@ -45,10 +45,15 @@ module Brute
45
45
  events: events,
46
46
  metadata: {},
47
47
  current_iteration: 1,
48
+ hooks: hooks,
48
49
  }
49
- env.tap do
50
+ hooks.emit(:turn_start, env)
51
+ begin
50
52
  build.call(env)
53
+ ensure
54
+ hooks.emit(:turn_end, env)
51
55
  end
56
+ env
52
57
  end
53
58
 
54
59
  private
@@ -119,6 +124,43 @@ describe "brute/turn/agent_pipeline" do
119
124
  agent.start("hi")[:messages].last.content.should == "from ru"
120
125
  end
121
126
 
127
+ it "Brute.load_agent loads an agent from a .ru file and starts it" do
128
+ require "tmpdir"
129
+ Dir.mktmpdir do |dir|
130
+ path = File.join(dir, "agent.ru")
131
+ File.write(path, 'run ->(env) { env[:messages].assistant("from file") }')
132
+
133
+ agent = Brute.load_agent(path)
134
+ agent.should.be.kind_of?(Brute::Turn::AgentPipeline)
135
+ agent.start("hi")[:messages].last.content.should == "from file"
136
+ end
137
+ end
138
+
139
+ it "Brute.load_agent raises for a missing file" do
140
+ lambda { Brute.load_agent("definitely/not/here.ru") }.should.raise(ArgumentError)
141
+ end
142
+
143
+ it ".on chains off the builder and fires turn hooks around the turn" do
144
+ fired = []
145
+ agent = Brute.agent
146
+ .run(->(env) { env[:messages].assistant("done") })
147
+ .on(:turn_start) { |env| fired << [:start, env[:messages].last.content] }
148
+ .on(:turn_end) { |env| fired << [:end, env[:messages].last.content] }
149
+
150
+ env = agent.start("go")
151
+ env[:messages].last.content.should == "done"
152
+ fired.should == [[:start, "go"], [:end, "done"]]
153
+ end
154
+
155
+ it "fires turn_end on error (ensure)" do
156
+ fired = []
157
+ agent = Brute.agent
158
+ .run(->(_env) { raise "boom" })
159
+ .on(:turn_end) { |_env| fired << :end }
160
+
161
+ lambda { agent.start("go") }.should.raise(RuntimeError)
162
+ fired.should == [:end]
163
+ end
122
164
  it "use in a ru string wraps the terminal" do
123
165
  script = <<~RUBY
124
166
  use AgentStubMW
@@ -19,6 +19,16 @@ module Brute
19
19
  #
20
20
  def use(...) = tap { super }
21
21
  def run(...) = tap { super }
22
+
23
+ # Subscribe a lifecycle hook (see Brute::Hooks):
24
+ #
25
+ # Brute.agent
26
+ # .use(MaxProfit)
27
+ # .run(->(env) { ... })
28
+ # .on(:before_llm) { |env| ... }
29
+ # .on(:approve_tool) { |call| call[:name] != "exec" }
30
+ #
31
+ def on(...) = tap { hooks.on(...) }
22
32
  end
23
33
 
24
34
  include Chainable
@@ -40,6 +50,11 @@ module Brute
40
50
  # when `run` was never called.
41
51
  alias_method :build, :to_app
42
52
 
53
+ # The lifecycle-hook registry for this pipeline (see Brute::Hooks).
54
+ def hooks
55
+ @hooks ||= Brute::Hooks.new
56
+ end
57
+
43
58
  # Default null sink for env[:events] — swallows anything pushed to it.
44
59
  class NullSink
45
60
  def <<(_event); self; 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 = "3.2.2"
4
+ VERSION = "4.1.0"
5
5
  end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Brute
4
+ VERSION = "<%= version %>"
5
+ end
data/lib/brute.rb CHANGED
@@ -57,6 +57,22 @@ module Brute
57
57
  Brute::Turn::AgentPipeline.new(&block)
58
58
  end
59
59
 
60
+ # Load an agent from a brute.ru file — the Brute analogue of `rackup`.
61
+ # The file is a rackup-style script using the same `use` / `run` / `map`
62
+ # DSL as `Brute.agent`, and what comes back is the AgentPipeline itself,
63
+ # so it can be started, further `.use`d, or served through
64
+ # Brute::Rack::Adapter:
65
+ #
66
+ # Brute.load_agent.start("what changed?") # ./agent.ru
67
+ # Brute.load_agent("examples/agents/brute.ru").start("hi")
68
+ #
69
+ def self.load_agent(path = "agent.ru")
70
+ path = File.expand_path(path)
71
+ raise ArgumentError, "no such agent file: #{path}" unless File.file?(path)
72
+
73
+ Brute::Turn::AgentPipeline.parse_file(path)
74
+ end
75
+
60
76
  # Adapt any Brute tools (hashes, Brute::Tool, Brute::Turn::ToolPipeline,
61
77
  # SubAgent …) into a { name_sym => Brute::Tools::Adapter } hash. Each
62
78
  # adapter exposes #to_h — a neutral JSON-Schema-ish definition the inline
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: brute
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.2.2
4
+ version: 4.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brute Contributors
@@ -37,6 +37,20 @@ dependencies:
37
37
  - - ">="
38
38
  - !ruby/object:Gem::Version
39
39
  version: '1.5'
40
+ - !ruby/object:Gem::Dependency
41
+ name: file-tail
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '1.2'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '1.2'
40
54
  - !ruby/object:Gem::Dependency
41
55
  name: activesupport
42
56
  requirement: !ruby/object:Gem::Requirement
@@ -163,6 +177,20 @@ dependencies:
163
177
  - - "~>"
164
178
  - !ruby/object:Gem::Version
165
179
  version: '2.1'
180
+ - !ruby/object:Gem::Dependency
181
+ name: gem_kit-release
182
+ requirement: !ruby/object:Gem::Requirement
183
+ requirements:
184
+ - - "~>"
185
+ - !ruby/object:Gem::Version
186
+ version: '0.1'
187
+ type: :development
188
+ prerelease: false
189
+ version_requirements: !ruby/object:Gem::Requirement
190
+ requirements:
191
+ - - "~>"
192
+ - !ruby/object:Gem::Version
193
+ version: '0.1'
166
194
  description: Production-grade coding agent with tool execution, middleware pipeline,
167
195
  context compaction, session persistence, and multi-provider LLM support.
168
196
  executables: []
@@ -170,9 +198,14 @@ extensions: []
170
198
  extra_rdoc_files: []
171
199
  files:
172
200
  - lib/brute.rb
201
+ - lib/brute/changelog.rb
202
+ - lib/brute/completion/open_router.rb
203
+ - lib/brute/contrib/log_file.rb
204
+ - lib/brute/deprecate.rb
173
205
  - lib/brute/events/handler.rb
174
206
  - lib/brute/events/prefixed_terminal_output.rb
175
207
  - lib/brute/events/terminal_output_handler.rb
208
+ - lib/brute/hooks.rb
176
209
  - lib/brute/message_transport.rb
177
210
  - lib/brute/message_transport/anthropic.rb
178
211
  - lib/brute/message_transport/llm.rb
@@ -190,6 +223,7 @@ files:
190
223
  - lib/brute/middleware/010_max_iterations.rb
191
224
  - lib/brute/middleware/015_otel_token_usage.rb
192
225
  - lib/brute/middleware/020_system_prompt.rb
226
+ - lib/brute/middleware/025_skills.rb
193
227
  - lib/brute/middleware/040_compaction_check.rb
194
228
  - lib/brute/middleware/060_questions.rb
195
229
  - lib/brute/middleware/070_tool_pipeline.rb
@@ -198,6 +232,7 @@ files:
198
232
  - lib/brute/middleware/event_handler.rb
199
233
  - lib/brute/middleware/open_router.rb
200
234
  - lib/brute/middleware/user_queue.rb
235
+ - lib/brute/prompt_template.rb
201
236
  - lib/brute/prompts.rb
202
237
  - lib/brute/prompts/autonomy.rb
203
238
  - lib/brute/prompts/base.rb
@@ -231,6 +266,7 @@ files:
231
266
  - lib/brute/prompts/text/identity/default.txt
232
267
  - lib/brute/prompts/text/identity/google.txt
233
268
  - lib/brute/prompts/text/identity/openai.txt
269
+ - lib/brute/prompts/text/skills/default.erb
234
270
  - lib/brute/prompts/text/tone_and_style/anthropic.txt
235
271
  - lib/brute/prompts/text/tone_and_style/default.txt
236
272
  - lib/brute/prompts/text/tone_and_style/google.txt
@@ -268,6 +304,7 @@ files:
268
304
  - lib/brute/turn/tool_pipeline.rb
269
305
  - lib/brute/utils/diff.rb
270
306
  - lib/brute/version.rb
307
+ - lib/brute/version.rb.erb
271
308
  - lib/brute_cli/providers/shell.rb
272
309
  - lib/brute_cli/providers/shell_response.rb
273
310
  homepage: https://github.com/general-intelligence-systems/brute