Add permission callback to Bash tool to enforce title setting before git commits

- Convert Bash tool from a plain variable to a struct with a permission callback function
- Add a constructor for creating Bash tools with custom callback functions
- Implement a permission check in agent.go that verifies a branch name has been set before allowing git commits
- If no branch name is set and a git commit command is attempted, return an error instructing to use the title tool first
- Maintain backward compatibility for tests with BashRun function

Co-Authored-By: sketch <hello@sketch.dev>
diff --git a/claudetool/bash.go b/claudetool/bash.go
index 2edd230..b3b8b03 100644
--- a/claudetool/bash.go
+++ b/claudetool/bash.go
@@ -17,14 +17,32 @@
 	"sketch.dev/claudetool/bashkit"
 )
 
-// The Bash tool executes shell commands with bash -c and optional timeout
-var Bash = &ant.Tool{
-	Name:        bashName,
-	Description: strings.TrimSpace(bashDescription),
-	InputSchema: ant.MustSchema(bashInputSchema),
-	Run:         BashRun,
+// PermissionCallback is a function type for checking if a command is allowed to run
+type PermissionCallback func(command string) error
+
+// BashTool is a struct for executing shell commands with bash -c and optional timeout
+type BashTool struct {
+	// CheckPermission is called before running any command, if set
+	CheckPermission PermissionCallback
 }
 
+// NewBashTool creates a new Bash tool with optional permission callback
+func NewBashTool(checkPermission PermissionCallback) *ant.Tool {
+	tool := &BashTool{
+		CheckPermission: checkPermission,
+	}
+
+	return &ant.Tool{
+		Name:        bashName,
+		Description: strings.TrimSpace(bashDescription),
+		InputSchema: ant.MustSchema(bashInputSchema),
+		Run:         tool.Run,
+	}
+}
+
+// The Bash tool executes shell commands with bash -c and optional timeout
+var Bash = NewBashTool(nil)
+
 const (
 	bashName        = "bash"
 	bashDescription = `
@@ -96,17 +114,25 @@
 	}
 }
 
-func BashRun(ctx context.Context, m json.RawMessage) (string, error) {
+func (b *BashTool) Run(ctx context.Context, m json.RawMessage) (string, error) {
 	var req bashInput
 	if err := json.Unmarshal(m, &req); err != nil {
 		return "", fmt.Errorf("failed to unmarshal bash command input: %w", err)
 	}
+
 	// do a quick permissions check (NOT a security barrier)
 	err := bashkit.Check(req.Command)
 	if err != nil {
 		return "", err
 	}
 
+	// Custom permission callback if set
+	if b.CheckPermission != nil {
+		if err := b.CheckPermission(req.Command); err != nil {
+			return "", err
+		}
+	}
+
 	// If Background is set to true, use executeBackgroundBash
 	if req.Background {
 		result, err := executeBackgroundBash(ctx, req)
@@ -278,3 +304,9 @@
 		StderrFile: stderrFile,
 	}, nil
 }
+
+// BashRun is the legacy function for testing compatibility
+func BashRun(ctx context.Context, m json.RawMessage) (string, error) {
+	// Use the default Bash tool which has no permission callback
+	return Bash.Run(ctx, m)
+}