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.
- checksums.yaml +4 -4
- data/lib/brute/agent_stream.rb +4 -1
- data/lib/brute/middleware/message_tracking.rb +15 -1
- data/lib/brute/orchestrator.rb +20 -6
- data/lib/brute/prompts/autonomy.rb +21 -0
- data/lib/brute/prompts/base.rb +23 -0
- data/lib/brute/prompts/build_switch.rb +19 -0
- data/lib/brute/prompts/code_references.rb +21 -0
- data/lib/brute/prompts/code_style.rb +16 -0
- data/lib/brute/prompts/conventions.rb +20 -0
- data/lib/brute/prompts/doing_tasks.rb +11 -0
- data/lib/brute/prompts/editing_approach.rb +20 -0
- data/lib/brute/prompts/editing_constraints.rb +24 -0
- data/lib/brute/prompts/environment.rb +25 -0
- data/lib/brute/prompts/frontend_tasks.rb +21 -0
- data/lib/brute/prompts/git_safety.rb +19 -0
- data/lib/brute/prompts/identity.rb +11 -0
- data/lib/brute/prompts/instructions.rb +18 -0
- data/lib/brute/prompts/max_steps.rb +30 -0
- data/lib/brute/prompts/objectivity.rb +16 -0
- data/lib/brute/prompts/plan_reminder.rb +40 -0
- data/lib/brute/prompts/proactiveness.rb +19 -0
- data/lib/brute/prompts/security_and_safety.rb +17 -0
- data/lib/brute/prompts/skills.rb +22 -0
- data/lib/brute/prompts/task_management.rb +59 -0
- data/lib/brute/prompts/text/agents/compaction.txt +15 -0
- data/lib/brute/prompts/text/agents/explore.txt +17 -0
- data/lib/brute/prompts/text/agents/summary.txt +11 -0
- data/lib/brute/prompts/text/agents/title.txt +40 -0
- data/lib/brute/prompts/text/doing_tasks/anthropic.txt +11 -0
- data/lib/brute/prompts/text/doing_tasks/default.txt +6 -0
- data/lib/brute/prompts/text/doing_tasks/google.txt +9 -0
- data/lib/brute/prompts/text/identity/anthropic.txt +5 -0
- data/lib/brute/prompts/text/identity/default.txt +3 -0
- data/lib/brute/prompts/text/identity/google.txt +1 -0
- data/lib/brute/prompts/text/identity/openai.txt +3 -0
- data/lib/brute/prompts/text/tone_and_style/anthropic.txt +5 -0
- data/lib/brute/prompts/text/tone_and_style/default.txt +9 -0
- data/lib/brute/prompts/text/tone_and_style/google.txt +6 -0
- data/lib/brute/prompts/text/tone_and_style/openai.txt +17 -0
- data/lib/brute/prompts/text/tool_usage/anthropic.txt +16 -0
- data/lib/brute/prompts/text/tool_usage/default.txt +4 -0
- data/lib/brute/prompts/text/tool_usage/google.txt +4 -0
- data/lib/brute/prompts/tone_and_style.rb +11 -0
- data/lib/brute/prompts/tool_usage.rb +11 -0
- data/lib/brute/skill.rb +118 -0
- data/lib/brute/system_prompt.rb +119 -64
- data/lib/brute/tools/question.rb +59 -0
- data/lib/brute/version.rb +1 -1
- data/lib/brute.rb +59 -2
- 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.
|
data/lib/brute/skill.rb
ADDED
|
@@ -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
|
data/lib/brute/system_prompt.rb
CHANGED
|
@@ -1,88 +1,143 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Brute
|
|
4
|
-
#
|
|
5
|
-
#
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
35
|
+
# Conditional: agent-specific reminders
|
|
36
|
+
if ctx[:agent] == "plan"
|
|
37
|
+
prompt << Prompts::PlanReminder.call(ctx)
|
|
38
|
+
end
|
|
27
39
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
40
|
+
if ctx[:agent_switched] == "build"
|
|
41
|
+
prompt << Prompts::BuildSwitch.call(ctx)
|
|
42
|
+
end
|
|
31
43
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
44
|
+
if ctx[:max_steps_reached]
|
|
45
|
+
prompt << Prompts::MaxSteps.call(ctx)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
36
48
|
end
|
|
37
49
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
44
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
-
|
|
55
|
-
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
-
|
|
85
|
-
|
|
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