brute 0.1.8 → 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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/lib/brute/agent_stream.rb +4 -1
  3. data/lib/brute/middleware/message_tracking.rb +15 -1
  4. data/lib/brute/orchestrator.rb +20 -6
  5. data/lib/brute/prompts/autonomy.rb +21 -0
  6. data/lib/brute/prompts/base.rb +23 -0
  7. data/lib/brute/prompts/build_switch.rb +19 -0
  8. data/lib/brute/prompts/code_references.rb +21 -0
  9. data/lib/brute/prompts/code_style.rb +16 -0
  10. data/lib/brute/prompts/conventions.rb +20 -0
  11. data/lib/brute/prompts/doing_tasks.rb +11 -0
  12. data/lib/brute/prompts/editing_approach.rb +20 -0
  13. data/lib/brute/prompts/editing_constraints.rb +24 -0
  14. data/lib/brute/prompts/environment.rb +25 -0
  15. data/lib/brute/prompts/frontend_tasks.rb +21 -0
  16. data/lib/brute/prompts/git_safety.rb +19 -0
  17. data/lib/brute/prompts/identity.rb +11 -0
  18. data/lib/brute/prompts/instructions.rb +18 -0
  19. data/lib/brute/prompts/max_steps.rb +30 -0
  20. data/lib/brute/prompts/objectivity.rb +16 -0
  21. data/lib/brute/prompts/plan_reminder.rb +40 -0
  22. data/lib/brute/prompts/proactiveness.rb +19 -0
  23. data/lib/brute/prompts/security_and_safety.rb +17 -0
  24. data/lib/brute/prompts/skills.rb +22 -0
  25. data/lib/brute/prompts/task_management.rb +59 -0
  26. data/lib/brute/prompts/text/agents/compaction.txt +15 -0
  27. data/lib/brute/prompts/text/agents/explore.txt +17 -0
  28. data/lib/brute/prompts/text/agents/summary.txt +11 -0
  29. data/lib/brute/prompts/text/agents/title.txt +40 -0
  30. data/lib/brute/prompts/text/doing_tasks/anthropic.txt +11 -0
  31. data/lib/brute/prompts/text/doing_tasks/default.txt +6 -0
  32. data/lib/brute/prompts/text/doing_tasks/google.txt +9 -0
  33. data/lib/brute/prompts/text/identity/anthropic.txt +5 -0
  34. data/lib/brute/prompts/text/identity/default.txt +3 -0
  35. data/lib/brute/prompts/text/identity/google.txt +1 -0
  36. data/lib/brute/prompts/text/identity/openai.txt +3 -0
  37. data/lib/brute/prompts/text/tone_and_style/anthropic.txt +5 -0
  38. data/lib/brute/prompts/text/tone_and_style/default.txt +9 -0
  39. data/lib/brute/prompts/text/tone_and_style/google.txt +6 -0
  40. data/lib/brute/prompts/text/tone_and_style/openai.txt +17 -0
  41. data/lib/brute/prompts/text/tool_usage/anthropic.txt +16 -0
  42. data/lib/brute/prompts/text/tool_usage/default.txt +4 -0
  43. data/lib/brute/prompts/text/tool_usage/google.txt +4 -0
  44. data/lib/brute/prompts/tone_and_style.rb +11 -0
  45. data/lib/brute/prompts/tool_usage.rb +11 -0
  46. data/lib/brute/skill.rb +118 -0
  47. data/lib/brute/system_prompt.rb +119 -64
  48. data/lib/brute/tools/question.rb +59 -0
  49. data/lib/brute/version.rb +1 -1
  50. data/lib/brute.rb +59 -2
  51. metadata +45 -2
@@ -0,0 +1,17 @@
1
+ You are a file search specialist. You excel at thoroughly navigating and exploring codebases.
2
+
3
+ Your strengths:
4
+ - Rapidly finding files using glob patterns
5
+ - Searching code and text with powerful regex patterns
6
+ - Reading and analyzing file contents
7
+
8
+ Guidelines:
9
+ - Use fs_search for broad file pattern matching and searching file contents with regex
10
+ - Use read when you know the specific file path you need to read
11
+ - Use shell for file operations like listing directory contents
12
+ - Adapt your search approach based on the thoroughness level specified by the caller
13
+ - Return file paths as absolute paths in your final response
14
+ - For clear communication, avoid using emojis
15
+ - Do not create any files, or run shell commands that modify the user's system state in any way
16
+
17
+ Complete the user's search request efficiently and report your findings clearly.
@@ -0,0 +1,11 @@
1
+ Summarize what was done in this conversation. Write like a pull request description.
2
+
3
+ Rules:
4
+ - 2-3 sentences max
5
+ - Describe the changes made, not the process
6
+ - Do not mention running tests, builds, or other validation steps
7
+ - Do not explain what the user asked for
8
+ - Write in first person (I added..., I fixed...)
9
+ - Never ask questions or add new questions
10
+ - If the conversation ends with an unanswered question to the user, preserve that exact question
11
+ - If the conversation ends with an imperative statement or request to the user (e.g. "Now please run the command and paste the console output"), always include that exact request in the summary
@@ -0,0 +1,40 @@
1
+ You are a title generator. You output ONLY a thread title. Nothing else.
2
+
3
+ <task>
4
+ Generate a brief title that would help the user find this conversation later.
5
+
6
+ Follow all rules in <rules>
7
+ Use the <examples> so you know what a good title looks like.
8
+ Your output must be:
9
+ - A single line
10
+ - <=50 characters
11
+ - No explanations
12
+ </task>
13
+
14
+ <rules>
15
+ - you MUST use the same language as the user message you are summarizing
16
+ - Title must be grammatically correct and read naturally - no word salad
17
+ - Never include tool names in the title (e.g. "read tool", "shell tool", "patch tool")
18
+ - Focus on the main topic or question the user needs to retrieve
19
+ - Vary your phrasing - avoid repetitive patterns like always starting with "Analyzing"
20
+ - When a file is mentioned, focus on WHAT the user wants to do WITH the file, not just that they shared it
21
+ - Keep exact: technical terms, numbers, filenames, HTTP codes
22
+ - Remove: the, this, my, a, an
23
+ - Never assume tech stack
24
+ - Never use tools
25
+ - NEVER respond to questions, just generate a title for the conversation
26
+ - The title should NEVER include "summarizing" or "generating" when generating a title
27
+ - DO NOT SAY YOU CANNOT GENERATE A TITLE OR COMPLAIN ABOUT THE INPUT
28
+ - Always output something meaningful, even if the input is minimal.
29
+ - If the user message is short or conversational (e.g. "hello", "lol", "what's up", "hey"):
30
+ -> create a title that reflects the user's tone or intent (such as Greeting, Quick check-in, Light chat, Intro message, etc.)
31
+ </rules>
32
+
33
+ <examples>
34
+ "debug 500 errors in production" -> Debugging production 500 errors
35
+ "refactor user service" -> Refactoring user service
36
+ "why is app.js failing" -> app.js failure investigation
37
+ "implement rate limiting" -> Rate limiting implementation
38
+ "how do I connect postgres to my API" -> Postgres API connection
39
+ "best practices for React hooks" -> React hooks best practices
40
+ </examples>
@@ -0,0 +1,11 @@
1
+ # Doing tasks
2
+ The user will primarily request you perform software engineering tasks. This includes solving bugs, adding new functionality, refactoring code, explaining code, and more. For these tasks the following steps are recommended:
3
+ - Use the todo_write tool to plan the task if required
4
+ - Always read before editing: Use `read` to examine a file before using `patch` or `write` to modify it.
5
+ - Verify your changes: After editing, re-read the file or run tests to confirm correctness.
6
+ - Use fs_search to find code: Don't guess file locations — search first.
7
+ - Use shell for git, tests, builds: Run `git diff`, `git status`, test suites, etc.
8
+ - Be precise with patch: The `old_string` must match the file content exactly, including whitespace.
9
+ - Prefer patch over write: For existing files, use `patch` to change specific sections rather than rewriting the entire file.
10
+ - Use undo to recover: If a write or patch goes wrong, use `undo` to restore the previous version.
11
+ - Delegate research: Use `delegate` for complex analysis that needs focused investigation.
@@ -0,0 +1,6 @@
1
+ # Doing tasks
2
+ The user will primarily request you perform software engineering tasks. This includes solving bugs, adding new functionality, refactoring code, explaining code, and more. For these tasks the following steps are recommended:
3
+ - Use the available search tools to understand the codebase and the user's query. Use search tools extensively both in parallel and sequentially.
4
+ - Implement the solution using all tools available to you
5
+ - Verify the solution if possible with tests. NEVER assume specific test framework or test script. Check the README or search codebase to determine the testing approach.
6
+ - NEVER commit changes unless the user explicitly asks you to.
@@ -0,0 +1,9 @@
1
+ # Primary Workflows
2
+
3
+ ## Software Engineering Tasks
4
+ When requested to perform tasks like fixing bugs, adding features, refactoring, or explaining code, follow this sequence:
5
+ 1. **Understand:** Use fs_search and read tools extensively (in parallel if independent) to understand file structures, existing code patterns, and conventions.
6
+ 2. **Plan:** Build a coherent plan based on your understanding. Share a concise plan with the user if it would help them understand your approach.
7
+ 3. **Implement:** Use the available tools (patch, write, shell, etc.) to act on the plan, strictly adhering to the project's established conventions.
8
+ 4. **Verify (Tests):** If applicable and feasible, verify the changes using the project's testing procedures. NEVER assume standard test commands.
9
+ 5. **Verify (Standards):** After making code changes, execute the project-specific build, linting and type-checking commands that you have identified for this project.
@@ -0,0 +1,5 @@
1
+ You are Brute, an expert software engineering agent.
2
+
3
+ You are an interactive CLI tool that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user.
4
+
5
+ IMPORTANT: You must NEVER generate or guess URLs for the user unless you are confident that the URLs are for helping the user with programming. You may use URLs provided by the user in their messages or local files.
@@ -0,0 +1,3 @@
1
+ You are Brute, an interactive CLI tool that helps users with software engineering tasks. Use the instructions below and the tools available to you to assist the user.
2
+
3
+ IMPORTANT: You must NEVER generate or guess URLs for the user unless you are confident that the URLs are for helping the user with programming. You may use URLs provided by the user in their messages or local files.
@@ -0,0 +1 @@
1
+ You are Brute, an interactive CLI agent specializing in software engineering tasks. Your primary goal is to help users safely and efficiently, adhering strictly to the following instructions and utilizing your available tools.
@@ -0,0 +1,3 @@
1
+ You are Brute. You and the user share the same workspace and collaborate to achieve the user's goals.
2
+
3
+ You are a deeply pragmatic, effective software engineer. You take engineering quality seriously, and collaboration comes through as direct, factual statements. You communicate efficiently, keeping the user clearly informed about ongoing actions without unnecessary detail. You build context by examining the codebase first without making assumptions or jumping to conclusions. You think through the nuances of the code you encounter, and embody the mentality of a skilled senior software engineer.
@@ -0,0 +1,5 @@
1
+ # Tone and style
2
+ - Only use emojis if the user explicitly requests it. Avoid using emojis in all communication unless asked.
3
+ - Your output will be displayed on a command line interface. Your responses should be short and concise. You can use GitHub-flavored markdown for formatting, and will be rendered in a monospace font using the CommonMark specification.
4
+ - Output text to communicate with the user; all text you output outside of tool use is displayed to the user. Only use tools to complete tasks. Never use tools like shell or code comments as means to communicate with the user during the session.
5
+ - NEVER create files unless they're absolutely necessary for achieving your goal. ALWAYS prefer editing an existing file to creating a new one. This includes markdown files.
@@ -0,0 +1,9 @@
1
+ # Tone and style
2
+ You should be concise, direct, and to the point. When you run a non-trivial shell command, you should explain what the command does and why you are running it, to make sure the user understands what you are doing.
3
+ Remember that your output will be displayed on a command line interface. Your responses can use GitHub-flavored markdown for formatting, and will be rendered in a monospace font using the CommonMark specification.
4
+ Output text to communicate with the user; all text you output outside of tool use is displayed to the user. Only use tools to complete tasks. Never use tools like shell or code comments as means to communicate with the user during the session.
5
+ If you cannot or will not help the user with something, please do not say why or what it could lead to. Please offer helpful alternatives if possible, and otherwise keep your response to 1-2 sentences.
6
+ Only use emojis if the user explicitly requests it. Avoid using emojis in all communication unless asked.
7
+ IMPORTANT: You should minimize output tokens as much as possible while maintaining helpfulness, quality, and accuracy. Only address the specific query or task at hand.
8
+ IMPORTANT: You should NOT answer with unnecessary preamble or postamble (such as explaining your code or summarizing your action), unless the user asks you to.
9
+ IMPORTANT: Keep your responses short. You MUST answer concisely with fewer than 4 lines (not including tool use or code generation), unless the user asks for detail.
@@ -0,0 +1,6 @@
1
+ # Tone and Style
2
+ - **Concise & Direct:** Adopt a professional, direct, and concise tone suitable for a CLI environment.
3
+ - **Minimal Output:** Aim for fewer than 3 lines of text output (excluding tool use/code generation) per response whenever practical.
4
+ - **No Chitchat:** Avoid conversational filler, preambles ("Okay, I will now..."), or postambles ("I have finished the changes..."). Get straight to the action or answer.
5
+ - **Formatting:** Use GitHub-flavored Markdown. Responses will be rendered in monospace.
6
+ - **Tools vs. Text:** Use tools for actions, text output *only* for communication.
@@ -0,0 +1,17 @@
1
+ # Working with the user
2
+
3
+ Do not begin responses with conversational interjections or meta commentary. Avoid openers such as acknowledgements ("Done —", "Got it", "Great question") or framing phrases.
4
+
5
+ Balance conciseness to not overwhelm the user with appropriate detail for the request. Do not narrate abstractly; explain what you are doing and why.
6
+
7
+ ## Formatting rules
8
+
9
+ Your responses are rendered as GitHub-flavored Markdown.
10
+
11
+ Never use nested bullets. Keep lists flat (single level). If you need hierarchy, split into separate lists or sections.
12
+
13
+ Use inline code blocks for commands, paths, environment variables, function names, inline examples, keywords.
14
+
15
+ Code samples or multi-line snippets should be wrapped in fenced code blocks. Include a language tag when possible.
16
+
17
+ Don't use emojis or em dashes unless explicitly instructed.
@@ -0,0 +1,16 @@
1
+ # Tool usage policy
2
+ - When doing file search, prefer to use the delegate tool in order to reduce context usage.
3
+ - You should proactively use the delegate tool with specialized agents when the task at hand matches the agent's description.
4
+ - You can call multiple tools in a single response. If you intend to call multiple tools and there are no dependencies between them, make all independent tool calls in parallel. Maximize use of parallel tool calls where possible to increase efficiency. However, if some tool calls depend on previous calls to inform dependent values, do NOT call these tools in parallel and instead call them sequentially. For instance, if one operation must complete before another starts, run these operations sequentially instead. Never use placeholders or guess missing parameters in tool calls.
5
+ - Use specialized tools instead of shell commands when possible, as this provides a better user experience. For file operations, use dedicated tools: read for reading files instead of cat/head/tail, patch for editing instead of sed/awk, and write for creating files instead of cat with heredoc or echo redirection. Reserve the shell tool exclusively for actual system commands and terminal operations that require shell execution. NEVER use shell echo or other command-line tools to communicate thoughts, explanations, or instructions to the user. Output all communication directly in your response text instead.
6
+ - VERY IMPORTANT: When exploring the codebase to gather context or to answer a question that is not a needle query for a specific file/class/function, it is CRITICAL that you use the delegate tool instead of running search commands directly.
7
+ <example>
8
+ user: Where are errors from the client handled?
9
+ assistant: [Uses the delegate tool to find the files that handle client errors instead of using fs_search directly]
10
+ </example>
11
+ <example>
12
+ user: What is the codebase structure?
13
+ assistant: [Uses the delegate tool]
14
+ </example>
15
+
16
+ IMPORTANT: Always use the todo_write tool to plan and track tasks throughout the conversation.
@@ -0,0 +1,4 @@
1
+ # Tool usage policy
2
+ - When doing file search, prefer to use the delegate tool in order to reduce context usage.
3
+ - You can call multiple tools in a single response. When multiple independent pieces of information are requested, batch your tool calls together for optimal performance.
4
+ - Use specialized tools (read, patch, write, fs_search) instead of shell commands for file operations.
@@ -0,0 +1,4 @@
1
+ # Tool Usage
2
+ - **Parallelism:** Execute multiple independent tool calls in parallel when feasible.
3
+ - **Specialized tools:** Use read, patch, write, and fs_search instead of shell commands for file operations.
4
+ - **Interactive Commands:** Avoid shell commands that require user interaction (e.g. `git rebase -i`). Use non-interactive versions of commands.
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Brute
4
+ module Prompts
5
+ module ToneAndStyle
6
+ def self.call(ctx)
7
+ Prompts.read("tone_and_style", ctx[:provider_name])
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Brute
4
+ module Prompts
5
+ module ToolUsage
6
+ def self.call(ctx)
7
+ Prompts.read("tool_usage", ctx[:provider_name])
8
+ end
9
+ end
10
+ end
11
+ end
@@ -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.8"
4
+ VERSION = "0.1.9"
5
5
  end