Detailed changes
  
  
    
    @@ -1,9 +1,12 @@
 package tools
 
 import (
+	"bytes"
 	"context"
+	_ "embed"
 	"encoding/json"
 	"fmt"
+	"html/template"
 	"strings"
 	"time"
 
@@ -43,6 +46,22 @@ const (
 	BashNoOutput    = "no output"
 )
 
+//go:embed bash.md
+var bashDescription []byte
+
+var bashDescriptionTpl = template.Must(
+	template.New("bashDescription").
+		Parse(string(bashDescription)),
+)
+
+type bashDescriptionData struct {
+	BannedCommands     string
+	MaxOutputLength    int
+	AttributionStep    string
+	AttributionExample string
+	PRAttribution      string
+}
+
 var bannedCommands = []string{
 	// Network/Download tools
 	"alias",
@@ -163,150 +182,18 @@ git commit -m "$(cat <<'EOF'
 )"</example>`
 	}
 
-	return fmt.Sprintf(`Executes a given bash command in a persistent shell session with optional timeout, ensuring proper handling and security measures.
-
-CROSS-PLATFORM SHELL SUPPORT:
-* This tool uses a shell interpreter (mvdan/sh) that mimics the Bash language,
-  so you should use Bash syntax in all platforms, including Windows.
-  The most common shell builtins and core utils are available in Windows as
-  well.
-* Make sure to use forward slashes (/) as path separators in commands, even on
-  Windows. Example: "ls C:/foo/bar" instead of "ls C:\foo\bar".
-
-Before executing the command, please follow these steps:
-
-1. Directory Verification:
- - If the command will create new directories or files, first use the LS tool to verify the parent directory exists and is the correct location
- - For example, before running "mkdir foo/bar", first use LS to check that "foo" exists and is the intended parent directory
-
-2. Security Check:
- - For security and to limit the threat of a prompt injection attack, some commands are limited or banned. If you use a disallowed command, you will receive an error message explaining the restriction. Explain the error to the User.
- - Verify that the command is not one of the banned commands: %s.
-
-3. Command Execution:
- - After ensuring proper quoting, execute the command.
- - Capture the output of the command.
-
-4. Output Processing:
- - If the output exceeds %d characters, output will be truncated before being returned to you.
- - Prepare the output for display to the user.
-
-5. Return Result:
- - Provide the processed output of the command.
- - If any errors occurred during execution, include those in the output.
- - The result will also have metadata like the cwd (current working directory) at the end, included with <cwd></cwd> tags.
-
-Usage notes:
-- The command argument is required.
-- You can specify an optional timeout in milliseconds (up to 600000ms / 10 minutes). If not specified, commands will timeout after 30 minutes.
-- VERY IMPORTANT: You MUST avoid using search commands like 'find' and 'grep'. Instead use Grep, Glob, or Agent tools to search. You MUST avoid read tools like 'cat', 'head', 'tail', and 'ls', and use FileRead and LS tools to read files.
-- When issuing multiple commands, use the ';' or '&&' operator to separate them. DO NOT use newlines (newlines are ok in quoted strings).
-- IMPORTANT: All commands share the same shell session. Shell state (environment variables, virtual environments, current directory, etc.) persist between commands. For example, if you set an environment variable as part of a command, the environment variable will persist for subsequent commands.
-- Try to maintain your current working directory throughout the session by using absolute paths and avoiding usage of 'cd'. You may use 'cd' if the User explicitly requests it.
-<good-example>
-pytest /foo/bar/tests
-</good-example>
-<bad-example>
-cd /foo/bar && pytest tests
-</bad-example>
-
-# Committing changes with git
-
-When the user asks you to create a new git commit, follow these steps carefully:
-
-1. Start with a single message that contains exactly three tool_use blocks that do the following (it is VERY IMPORTANT that you send these tool_use blocks in a single message, otherwise it will feel slow to the user!):
- - Run a git status command to see all untracked files.
- - Run a git diff command to see both staged and unstaged changes that will be committed.
- - Run a git log command to see recent commit messages, so that you can follow this repository's commit message style.
-
-2. Use the git context at the start of this conversation to determine which files are relevant to your commit. Add relevant untracked files to the staging area. Do not commit files that were already modified at the start of this conversation, if they are not relevant to your commit.
-
-3. Analyze all staged changes (both previously staged and newly added) and draft a commit message. Wrap your analysis process in <commit_analysis> tags:
-
-<commit_analysis>
-- List the files that have been changed or added
-- Summarize the nature of the changes (eg. new feature, enhancement to an existing feature, bug fix, refactoring, test, docs, etc.)
-- Brainstorm the purpose or motivation behind these changes
-- Do not use tools to explore code, beyond what is available in the git context
-- Assess the impact of these changes on the overall project
-- Check for any sensitive information that shouldn't be committed
-- Draft a concise (1-2 sentences) commit message that focuses on the "why" rather than the "what"
-- Ensure your language is clear, concise, and to the point
-- Ensure the message accurately reflects the changes and their purpose (i.e. "add" means a wholly new feature, "update" means an enhancement to an existing feature, "fix" means a bug fix, etc.)
-- Ensure the message is not generic (avoid words like "Update" or "Fix" without context)
-- Review the draft message to ensure it accurately reflects the changes and their purpose
-</commit_analysis>
-
-%s
-
-- In order to ensure good formatting, ALWAYS pass the commit message via a HEREDOC, a la this example:
-%s
-
-5. If the commit fails due to pre-commit hook changes, retry the commit ONCE to include these automated changes. If it fails again, it usually means a pre-commit hook is preventing the commit. If the commit succeeds but you notice that files were modified by the pre-commit hook, you MUST amend your commit to include them.
-
-6. Finally, run git status to make sure the commit succeeded.
-
-Important notes:
-- When possible, combine the "git add" and "git commit" commands into a single "git commit -am" command, to speed things up
-- However, be careful not to stage files (e.g. with 'git add .') for commits that aren't part of the change, they may have untracked files they want to keep around, but not commit.
-- NEVER update the git config
-- DO NOT push to the remote repository
-- IMPORTANT: Never use git commands with the -i flag (like git rebase -i or git add -i) since they require interactive input which is not supported.
-- If there are no changes to commit (i.e., no untracked files and no modifications), do not create an empty commit
-- Ensure your commit message is meaningful and concise. It should explain the purpose of the changes, not just describe them.
-- Return an empty response - the user will see the git output directly
-
-# Creating pull requests
-Use the gh command via the Bash tool for ALL GitHub-related tasks including working with issues, pull requests, checks, and releases. If given a Github URL use the gh command to get the information needed.
-
-IMPORTANT: When the user asks you to create a pull request, follow these steps carefully:
-
-1. Understand the current state of the branch. Remember to send a single message that contains multiple tool_use blocks (it is VERY IMPORTANT that you do this in a single message, otherwise it will feel slow to the user!):
- - Run a git status command to see all untracked files.
- - Run a git diff command to see both staged and unstaged changes that will be committed.
- - Check if the current branch tracks a remote branch and is up to date with the remote, so you know if you need to push to the remote
- - Run a git log command and 'git diff main...HEAD' to understand the full commit history for the current branch (from the time it diverged from the 'main' branch.)
-
-2. Create new branch if needed
-
-3. Commit changes if needed
-
-4. Push to remote with -u flag if needed
-
-5. Analyze all changes that will be included in the pull request, making sure to look at all relevant commits (not just the latest commit, but all commits that will be included in the pull request!), and draft a pull request summary. Wrap your analysis process in <pr_analysis> tags:
-
-<pr_analysis>
-- List the commits since diverging from the main branch
-- Summarize the nature of the changes (eg. new feature, enhancement to an existing feature, bug fix, refactoring, test, docs, etc.)
-- Brainstorm the purpose or motivation behind these changes
-- Assess the impact of these changes on the overall project
-- Do not use tools to explore code, beyond what is available in the git context
-- Check for any sensitive information that shouldn't be committed
-- Draft a concise (1-2 bullet points) pull request summary that focuses on the "why" rather than the "what"
-- Ensure the summary accurately reflects all changes since diverging from the main branch
-- Ensure your language is clear, concise, and to the point
-- Ensure the summary accurately reflects the changes and their purpose (ie. "add" means a wholly new feature, "update" means an enhancement to an existing feature, "fix" means a bug fix, etc.)
-- Ensure the summary is not generic (avoid words like "Update" or "Fix" without context)
-- Review the draft summary to ensure it accurately reflects the changes and their purpose
-</pr_analysis>
-
-6. Create PR using gh pr create with the format below. Use a HEREDOC to pass the body to ensure correct formatting.
-<example>
-gh pr create --title "the pr title" --body "$(cat <<'EOF'
-## Summary
-<1-3 bullet points>
-
-## Test plan
-[Checklist of TODOs for testing the pull request...]
-
-%s
-EOF
-)"
-</example>
-
-Important:
-- Return an empty response - the user will see the gh output directly
-- Never update git config`, bannedCommandsStr, MaxOutputLength, attributionStep, attributionExample, prAttribution)
+	var out bytes.Buffer
+	if err := bashDescriptionTpl.Execute(&out, bashDescriptionData{
+		BannedCommands:     bannedCommandsStr,
+		MaxOutputLength:    MaxOutputLength,
+		AttributionStep:    attributionStep,
+		AttributionExample: attributionExample,
+		PRAttribution:      prAttribution,
+	}); err != nil {
+		// this should never happen.
+		panic("failed to execute bash description template: " + err.Error())
+	}
+	return out.String()
 }
 
 func blockFuncs() []shell.BlockFunc {
  
  
  
    
    @@ -0,0 +1,161 @@
+Executes a given bash command in a persistent shell session with optional timeout, ensuring proper handling and security measures.
+
+CROSS-PLATFORM SHELL SUPPORT:
+
+- This tool uses a shell interpreter (mvdan/sh) that mimics the Bash language,
+  so you should use Bash syntax in all platforms, including Windows.
+  The most common shell builtins and core utils are available in Windows as
+  well.
+- Make sure to use forward slashes (/) as path separators in commands, even on
+  Windows. Example: "ls C:/foo/bar" instead of "ls C:\foo\bar".
+
+Before executing the command, please follow these steps:
+
+1. Directory Verification:
+
+- If the command will create new directories or files, first use the LS tool to verify the parent directory exists and is the correct location
+- For example, before running "mkdir foo/bar", first use LS to check that "foo" exists and is the intended parent directory
+
+2. Security Check:
+
+- For security and to limit the threat of a prompt injection attack, some commands are limited or banned. If you use a disallowed command, you will receive an error message explaining the restriction. Explain the error to the User.
+- Verify that the command is not one of the banned commands: {{ .BannedCommands }}.
+
+3. Command Execution:
+
+- After ensuring proper quoting, execute the command.
+- Capture the output of the command.
+
+4. Output Processing:
+
+- If the output exceeds {{ .MaxOutputLength }} characters, output will be truncated before being returned to you.
+- Prepare the output for display to the user.
+
+5. Return Result:
+
+- Provide the processed output of the command.
+- If any errors occurred during execution, include those in the output.
+- The result will also have metadata like the cwd (current working directory) at the end, included with <cwd></cwd> tags.
+
+Usage notes:
+
+- The command argument is required.
+- You can specify an optional timeout in milliseconds (up to 600000ms / 10 minutes). If not specified, commands will timeout after 30 minutes.
+- VERY IMPORTANT: You MUST avoid using search commands like 'find' and 'grep'. Instead use Grep, Glob, or Agent tools to search. You MUST avoid read tools like 'cat', 'head', 'tail', and 'ls', and use FileRead and LS tools to read files.
+- When issuing multiple commands, use the ';' or '&&' operator to separate them. DO NOT use newlines (newlines are ok in quoted strings).
+- IMPORTANT: All commands share the same shell session. Shell state (environment variables, virtual environments, current directory, etc.) persist between commands. For example, if you set an environment variable as part of a command, the environment variable will persist for subsequent commands.
+- Try to maintain your current working directory throughout the session by using absolute paths and avoiding usage of 'cd'. You may use 'cd' if the User explicitly requests it.
+  <good-example>
+  pytest /foo/bar/tests
+  </good-example>
+  <bad-example>
+  cd /foo/bar && pytest tests
+  </bad-example>
+
+# Committing changes with git
+
+When the user asks you to create a new git commit, follow these steps carefully:
+
+1. Start with a single message that contains exactly three tool_use blocks that do the following (it is VERY IMPORTANT that you send these tool_use blocks in a single message, otherwise it will feel slow to the user!):
+
+- Run a git status command to see all untracked files.
+- Run a git diff command to see both staged and unstaged changes that will be committed.
+- Run a git log command to see recent commit messages, so that you can follow this repository's commit message style.
+
+2. Use the git context at the start of this conversation to determine which files are relevant to your commit. Add relevant untracked files to the staging area. Do not commit files that were already modified at the start of this conversation, if they are not relevant to your commit.
+
+3. Analyze all staged changes (both previously staged and newly added) and draft a commit message. Wrap your analysis process in <commit_analysis> tags:
+
+<commit_analysis>
+
+- List the files that have been changed or added
+- Summarize the nature of the changes (eg. new feature, enhancement to an existing feature, bug fix, refactoring, test, docs, etc.)
+- Brainstorm the purpose or motivation behind these changes
+- Do not use tools to explore code, beyond what is available in the git context
+- Assess the impact of these changes on the overall project
+- Check for any sensitive information that shouldn't be committed
+- Draft a concise (1-2 sentences) commit message that focuses on the "why" rather than the "what"
+- Ensure your language is clear, concise, and to the point
+- Ensure the message accurately reflects the changes and their purpose (i.e. "add" means a wholly new feature, "update" means an enhancement to an existing feature, "fix" means a bug fix, etc.)
+- Ensure the message is not generic (avoid words like "Update" or "Fix" without context)
+- Review the draft message to ensure it accurately reflects the changes and their purpose
+  </commit_analysis>
+
+{{ .AttributionStep }}
+
+- In order to ensure good formatting, ALWAYS pass the commit message via a HEREDOC, a la this example:
+  {{ .AttributionExample }}
+
+5. If the commit fails due to pre-commit hook changes, retry the commit ONCE to include these automated changes. If it fails again, it usually means a pre-commit hook is preventing the commit. If the commit succeeds but you notice that files were modified by the pre-commit hook, you MUST amend your commit to include them.
+
+6. Finally, run git status to make sure the commit succeeded.
+
+Important notes:
+
+- When possible, combine the "git add" and "git commit" commands into a single "git commit -am" command, to speed things up
+- However, be careful not to stage files (e.g. with 'git add .') for commits that aren't part of the change, they may have untracked files they want to keep around, but not commit.
+- NEVER update the git config
+- DO NOT push to the remote repository
+- IMPORTANT: Never use git commands with the -i flag (like git rebase -i or git add -i) since they require interactive input which is not supported.
+- If there are no changes to commit (i.e., no untracked files and no modifications), do not create an empty commit
+- Ensure your commit message is meaningful and concise. It should explain the purpose of the changes, not just describe them.
+- Return an empty response - the user will see the git output directly
+
+# Creating pull requests
+
+Use the gh command via the Bash tool for ALL GitHub-related tasks including working with issues, pull requests, checks, and releases. If given a Github URL use the gh command to get the information needed.
+
+IMPORTANT: When the user asks you to create a pull request, follow these steps carefully:
+
+1. Understand the current state of the branch. Remember to send a single message that contains multiple tool_use blocks (it is VERY IMPORTANT that you do this in a single message, otherwise it will feel slow to the user!):
+
+- Run a git status command to see all untracked files.
+- Run a git diff command to see both staged and unstaged changes that will be committed.
+- Check if the current branch tracks a remote branch and is up to date with the remote, so you know if you need to push to the remote
+- Run a git log command and 'git diff main...HEAD' to understand the full commit history for the current branch (from the time it diverged from the 'main' branch.)
+
+2. Create new branch if needed
+
+3. Commit changes if needed
+
+4. Push to remote with -u flag if needed
+
+5. Analyze all changes that will be included in the pull request, making sure to look at all relevant commits (not just the latest commit, but all commits that will be included in the pull request!), and draft a pull request summary. Wrap your analysis process in <pr_analysis> tags:
+
+<pr_analysis>
+
+- List the commits since diverging from the main branch
+- Summarize the nature of the changes (eg. new feature, enhancement to an existing feature, bug fix, refactoring, test, docs, etc.)
+- Brainstorm the purpose or motivation behind these changes
+- Assess the impact of these changes on the overall project
+- Do not use tools to explore code, beyond what is available in the git context
+- Check for any sensitive information that shouldn't be committed
+- Draft a concise (1-2 bullet points) pull request summary that focuses on the "why" rather than the "what"
+- Ensure the summary accurately reflects all changes since diverging from the main branch
+- Ensure your language is clear, concise, and to the point
+- Ensure the summary accurately reflects the changes and their purpose (ie. "add" means a wholly new feature, "update" means an enhancement to an existing feature, "fix" means a bug fix, etc.)
+- Ensure the summary is not generic (avoid words like "Update" or "Fix" without context)
+- Review the draft summary to ensure it accurately reflects the changes and their purpose
+  </pr_analysis>
+
+6. Create PR using gh pr create with the format below. Use a HEREDOC to pass the body to ensure correct formatting.
+   <example>
+   gh pr create --title "the pr title" --body "$(cat <<'EOF'
+
+## Summary
+
+<1-3 bullet points>
+
+## Test plan
+
+[Checklist of TODOs for testing the pull request...]
+
+{{ .PRAttribution }}
+EOF
+)"
+</example>
+
+Important:
+
+- Return an empty response - the user will see the gh output directly
+- Never update git config
  
  
  
    
    @@ -2,6 +2,7 @@ package tools
 
 import (
 	"context"
+	_ "embed"
 	"encoding/json"
 	"fmt"
 	"log/slog"
@@ -22,30 +23,10 @@ type diagnosticsTool struct {
 	lspClients *csync.Map[string, *lsp.Client]
 }
 
-const (
-	DiagnosticsToolName    = "diagnostics"
-	diagnosticsDescription = `Get diagnostics for a file and/or project.
-WHEN TO USE THIS TOOL:
-- Use when you need to check for errors or warnings in your code
-- Helpful for debugging and ensuring code quality
-- Good for getting a quick overview of issues in a file or project
-HOW TO USE:
-- Provide a path to a file to get diagnostics for that file
-- Leave the path empty to get diagnostics for the entire project
-- Results are displayed in a structured format with severity levels
-FEATURES:
-- Displays errors, warnings, and hints
-- Groups diagnostics by severity
-- Provides detailed information about each diagnostic
-LIMITATIONS:
-- Results are limited to the diagnostics provided by the LSP clients
-- May not cover all possible issues in the code
-- Does not provide suggestions for fixing issues
-TIPS:
-- Use in conjunction with other tools for a comprehensive code review
-- Combine with the LSP client for real-time diagnostics
-`
-)
+const DiagnosticsToolName = "diagnostics"
+
+//go:embed diagnostics.md
+var diagnosticsDescription []byte
 
 func NewDiagnosticsTool(lspClients *csync.Map[string, *lsp.Client]) BaseTool {
 	return &diagnosticsTool{
@@ -60,7 +41,7 @@ func (b *diagnosticsTool) Name() string {
 func (b *diagnosticsTool) Info() ToolInfo {
 	return ToolInfo{
 		Name:        DiagnosticsToolName,
-		Description: diagnosticsDescription,
+		Description: string(diagnosticsDescription),
 		Parameters: map[string]any{
 			"file_path": map[string]any{
 				"type":        "string",
  
  
  
    
    @@ -0,0 +1,21 @@
+Get diagnostics for a file and/or project.
+WHEN TO USE THIS TOOL:
+
+- Use when you need to check for errors or warnings in your code
+- Helpful for debugging and ensuring code quality
+- Good for getting a quick overview of issues in a file or project
+  HOW TO USE:
+- Provide a path to a file to get diagnostics for that file
+- Leave the path empty to get diagnostics for the entire project
+- Results are displayed in a structured format with severity levels
+  FEATURES:
+- Displays errors, warnings, and hints
+- Groups diagnostics by severity
+- Provides detailed information about each diagnostic
+  LIMITATIONS:
+- Results are limited to the diagnostics provided by the LSP clients
+- May not cover all possible issues in the code
+- Does not provide suggestions for fixing issues
+  TIPS:
+- Use in conjunction with other tools for a comprehensive code review
+- Combine with the LSP client for real-time diagnostics
  
  
  
    
    @@ -2,6 +2,7 @@ package tools
 
 import (
 	"context"
+	_ "embed"
 	"encoding/json"
 	"fmt"
 	"io"
@@ -32,38 +33,10 @@ type downloadTool struct {
 	workingDir  string
 }
 
-const (
-	DownloadToolName        = "download"
-	downloadToolDescription = `Downloads binary data from a URL and saves it to a local file.
-
-WHEN TO USE THIS TOOL:
-- Use when you need to download files, images, or other binary data from URLs
-- Helpful for downloading assets, documents, or any file type
-- Useful for saving remote content locally for processing or storage
-
-HOW TO USE:
-- Provide the URL to download from
-- Specify the local file path where the content should be saved
-- Optionally set a timeout for the request
-
-FEATURES:
-- Downloads any file type (binary or text)
-- Automatically creates parent directories if they don't exist
-- Handles large files efficiently with streaming
-- Sets reasonable timeouts to prevent hanging
-- Validates input parameters before making requests
-
-LIMITATIONS:
-- Maximum file size is 100MB
-- Only supports HTTP and HTTPS protocols
-- Cannot handle authentication or cookies
-- Some websites may block automated requests
-- Will overwrite existing files without warning
-
-TIPS:
-- Use absolute paths or paths relative to the working directory
-- Set appropriate timeouts for large files or slow connections`
-)
+const DownloadToolName = "download"
+
+//go:embed download.md
+var downloadDescription []byte
 
 func NewDownloadTool(permissions permission.Service, workingDir string) BaseTool {
 	return &downloadTool{
@@ -87,7 +60,7 @@ func (t *downloadTool) Name() string {
 func (t *downloadTool) Info() ToolInfo {
 	return ToolInfo{
 		Name:        DownloadToolName,
-		Description: downloadToolDescription,
+		Description: string(downloadDescription),
 		Parameters: map[string]any{
 			"url": map[string]any{
 				"type":        "string",
  
  
  
    
    @@ -0,0 +1,34 @@
+Downloads binary data from a URL and saves it to a local file.
+
+WHEN TO USE THIS TOOL:
+
+- Use when you need to download files, images, or other binary data from URLs
+- Helpful for downloading assets, documents, or any file type
+- Useful for saving remote content locally for processing or storage
+
+HOW TO USE:
+
+- Provide the URL to download from
+- Specify the local file path where the content should be saved
+- Optionally set a timeout for the request
+
+FEATURES:
+
+- Downloads any file type (binary or text)
+- Automatically creates parent directories if they don't exist
+- Handles large files efficiently with streaming
+- Sets reasonable timeouts to prevent hanging
+- Validates input parameters before making requests
+
+LIMITATIONS:
+
+- Maximum file size is 100MB
+- Only supports HTTP and HTTPS protocols
+- Cannot handle authentication or cookies
+- Some websites may block automated requests
+- Will overwrite existing files without warning
+
+TIPS:
+
+- Use absolute paths or paths relative to the working directory
+- Set appropriate timeouts for large files or slow connections
  
  
  
    
    @@ -2,6 +2,7 @@ package tools
 
 import (
 	"context"
+	_ "embed"
 	"encoding/json"
 	"fmt"
 	"log/slog"
@@ -46,64 +47,10 @@ type editTool struct {
 	workingDir  string
 }
 
-const (
-	EditToolName    = "edit"
-	editDescription = `Edits files by replacing text, creating new files, or deleting content. For moving or renaming files, use the Bash tool with the 'mv' command instead. For larger file edits, use the FileWrite tool to overwrite files.
+const EditToolName = "edit"
 
-Before using this tool:
-
-1. Use the FileRead tool to understand the file's contents and context
-
-2. Verify the directory path is correct (only applicable when creating new files):
-   - Use the LS tool to verify the parent directory exists and is the correct location
-
-To make a file edit, provide the following:
-1. file_path: The absolute path to the file to modify (must be absolute, not relative)
-2. old_string: The text to replace (must be unique within the file, and must match the file contents exactly, including all whitespace and indentation)
-3. new_string: The edited text to replace the old_string
-4. replace_all: Replace all occurrences of old_string (default false)
-
-Special cases:
-- To create a new file: provide file_path and new_string, leave old_string empty
-- To delete content: provide file_path and old_string, leave new_string empty
-
-The tool will replace ONE occurrence of old_string with new_string in the specified file by default. Set replace_all to true to replace all occurrences.
-
-CRITICAL REQUIREMENTS FOR USING THIS TOOL:
-
-1. UNIQUENESS: When replace_all is false (default), the old_string MUST uniquely identify the specific instance you want to change. This means:
-   - Include AT LEAST 3-5 lines of context BEFORE the change point
-   - Include AT LEAST 3-5 lines of context AFTER the change point
-   - Include all whitespace, indentation, and surrounding code exactly as it appears in the file
-
-2. SINGLE INSTANCE: When replace_all is false, this tool can only change ONE instance at a time. If you need to change multiple instances:
-   - Set replace_all to true to replace all occurrences at once
-   - Or make separate calls to this tool for each instance
-   - Each call must uniquely identify its specific instance using extensive context
-
-3. VERIFICATION: Before using this tool:
-   - Check how many instances of the target text exist in the file
-   - If multiple instances exist and replace_all is false, gather enough context to uniquely identify each one
-   - Plan separate tool calls for each instance or use replace_all
-
-WARNING: If you do not follow these requirements:
-   - The tool will fail if old_string matches multiple locations and replace_all is false
-   - The tool will fail if old_string doesn't match exactly (including whitespace)
-   - You may change the wrong instance if you don't include enough context
-
-When making edits:
-   - Ensure the edit results in idiomatic, correct code
-   - Do not leave the code in a broken state
-   - Always use absolute file paths (starting with /)
-
-WINDOWS NOTES:
-- File paths should use forward slashes (/) for cross-platform compatibility
-- On Windows, absolute paths start with drive letters (C:/) but forward slashes work throughout
-- File permissions are handled automatically by the Go runtime
-- Always assumes \n for line endings. The tool will handle \r\n conversion automatically if needed.
-
-Remember: when making multiple file edits in a row to the same file, you should prefer to send all edits in a single message with multiple calls to this tool, rather than multiple messages with a single call each.`
-)
+//go:embed edit.md
+var editDescription []byte
 
 func NewEditTool(lspClients *csync.Map[string, *lsp.Client], permissions permission.Service, files history.Service, workingDir string) BaseTool {
 	return &editTool{
@@ -121,7 +68,7 @@ func (e *editTool) Name() string {
 func (e *editTool) Info() ToolInfo {
 	return ToolInfo{
 		Name:        EditToolName,
-		Description: editDescription,
+		Description: string(editDescription),
 		Parameters: map[string]any{
 			"file_path": map[string]any{
 				"type":        "string",
  
  
  
    
    @@ -0,0 +1,60 @@
+Edits files by replacing text, creating new files, or deleting content. For moving or renaming files, use the Bash tool with the 'mv' command instead. For larger file edits, use the FileWrite tool to overwrite files.
+
+Before using this tool:
+
+1. Use the FileRead tool to understand the file's contents and context
+
+2. Verify the directory path is correct (only applicable when creating new files):
+   - Use the LS tool to verify the parent directory exists and is the correct location
+
+To make a file edit, provide the following:
+
+1. file_path: The absolute path to the file to modify (must be absolute, not relative)
+2. old_string: The text to replace (must be unique within the file, and must match the file contents exactly, including all whitespace and indentation)
+3. new_string: The edited text to replace the old_string
+4. replace_all: Replace all occurrences of old_string (default false)
+
+Special cases:
+
+- To create a new file: provide file_path and new_string, leave old_string empty
+- To delete content: provide file_path and old_string, leave new_string empty
+
+The tool will replace ONE occurrence of old_string with new_string in the specified file by default. Set replace_all to true to replace all occurrences.
+
+CRITICAL REQUIREMENTS FOR USING THIS TOOL:
+
+1. UNIQUENESS: When replace_all is false (default), the old_string MUST uniquely identify the specific instance you want to change. This means:
+   - Include AT LEAST 3-5 lines of context BEFORE the change point
+   - Include AT LEAST 3-5 lines of context AFTER the change point
+   - Include all whitespace, indentation, and surrounding code exactly as it appears in the file
+
+2. SINGLE INSTANCE: When replace_all is false, this tool can only change ONE instance at a time. If you need to change multiple instances:
+   - Set replace_all to true to replace all occurrences at once
+   - Or make separate calls to this tool for each instance
+   - Each call must uniquely identify its specific instance using extensive context
+
+3. VERIFICATION: Before using this tool:
+   - Check how many instances of the target text exist in the file
+   - If multiple instances exist and replace_all is false, gather enough context to uniquely identify each one
+   - Plan separate tool calls for each instance or use replace_all
+
+WARNING: If you do not follow these requirements:
+
+- The tool will fail if old_string matches multiple locations and replace_all is false
+- The tool will fail if old_string doesn't match exactly (including whitespace)
+- You may change the wrong instance if you don't include enough context
+
+When making edits:
+
+- Ensure the edit results in idiomatic, correct code
+- Do not leave the code in a broken state
+- Always use absolute file paths (starting with /)
+
+WINDOWS NOTES:
+
+- File paths should use forward slashes (/) for cross-platform compatibility
+- On Windows, absolute paths start with drive letters (C:/) but forward slashes work throughout
+- File permissions are handled automatically by the Go runtime
+- Always assumes \n for line endings. The tool will handle \r\n conversion automatically if needed.
+
+Remember: when making multiple file edits in a row to the same file, you should prefer to send all edits in a single message with multiple calls to this tool, rather than multiple messages with a single call each.
  
  
  
    
    @@ -2,6 +2,7 @@ package tools
 
 import (
 	"context"
+	_ "embed"
 	"encoding/json"
 	"fmt"
 	"io"
@@ -33,38 +34,10 @@ type fetchTool struct {
 	workingDir  string
 }
 
-const (
-	FetchToolName        = "fetch"
-	fetchToolDescription = `Fetches content from a URL and returns it in the specified format.
-
-WHEN TO USE THIS TOOL:
-- Use when you need to download content from a URL
-- Helpful for retrieving documentation, API responses, or web content
-- Useful for getting external information to assist with tasks
-
-HOW TO USE:
-- Provide the URL to fetch content from
-- Specify the desired output format (text, markdown, or html)
-- Optionally set a timeout for the request
-
-FEATURES:
-- Supports three output formats: text, markdown, and html
-- Automatically handles HTTP redirects
-- Sets reasonable timeouts to prevent hanging
-- Validates input parameters before making requests
-
-LIMITATIONS:
-- Maximum response size is 5MB
-- Only supports HTTP and HTTPS protocols
-- Cannot handle authentication or cookies
-- Some websites may block automated requests
-
-TIPS:
-- Use text format for plain text content or simple API responses
-- Use markdown format for content that should be rendered with formatting
-- Use html format when you need the raw HTML structure
-- Set appropriate timeouts for potentially slow websites`
-)
+const FetchToolName = "fetch"
+
+//go:embed fetch.md
+var fetchDescription []byte
 
 func NewFetchTool(permissions permission.Service, workingDir string) BaseTool {
 	return &fetchTool{
@@ -88,7 +61,7 @@ func (t *fetchTool) Name() string {
 func (t *fetchTool) Info() ToolInfo {
 	return ToolInfo{
 		Name:        FetchToolName,
-		Description: fetchToolDescription,
+		Description: string(fetchDescription),
 		Parameters: map[string]any{
 			"url": map[string]any{
 				"type":        "string",
  
  
  
    
    @@ -0,0 +1,34 @@
+Fetches content from a URL and returns it in the specified format.
+
+WHEN TO USE THIS TOOL:
+
+- Use when you need to download content from a URL
+- Helpful for retrieving documentation, API responses, or web content
+- Useful for getting external information to assist with tasks
+
+HOW TO USE:
+
+- Provide the URL to fetch content from
+- Specify the desired output format (text, markdown, or html)
+- Optionally set a timeout for the request
+
+FEATURES:
+
+- Supports three output formats: text, markdown, and html
+- Automatically handles HTTP redirects
+- Sets reasonable timeouts to prevent hanging
+- Validates input parameters before making requests
+
+LIMITATIONS:
+
+- Maximum response size is 5MB
+- Only supports HTTP and HTTPS protocols
+- Cannot handle authentication or cookies
+- Some websites may block automated requests
+
+TIPS:
+
+- Use text format for plain text content or simple API responses
+- Use markdown format for content that should be rendered with formatting
+- Use html format when you need the raw HTML structure
+- Set appropriate timeouts for potentially slow websites
  
  
  
    
    @@ -3,6 +3,7 @@ package tools
 import (
 	"bytes"
 	"context"
+	_ "embed"
 	"encoding/json"
 	"fmt"
 	"log/slog"
@@ -14,48 +15,10 @@ import (
 	"github.com/charmbracelet/crush/internal/fsext"
 )
 
-const (
-	GlobToolName    = "glob"
-	globDescription = `Fast file pattern matching tool that finds files by name and pattern, returning matching paths sorted by modification time (newest first).
-
-WHEN TO USE THIS TOOL:
-- Use when you need to find files by name patterns or extensions
-- Great for finding specific file types across a directory structure
-- Useful for discovering files that match certain naming conventions
-
-HOW TO USE:
-- Provide a glob pattern to match against file paths
-- Optionally specify a starting directory (defaults to current working directory)
-- Results are sorted with most recently modified files first
-
-GLOB PATTERN SYNTAX:
-- '*' matches any sequence of non-separator characters
-- '**' matches any sequence of characters, including separators
-- '?' matches any single non-separator character
-- '[...]' matches any character in the brackets
-- '[!...]' matches any character not in the brackets
-
-COMMON PATTERN EXAMPLES:
-- '*.js' - Find all JavaScript files in the current directory
-- '**/*.js' - Find all JavaScript files in any subdirectory
-- 'src/**/*.{ts,tsx}' - Find all TypeScript files in the src directory
-- '*.{html,css,js}' - Find all HTML, CSS, and JS files
-
-LIMITATIONS:
-- Results are limited to 100 files (newest first)
-- Does not search file contents (use Grep tool for that)
-- Hidden files (starting with '.') are skipped
-
-WINDOWS NOTES:
-- Path separators are handled automatically (both / and \ work)
-- Uses ripgrep (rg) command if available, otherwise falls back to built-in Go implementation
-
-TIPS:
-- Patterns should use forward slashes (/) for cross-platform compatibility
-- For the most useful results, combine with the Grep tool: first find files with Glob, then search their contents with Grep
-- When doing iterative exploration that may require multiple rounds of searching, consider using the Agent tool instead
-- Always check if results are truncated and refine your search pattern if needed`
-)
+const GlobToolName = "glob"
+
+//go:embed glob.md
+var globDescription []byte
 
 type GlobParams struct {
 	Pattern string `json:"pattern"`
@@ -84,7 +47,7 @@ func (g *globTool) Name() string {
 func (g *globTool) Info() ToolInfo {
 	return ToolInfo{
 		Name:        GlobToolName,
-		Description: globDescription,
+		Description: string(globDescription),
 		Parameters: map[string]any{
 			"pattern": map[string]any{
 				"type":        "string",
  
  
  
    
    @@ -0,0 +1,46 @@
+Fast file pattern matching tool that finds files by name and pattern, returning matching paths sorted by modification time (newest first).
+
+WHEN TO USE THIS TOOL:
+
+- Use when you need to find files by name patterns or extensions
+- Great for finding specific file types across a directory structure
+- Useful for discovering files that match certain naming conventions
+
+HOW TO USE:
+
+- Provide a glob pattern to match against file paths
+- Optionally specify a starting directory (defaults to current working directory)
+- Results are sorted with most recently modified files first
+
+GLOB PATTERN SYNTAX:
+
+- '\*' matches any sequence of non-separator characters
+- '\*\*' matches any sequence of characters, including separators
+- '?' matches any single non-separator character
+- '[...]' matches any character in the brackets
+- '[!...]' matches any character not in the brackets
+
+COMMON PATTERN EXAMPLES:
+
+- '\*.js' - Find all JavaScript files in the current directory
+- '\*_/_.js' - Find all JavaScript files in any subdirectory
+- 'src/\*_/_.{ts,tsx}' - Find all TypeScript files in the src directory
+- '\*.{html,css,js}' - Find all HTML, CSS, and JS files
+
+LIMITATIONS:
+
+- Results are limited to 100 files (newest first)
+- Does not search file contents (use Grep tool for that)
+- Hidden files (starting with '.') are skipped
+
+WINDOWS NOTES:
+
+- Path separators are handled automatically (both / and \ work)
+- Uses ripgrep (rg) command if available, otherwise falls back to built-in Go implementation
+
+TIPS:
+
+- Patterns should use forward slashes (/) for cross-platform compatibility
+- For the most useful results, combine with the Grep tool: first find files with Glob, then search their contents with Grep
+- When doing iterative exploration that may require multiple rounds of searching, consider using the Agent tool instead
+- Always check if results are truncated and refine your search pattern if needed
  
  
  
    
    @@ -3,6 +3,7 @@ package tools
 import (
 	"bufio"
 	"context"
+	_ "embed"
 	"encoding/json"
 	"fmt"
 	"io"
@@ -92,55 +93,10 @@ type grepTool struct {
 	workingDir string
 }
 
-const (
-	GrepToolName    = "grep"
-	grepDescription = `Fast content search tool that finds files containing specific text or patterns, returning matching file paths sorted by modification time (newest first).
-
-WHEN TO USE THIS TOOL:
-- Use when you need to find files containing specific text or patterns
-- Great for searching code bases for function names, variable declarations, or error messages
-- Useful for finding all files that use a particular API or pattern
-
-HOW TO USE:
-- Provide a regex pattern to search for within file contents
-- Set literal_text=true if you want to search for the exact text with special characters (recommended for non-regex users)
-- Optionally specify a starting directory (defaults to current working directory)
-- Optionally provide an include pattern to filter which files to search
-- Results are sorted with most recently modified files first
-
-REGEX PATTERN SYNTAX (when literal_text=false):
-- Supports standard regular expression syntax
-- 'function' searches for the literal text "function"
-- 'log\..*Error' finds text starting with "log." and ending with "Error"
-- 'import\s+.*\s+from' finds import statements in JavaScript/TypeScript
-
-COMMON INCLUDE PATTERN EXAMPLES:
-- '*.js' - Only search JavaScript files
-- '*.{ts,tsx}' - Only search TypeScript files
-- '*.go' - Only search Go files
-
-LIMITATIONS:
-- Results are limited to 100 files (newest first)
-- Performance depends on the number of files being searched
-- Very large binary files may be skipped
-- Hidden files (starting with '.') are skipped
-
-IGNORE FILE SUPPORT:
-- Respects .gitignore patterns to skip ignored files and directories
-- Respects .crushignore patterns for additional ignore rules
-- Both ignore files are automatically detected in the search root directory
-
-CROSS-PLATFORM NOTES:
-- Uses ripgrep (rg) command if available for better performance
-- Falls back to built-in Go implementation if ripgrep is not available
-- File paths are normalized automatically for cross-platform compatibility
-
-TIPS:
-- For faster, more targeted searches, first use Glob to find relevant files, then use Grep
-- When doing iterative exploration that may require multiple rounds of searching, consider using the Agent tool instead
-- Always check if results are truncated and refine your search pattern if needed
-- Use literal_text=true when searching for exact text containing special characters like dots, parentheses, etc.`
-)
+const GrepToolName = "grep"
+
+//go:embed grep.md
+var grepDescription []byte
 
 func NewGrepTool(workingDir string) BaseTool {
 	return &grepTool{
@@ -155,7 +111,7 @@ func (g *grepTool) Name() string {
 func (g *grepTool) Info() ToolInfo {
 	return ToolInfo{
 		Name:        GrepToolName,
-		Description: grepDescription,
+		Description: string(grepDescription),
 		Parameters: map[string]any{
 			"pattern": map[string]any{
 				"type":        "string",
  
  
  
    
    @@ -0,0 +1,54 @@
+Fast content search tool that finds files containing specific text or patterns, returning matching file paths sorted by modification time (newest first).
+
+WHEN TO USE THIS TOOL:
+
+- Use when you need to find files containing specific text or patterns
+- Great for searching code bases for function names, variable declarations, or error messages
+- Useful for finding all files that use a particular API or pattern
+
+HOW TO USE:
+
+- Provide a regex pattern to search for within file contents
+- Set literal_text=true if you want to search for the exact text with special characters (recommended for non-regex users)
+- Optionally specify a starting directory (defaults to current working directory)
+- Optionally provide an include pattern to filter which files to search
+- Results are sorted with most recently modified files first
+
+REGEX PATTERN SYNTAX (when literal_text=false):
+
+- Supports standard regular expression syntax
+- 'function' searches for the literal text "function"
+- 'log\..\*Error' finds text starting with "log." and ending with "Error"
+- 'import\s+.\*\s+from' finds import statements in JavaScript/TypeScript
+
+COMMON INCLUDE PATTERN EXAMPLES:
+
+- '\*.js' - Only search JavaScript files
+- '\*.{ts,tsx}' - Only search TypeScript files
+- '\*.go' - Only search Go files
+
+LIMITATIONS:
+
+- Results are limited to 100 files (newest first)
+- Performance depends on the number of files being searched
+- Very large binary files may be skipped
+- Hidden files (starting with '.') are skipped
+
+IGNORE FILE SUPPORT:
+
+- Respects .gitignore patterns to skip ignored files and directories
+- Respects .crushignore patterns for additional ignore rules
+- Both ignore files are automatically detected in the search root directory
+
+CROSS-PLATFORM NOTES:
+
+- Uses ripgrep (rg) command if available for better performance
+- Falls back to built-in Go implementation if ripgrep is not available
+- File paths are normalized automatically for cross-platform compatibility
+
+TIPS:
+
+- For faster, more targeted searches, first use Glob to find relevant files, then use Grep
+- When doing iterative exploration that may require multiple rounds of searching, consider using the Agent tool instead
+- Always check if results are truncated and refine your search pattern if needed
+- Use literal_text=true when searching for exact text containing special characters like dots, parentheses, etc.
  
  
  
    
    @@ -2,6 +2,7 @@ package tools
 
 import (
 	"context"
+	_ "embed"
 	"encoding/json"
 	"fmt"
 	"os"
@@ -40,44 +41,13 @@ type lsTool struct {
 }
 
 const (
-	LSToolName    = "ls"
-	MaxLSFiles    = 1000
-	lsDescription = `Directory listing tool that shows files and subdirectories in a tree structure, helping you explore and understand the project organization.
-
-WHEN TO USE THIS TOOL:
-- Use when you need to explore the structure of a directory
-- Helpful for understanding the organization of a project
-- Good first step when getting familiar with a new codebase
-
-HOW TO USE:
-- Provide a path to list (defaults to current working directory)
-- Optionally specify glob patterns to ignore
-- Results are displayed in a tree structure
-
-FEATURES:
-- Displays a hierarchical view of files and directories
-- Automatically skips hidden files/directories (starting with '.')
-- Skips common system directories like __pycache__
-- Can filter out files matching specific patterns
-
-LIMITATIONS:
-- Results are limited to 1000 files
-- Very large directories will be truncated
-- Does not show file sizes or permissions
-- Cannot recursively list all directories in a large project
-
-WINDOWS NOTES:
-- Hidden file detection uses Unix convention (files starting with '.')
-- Windows-specific hidden files (with hidden attribute) are not automatically skipped
-- Common Windows directories like System32, Program Files are not in default ignore list
-- Path separators are handled automatically (both / and \ work)
-
-TIPS:
-- Use Glob tool for finding files by name patterns instead of browsing
-- Use Grep tool for searching file contents
-- Combine with other tools for more effective exploration`
+	LSToolName = "ls"
+	MaxLSFiles = 1000
 )
 
+//go:embed ls.md
+var lsDescription []byte
+
 func NewLsTool(permissions permission.Service, workingDir string) BaseTool {
 	return &lsTool{
 		workingDir:  workingDir,
@@ -92,7 +62,7 @@ func (l *lsTool) Name() string {
 func (l *lsTool) Info() ToolInfo {
 	return ToolInfo{
 		Name:        LSToolName,
-		Description: lsDescription,
+		Description: string(lsDescription),
 		Parameters: map[string]any{
 			"path": map[string]any{
 				"type":        "string",
  
  
  
    
    @@ -0,0 +1,40 @@
+Directory listing tool that shows files and subdirectories in a tree structure, helping you explore and understand the project organization.
+
+WHEN TO USE THIS TOOL:
+
+- Use when you need to explore the structure of a directory
+- Helpful for understanding the organization of a project
+- Good first step when getting familiar with a new codebase
+
+HOW TO USE:
+
+- Provide a path to list (defaults to current working directory)
+- Optionally specify glob patterns to ignore
+- Results are displayed in a tree structure
+
+FEATURES:
+
+- Displays a hierarchical view of files and directories
+- Automatically skips hidden files/directories (starting with '.')
+- Skips common system directories like **pycache**
+- Can filter out files matching specific patterns
+
+LIMITATIONS:
+
+- Results are limited to 1000 files
+- Very large directories will be truncated
+- Does not show file sizes or permissions
+- Cannot recursively list all directories in a large project
+
+WINDOWS NOTES:
+
+- Hidden file detection uses Unix convention (files starting with '.')
+- Windows-specific hidden files (with hidden attribute) are not automatically skipped
+- Common Windows directories like System32, Program Files are not in default ignore list
+- Path separators are handled automatically (both / and \ work)
+
+TIPS:
+
+- Use Glob tool for finding files by name patterns instead of browsing
+- Use Grep tool for searching file contents
+- Combine with other tools for more effective exploration
  
  
  
    
    @@ -2,6 +2,7 @@ package tools
 
 import (
 	"context"
+	_ "embed"
 	"encoding/json"
 	"fmt"
 	"log/slog"
@@ -50,51 +51,10 @@ type multiEditTool struct {
 	workingDir  string
 }
 
-const (
-	MultiEditToolName    = "multiedit"
-	multiEditDescription = `This is a tool for making multiple edits to a single file in one operation. It is built on top of the Edit tool and allows you to perform multiple find-and-replace operations efficiently. Prefer this tool over the Edit tool when you need to make multiple edits to the same file.
-
-Before using this tool:
-
-1. Use the Read tool to understand the file's contents and context
-
-2. Verify the directory path is correct
-
-To make multiple file edits, provide the following:
-1. file_path: The absolute path to the file to modify (must be absolute, not relative)
-2. edits: An array of edit operations to perform, where each edit contains:
-   - old_string: The text to replace (must match the file contents exactly, including all whitespace and indentation)
-   - new_string: The edited text to replace the old_string
-   - replace_all: Replace all occurrences of old_string. This parameter is optional and defaults to false.
-
-IMPORTANT:
-- All edits are applied in sequence, in the order they are provided
-- Each edit operates on the result of the previous edit
-- All edits must be valid for the operation to succeed - if any edit fails, none will be applied
-- This tool is ideal when you need to make several changes to different parts of the same file
-
-CRITICAL REQUIREMENTS:
-1. All edits follow the same requirements as the single Edit tool
-2. The edits are atomic - either all succeed or none are applied
-3. Plan your edits carefully to avoid conflicts between sequential operations
-
-WARNING:
-- The tool will fail if edits.old_string doesn't match the file contents exactly (including whitespace)
-- The tool will fail if edits.old_string and edits.new_string are the same
-- Since edits are applied in sequence, ensure that earlier edits don't affect the text that later edits are trying to find
-
-When making edits:
-- Ensure all edits result in idiomatic, correct code
-- Do not leave the code in a broken state
-- Always use absolute file paths (starting with /)
-- Only use emojis if the user explicitly requests it. Avoid adding emojis to files unless asked.
-- Use replace_all for replacing and renaming strings across the file. This parameter is useful if you want to rename a variable for instance.
-
-If you want to create a new file, use:
-- A new file path, including dir name if needed
-- First edit: empty old_string and the new file's contents as new_string
-- Subsequent edits: normal edit operations on the created content`
-)
+const MultiEditToolName = "multiedit"
+
+//go:embed multiedit.md
+var multieditDescription []byte
 
 func NewMultiEditTool(lspClients *csync.Map[string, *lsp.Client], permissions permission.Service, files history.Service, workingDir string) BaseTool {
 	return &multiEditTool{
@@ -112,7 +72,7 @@ func (m *multiEditTool) Name() string {
 func (m *multiEditTool) Info() ToolInfo {
 	return ToolInfo{
 		Name:        MultiEditToolName,
-		Description: multiEditDescription,
+		Description: string(multieditDescription),
 		Parameters: map[string]any{
 			"file_path": map[string]any{
 				"type":        "string",
  
  
  
    
    @@ -0,0 +1,48 @@
+This is a tool for making multiple edits to a single file in one operation. It is built on top of the Edit tool and allows you to perform multiple find-and-replace operations efficiently. Prefer this tool over the Edit tool when you need to make multiple edits to the same file.
+
+Before using this tool:
+
+1. Use the Read tool to understand the file's contents and context
+
+2. Verify the directory path is correct
+
+To make multiple file edits, provide the following:
+
+1. file_path: The absolute path to the file to modify (must be absolute, not relative)
+2. edits: An array of edit operations to perform, where each edit contains:
+   - old_string: The text to replace (must match the file contents exactly, including all whitespace and indentation)
+   - new_string: The edited text to replace the old_string
+   - replace_all: Replace all occurrences of old_string. This parameter is optional and defaults to false.
+
+IMPORTANT:
+
+- All edits are applied in sequence, in the order they are provided
+- Each edit operates on the result of the previous edit
+- All edits must be valid for the operation to succeed - if any edit fails, none will be applied
+- This tool is ideal when you need to make several changes to different parts of the same file
+
+CRITICAL REQUIREMENTS:
+
+1. All edits follow the same requirements as the single Edit tool
+2. The edits are atomic - either all succeed or none are applied
+3. Plan your edits carefully to avoid conflicts between sequential operations
+
+WARNING:
+
+- The tool will fail if edits.old_string doesn't match the file contents exactly (including whitespace)
+- The tool will fail if edits.old_string and edits.new_string are the same
+- Since edits are applied in sequence, ensure that earlier edits don't affect the text that later edits are trying to find
+
+When making edits:
+
+- Ensure all edits result in idiomatic, correct code
+- Do not leave the code in a broken state
+- Always use absolute file paths (starting with /)
+- Only use emojis if the user explicitly requests it. Avoid adding emojis to files unless asked.
+- Use replace_all for replacing and renaming strings across the file. This parameter is useful if you want to rename a variable for instance.
+
+If you want to create a new file, use:
+
+- A new file path, including dir name if needed
+- First edit: empty old_string and the new file's contents as new_string
+- Subsequent edits: normal edit operations on the created content
  
  
  
    
    @@ -3,6 +3,7 @@ package tools
 import (
 	"bytes"
 	"context"
+	_ "embed"
 	"encoding/json"
 	"fmt"
 	"io"
@@ -27,103 +28,10 @@ type sourcegraphTool struct {
 	client *http.Client
 }
 
-const (
-	SourcegraphToolName        = "sourcegraph"
-	sourcegraphToolDescription = `Search code across public repositories using Sourcegraph's GraphQL API.
-
-WHEN TO USE THIS TOOL:
-- Use when you need to find code examples or implementations across public repositories
-- Helpful for researching how others have solved similar problems
-- Useful for discovering patterns and best practices in open source code
-
-HOW TO USE:
-- Provide a search query using Sourcegraph's query syntax
-- Optionally specify the number of results to return (default: 10)
-- Optionally set a timeout for the request
-
-QUERY SYNTAX:
-- Basic search: "fmt.Println" searches for exact matches
-- File filters: "file:.go fmt.Println" limits to Go files
-- Repository filters: "repo:^github\.com/golang/go$ fmt.Println" limits to specific repos
-- Language filters: "lang:go fmt.Println" limits to Go code
-- Boolean operators: "fmt.Println AND log.Fatal" for combined terms
-- Regular expressions: "fmt\.(Print|Printf|Println)" for pattern matching
-- Quoted strings: "\"exact phrase\"" for exact phrase matching
-- Exclude filters: "-file:test" or "-repo:forks" to exclude matches
-
-ADVANCED FILTERS:
-- Repository filters:
-  * "repo:name" - Match repositories with name containing "name"
-  * "repo:^github\.com/org/repo$" - Exact repository match
-  * "repo:org/repo@branch" - Search specific branch
-  * "repo:org/repo rev:branch" - Alternative branch syntax
-  * "-repo:name" - Exclude repositories
-  * "fork:yes" or "fork:only" - Include or only show forks
-  * "archived:yes" or "archived:only" - Include or only show archived repos
-  * "visibility:public" or "visibility:private" - Filter by visibility
-
-- File filters:
-  * "file:\.js$" - Files with .js extension
-  * "file:internal/" - Files in internal directory
-  * "-file:test" - Exclude test files
-  * "file:has.content(Copyright)" - Files containing "Copyright"
-  * "file:has.contributor([email protected])" - Files with specific contributor
-
-- Content filters:
-  * "content:\"exact string\"" - Search for exact string
-  * "-content:\"unwanted\"" - Exclude files with unwanted content
-  * "case:yes" - Case-sensitive search
-
-- Type filters:
-  * "type:symbol" - Search for symbols (functions, classes, etc.)
-  * "type:file" - Search file content only
-  * "type:path" - Search filenames only
-  * "type:diff" - Search code changes
-  * "type:commit" - Search commit messages
-
-- Commit/diff search:
-  * "after:\"1 month ago\"" - Commits after date
-  * "before:\"2023-01-01\"" - Commits before date
-  * "author:name" - Commits by author
-  * "message:\"fix bug\"" - Commits with message
-
-- Result selection:
-  * "select:repo" - Show only repository names
-  * "select:file" - Show only file paths
-  * "select:content" - Show only matching content
-  * "select:symbol" - Show only matching symbols
-
-- Result control:
-  * "count:100" - Return up to 100 results
-  * "count:all" - Return all results
-  * "timeout:30s" - Set search timeout
-
-EXAMPLES:
-- "file:.go context.WithTimeout" - Find Go code using context.WithTimeout
-- "lang:typescript useState type:symbol" - Find TypeScript React useState hooks
-- "repo:^github\.com/kubernetes/kubernetes$ pod list type:file" - Find Kubernetes files related to pod listing
-- "repo:sourcegraph/sourcegraph$ after:\"3 months ago\" type:diff database" - Recent changes to database code
-- "file:Dockerfile (alpine OR ubuntu) -content:alpine:latest" - Dockerfiles with specific base images
-- "repo:has.path(\.py) file:requirements.txt tensorflow" - Python projects using TensorFlow
-
-BOOLEAN OPERATORS:
-- "term1 AND term2" - Results containing both terms
-- "term1 OR term2" - Results containing either term
-- "term1 NOT term2" - Results with term1 but not term2
-- "term1 and (term2 or term3)" - Grouping with parentheses
-
-LIMITATIONS:
-- Only searches public repositories
-- Rate limits may apply
-- Complex queries may take longer to execute
-- Maximum of 20 results per query
-
-TIPS:
-- Use specific file extensions to narrow results
-- Add repo: filters for more targeted searches
-- Use type:symbol to find function/method definitions
-- Use type:file to find relevant files`
-)
+const SourcegraphToolName = "sourcegraph"
+
+//go:embed sourcegraph.md
+var sourcegraphDescription []byte
 
 func NewSourcegraphTool() BaseTool {
 	return &sourcegraphTool{
@@ -145,7 +53,7 @@ func (t *sourcegraphTool) Name() string {
 func (t *sourcegraphTool) Info() ToolInfo {
 	return ToolInfo{
 		Name:        SourcegraphToolName,
-		Description: sourcegraphToolDescription,
+		Description: string(sourcegraphDescription),
 		Parameters: map[string]any{
 			"query": map[string]any{
 				"type":        "string",
  
  
  
    
    @@ -0,0 +1,102 @@
+Search code across public repositories using Sourcegraph's GraphQL API.
+
+WHEN TO USE THIS TOOL:
+
+- Use when you need to find code examples or implementations across public repositories
+- Helpful for researching how others have solved similar problems
+- Useful for discovering patterns and best practices in open source code
+
+HOW TO USE:
+
+- Provide a search query using Sourcegraph's query syntax
+- Optionally specify the number of results to return (default: 10)
+- Optionally set a timeout for the request
+
+QUERY SYNTAX:
+
+- Basic search: "fmt.Println" searches for exact matches
+- File filters: "file:.go fmt.Println" limits to Go files
+- Repository filters: "repo:^github\.com/golang/go$ fmt.Println" limits to specific repos
+- Language filters: "lang:go fmt.Println" limits to Go code
+- Boolean operators: "fmt.Println AND log.Fatal" for combined terms
+- Regular expressions: "fmt\.(Print|Printf|Println)" for pattern matching
+- Quoted strings: "\"exact phrase\"" for exact phrase matching
+- Exclude filters: "-file:test" or "-repo:forks" to exclude matches
+
+ADVANCED FILTERS:
+
+- Repository filters:
+  - "repo:name" - Match repositories with name containing "name"
+  - "repo:^github\.com/org/repo$" - Exact repository match
+  - "repo:org/repo@branch" - Search specific branch
+  - "repo:org/repo rev:branch" - Alternative branch syntax
+  - "-repo:name" - Exclude repositories
+  - "fork:yes" or "fork:only" - Include or only show forks
+  - "archived:yes" or "archived:only" - Include or only show archived repos
+  - "visibility:public" or "visibility:private" - Filter by visibility
+
+- File filters:
+  - "file:\.js$" - Files with .js extension
+  - "file:internal/" - Files in internal directory
+  - "-file:test" - Exclude test files
+  - "file:has.content(Copyright)" - Files containing "Copyright"
+  - "file:has.contributor([email protected])" - Files with specific contributor
+
+- Content filters:
+  - "content:\"exact string\"" - Search for exact string
+  - "-content:\"unwanted\"" - Exclude files with unwanted content
+  - "case:yes" - Case-sensitive search
+
+- Type filters:
+  - "type:symbol" - Search for symbols (functions, classes, etc.)
+  - "type:file" - Search file content only
+  - "type:path" - Search filenames only
+  - "type:diff" - Search code changes
+  - "type:commit" - Search commit messages
+
+- Commit/diff search:
+  - "after:\"1 month ago\"" - Commits after date
+  - "before:\"2023-01-01\"" - Commits before date
+  - "author:name" - Commits by author
+  - "message:\"fix bug\"" - Commits with message
+
+- Result selection:
+  - "select:repo" - Show only repository names
+  - "select:file" - Show only file paths
+  - "select:content" - Show only matching content
+  - "select:symbol" - Show only matching symbols
+
+- Result control:
+  - "count:100" - Return up to 100 results
+  - "count:all" - Return all results
+  - "timeout:30s" - Set search timeout
+
+EXAMPLES:
+
+- "file:.go context.WithTimeout" - Find Go code using context.WithTimeout
+- "lang:typescript useState type:symbol" - Find TypeScript React useState hooks
+- "repo:^github\.com/kubernetes/kubernetes$ pod list type:file" - Find Kubernetes files related to pod listing
+- "repo:sourcegraph/sourcegraph$ after:\"3 months ago\" type:diff database" - Recent changes to database code
+- "file:Dockerfile (alpine OR ubuntu) -content:alpine:latest" - Dockerfiles with specific base images
+- "repo:has.path(\.py) file:requirements.txt tensorflow" - Python projects using TensorFlow
+
+BOOLEAN OPERATORS:
+
+- "term1 AND term2" - Results containing both terms
+- "term1 OR term2" - Results containing either term
+- "term1 NOT term2" - Results with term1 but not term2
+- "term1 and (term2 or term3)" - Grouping with parentheses
+
+LIMITATIONS:
+
+- Only searches public repositories
+- Rate limits may apply
+- Complex queries may take longer to execute
+- Maximum of 20 results per query
+
+TIPS:
+
+- Use specific file extensions to narrow results
+- Add repo: filters for more targeted searches
+- Use type:symbol to find function/method definitions
+- Use type:file to find relevant files
  
  
  
    
    @@ -3,6 +3,7 @@ package tools
 import (
 	"bufio"
 	"context"
+	_ "embed"
 	"encoding/json"
 	"fmt"
 	"io"
@@ -16,6 +17,9 @@ import (
 	"github.com/charmbracelet/crush/internal/permission"
 )
 
+//go:embed view.md
+var viewDescription []byte
+
 type ViewParams struct {
 	FilePath string `json:"file_path"`
 	Offset   int    `json:"offset"`
@@ -44,42 +48,6 @@ const (
 	MaxReadSize      = 250 * 1024
 	DefaultReadLimit = 2000
 	MaxLineLength    = 2000
-	viewDescription  = `File viewing tool that reads and displays the contents of files with line numbers, allowing you to examine code, logs, or text data.
-
-WHEN TO USE THIS TOOL:
-- Use when you need to read the contents of a specific file
-- Helpful for examining source code, configuration files, or log files
-- Perfect for looking at text-based file formats
-
-HOW TO USE:
-- Provide the path to the file you want to view
-- Optionally specify an offset to start reading from a specific line
-- Optionally specify a limit to control how many lines are read
-- Do not use this for directories use the ls tool instead
-
-FEATURES:
-- Displays file contents with line numbers for easy reference
-- Can read from any position in a file using the offset parameter
-- Handles large files by limiting the number of lines read
-- Automatically truncates very long lines for better display
-- Suggests similar file names when the requested file isn't found
-
-LIMITATIONS:
-- Maximum file size is 250KB
-- Default reading limit is 2000 lines
-- Lines longer than 2000 characters are truncated
-- Cannot display binary files or images
-- Images can be identified but not displayed
-
-WINDOWS NOTES:
-- Handles both Windows (CRLF) and Unix (LF) line endings automatically
-- File paths work with both forward slashes (/) and backslashes (\)
-- Text encoding is detected automatically for most common formats
-
-TIPS:
-- Use with Glob tool to first find files you want to view
-- For code exploration, first use Grep to find relevant files, then View to examine them
-- When viewing large files, use the offset parameter to read specific sections`
 )
 
 func NewViewTool(lspClients *csync.Map[string, *lsp.Client], permissions permission.Service, workingDir string) BaseTool {
@@ -97,7 +65,7 @@ func (v *viewTool) Name() string {
 func (v *viewTool) Info() ToolInfo {
 	return ToolInfo{
 		Name:        ViewToolName,
-		Description: viewDescription,
+		Description: string(viewDescription),
 		Parameters: map[string]any{
 			"file_path": map[string]any{
 				"type":        "string",
  
  
  
    
    @@ -0,0 +1,42 @@
+File viewing tool that reads and displays the contents of files with line numbers, allowing you to examine code, logs, or text data.
+
+WHEN TO USE THIS TOOL:
+
+- Use when you need to read the contents of a specific file
+- Helpful for examining source code, configuration files, or log files
+- Perfect for looking at text-based file formats
+
+HOW TO USE:
+
+- Provide the path to the file you want to view
+- Optionally specify an offset to start reading from a specific line
+- Optionally specify a limit to control how many lines are read
+- Do not use this for directories use the ls tool instead
+
+FEATURES:
+
+- Displays file contents with line numbers for easy reference
+- Can read from any position in a file using the offset parameter
+- Handles large files by limiting the number of lines read
+- Automatically truncates very long lines for better display
+- Suggests similar file names when the requested file isn't found
+
+LIMITATIONS:
+
+- Maximum file size is 250KB
+- Default reading limit is 2000 lines
+- Lines longer than 2000 characters are truncated
+- Cannot display binary files or images
+- Images can be identified but not displayed
+
+WINDOWS NOTES:
+
+- Handles both Windows (CRLF) and Unix (LF) line endings automatically
+- File paths work with both forward slashes (/) and backslashes (\)
+- Text encoding is detected automatically for most common formats
+
+TIPS:
+
+- Use with Glob tool to first find files you want to view
+- For code exploration, first use Grep to find relevant files, then View to examine them
+- When viewing large files, use the offset parameter to read specific sections
  
  
  
    
    @@ -2,6 +2,7 @@ package tools
 
 import (
 	"context"
+	_ "embed"
 	"encoding/json"
 	"fmt"
 	"log/slog"
@@ -19,6 +20,9 @@ import (
 	"github.com/charmbracelet/crush/internal/permission"
 )
 
+//go:embed write.md
+var writeDescription []byte
+
 type WriteParams struct {
 	FilePath string `json:"file_path"`
 	Content  string `json:"content"`
@@ -43,41 +47,7 @@ type WriteResponseMetadata struct {
 	Removals  int    `json:"removals"`
 }
 
-const (
-	WriteToolName    = "write"
-	writeDescription = `File writing tool that creates or updates files in the filesystem, allowing you to save or modify text content.
-
-WHEN TO USE THIS TOOL:
-- Use when you need to create a new file
-- Helpful for updating existing files with modified content
-- Perfect for saving generated code, configurations, or text data
-
-HOW TO USE:
-- Provide the path to the file you want to write
-- Include the content to be written to the file
-- The tool will create any necessary parent directories
-
-FEATURES:
-- Can create new files or overwrite existing ones
-- Creates parent directories automatically if they don't exist
-- Checks if the file has been modified since last read for safety
-- Avoids unnecessary writes when content hasn't changed
-
-LIMITATIONS:
-- You should read a file before writing to it to avoid conflicts
-- Cannot append to files (rewrites the entire file)
-
-WINDOWS NOTES:
-- File permissions (0o755, 0o644) are Unix-style but work on Windows with appropriate translations
-- Use forward slashes (/) in paths for cross-platform compatibility
-- Windows file attributes and permissions are handled automatically by the Go runtime
-
-TIPS:
-- Use the View tool first to examine existing files before modifying them
-- Use the LS tool to verify the correct location when creating new files
-- Combine with Glob and Grep tools to find and modify multiple files
-- Always include descriptive comments when making changes to existing code`
-)
+const WriteToolName = "write"
 
 func NewWriteTool(lspClients *csync.Map[string, *lsp.Client], permissions permission.Service, files history.Service, workingDir string) BaseTool {
 	return &writeTool{
@@ -95,7 +65,7 @@ func (w *writeTool) Name() string {
 func (w *writeTool) Info() ToolInfo {
 	return ToolInfo{
 		Name:        WriteToolName,
-		Description: writeDescription,
+		Description: string(writeDescription),
 		Parameters: map[string]any{
 			"file_path": map[string]any{
 				"type":        "string",
  
  
  
    
    @@ -0,0 +1,38 @@
+File writing tool that creates or updates files in the filesystem, allowing you to save or modify text content.
+
+WHEN TO USE THIS TOOL:
+
+- Use when you need to create a new file
+- Helpful for updating existing files with modified content
+- Perfect for saving generated code, configurations, or text data
+
+HOW TO USE:
+
+- Provide the path to the file you want to write
+- Include the content to be written to the file
+- The tool will create any necessary parent directories
+
+FEATURES:
+
+- Can create new files or overwrite existing ones
+- Creates parent directories automatically if they don't exist
+- Checks if the file has been modified since last read for safety
+- Avoids unnecessary writes when content hasn't changed
+
+LIMITATIONS:
+
+- You should read a file before writing to it to avoid conflicts
+- Cannot append to files (rewrites the entire file)
+
+WINDOWS NOTES:
+
+- File permissions (0o755, 0o644) are Unix-style but work on Windows with appropriate translations
+- Use forward slashes (/) in paths for cross-platform compatibility
+- Windows file attributes and permissions are handled automatically by the Go runtime
+
+TIPS:
+
+- Use the View tool first to examine existing files before modifying them
+- Use the LS tool to verify the correct location when creating new files
+- Combine with Glob and Grep tools to find and modify multiple files
+- Always include descriptive comments when making changes to existing code